Fenster/Tueren LoD + Stile + Phase-3-Ausschnitt-Darstellung + UI-Konsistenz
Fenster/Tueren: - 3-stufige SIA-400-Darstellung pro Element: einfach (1:100, flache Scheibe ohne Tiefe in Wand-Mittelebene), standard (1:50, Rahmen + Glas + Sims), detail (1:20, Doppelverglasung). - Aussenseite-Flag mit Auto-Detection aus der Click-Richtung beim Setzen — Sim sitzt automatisch aussen. Im Panel als Umkehren-Toggle. - Tueren-Rahmen-Typ Zarge|Block — Blockrahmen ragt seitlich raus. - Rahmen-Offset (m von Wand-Innenseite) ersetzt das 3-Preset Lage- Feld. Wirkt auch in der einfachen Darstellung (Pane sitzt auf der Rahmen-Mittelebene, nicht in Wand-Mitte). - Sims nur AUSSEN. Innen entfaellt — der Sim ist gleichzeitig der visuelle Indikator fuer die Aussenseite. - Oeffnungs-Stile: list/save/delete-API mit 6 Default-Presets (Fenster Standard/Gross/Bandlage, Tuer Innen/Eingang/Verglast). Style-ID per UserString am Objekt persistiert. Im Panel BarCombo mit "Aktuelle als Stil speichern…". Beim Rhino-Command "Stil"- Option zum Picken vor dem Klick. Ausschnitt-Darstellung (Phase 3): - Doc-Level Override dossier_aktive_darstellung gewinnt vor per- Object-Setting. Wechsel triggert Regen aller Oeffnungen via neuer regenerate_all_oeffnungen-API. - Ausschnitt-Capture speichert die Darstellung mit, Restore wendet sie an und regeneriert. - Oberleiste-Quick-Switch BarCombo mit 4 Optionen. - AusschnittSettings-Dialog: Darstellungs-Dropdown. Gestaltung (SectionStyle Phase 2): - _set_section_style schreibt per-Object SectionHatchIndex/Scale/ Rotation/Color mit Multi-Fallback (Property-Namen varieren je Rhino-Build). _selection_summary liest die selben zurueck. - HatchEditor als shared Component fuer Fill + Section. - geometryKind ignoriert DOSSIER-Source-Curves damit Wand-Selektion (Axis + Volume) als 3D klassifiziert wird. UI-Konsistenz Panels: - Ebenenkombi zurueck als eigene Section oben im Ebenen-Panel, Modelldarstellung-Dropdown an die freigewordene Position in der Oberleiste (Row 1 Col 2 im 2x2-Preset-Block). - BarCombo erweitert: stretch-Prop (Pill waechst auf Container- Breite), onSecond/secondIcon/secondTitle fuer 2. Trailing-Button, gearIcon-Prop. Plus-Slot immer ganz aussen rechts, Settings-Slot direkt nach dem Caret. - Ebenen + Zeichnungsebenen visuell kohaerent: identisches Padding (1px 12px 1px 0), Chevron/Spacer-Slot 12px, Master-Row mit Eye 16x16 + Lock 14x14, gleiche Border + Borderfarbe. Eye-Icons in beiden Panels untereinander ausgerichtet. - Properties-Container ohne Border (war zuvor accent-gruen, dann border — User wollte gar nichts mehr). - ElementList raus aus dem Elemente-Panel (Uebersicht via Tree- Window erreichbar). NeuesElement bleibt voll sichtbar bei Selektion (kein Collapse), Properties oben. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -327,6 +327,8 @@ class AusschnittBridge(panel_base.BaseBridge):
|
||||
elif t == "DELETE": self._delete(p.get("id"))
|
||||
elif t == "SET_FOLDER": self._set_field(p.get("id"), "folder", p.get("folder") or "")
|
||||
elif t == "SET_SCALE": self._set_field(p.get("id"), "scale", p.get("scale") or "")
|
||||
elif t == "SET_DARSTELLUNG": self._set_field(p.get("id"), "darstellung",
|
||||
p.get("darstellung") or "")
|
||||
elif t == "DUPLICATE": self._duplicate(p.get("id"))
|
||||
elif t == "ADD_FOLDER": self._add_folder(p.get("name"))
|
||||
elif t == "REMOVE_FOLDER": self._remove_folder(p.get("name"))
|
||||
@@ -494,6 +496,13 @@ class AusschnittBridge(panel_base.BaseBridge):
|
||||
print("[AUSSCHNITTE] Live-Skala (Fallback):", ex)
|
||||
if not scale_str and prior_scale:
|
||||
scale_str = prior_scale # Perspective -> alten Wert nicht ueberschreiben
|
||||
# Darstellungs-Override aus dem aktuellen Doc-Setting uebernehmen.
|
||||
# Leer = "kein Override, per-Object respektieren".
|
||||
darst = ""
|
||||
try:
|
||||
import elemente
|
||||
darst = elemente.get_aktive_darstellung(doc) or ""
|
||||
except Exception: pass
|
||||
return {
|
||||
"id": existing_id or "snap_" + uuid.uuid4().hex[:8],
|
||||
"name": name,
|
||||
@@ -501,6 +510,7 @@ class AusschnittBridge(panel_base.BaseBridge):
|
||||
"camera": _capture_camera(vp),
|
||||
"layers": _capture_layers(doc),
|
||||
"dossier": _capture_dossier_state(doc),
|
||||
"darstellung": darst,
|
||||
}
|
||||
|
||||
def _save(self, name):
|
||||
@@ -558,6 +568,21 @@ class AusschnittBridge(panel_base.BaseBridge):
|
||||
rhinopanel._notify_oberleiste_combs()
|
||||
except Exception: pass
|
||||
_apply_dossier_state(doc, snap.get("dossier") or snap.get("pause") or {})
|
||||
# Darstellung anwenden + Oeffnungen regenerieren
|
||||
try:
|
||||
import elemente
|
||||
new_darst = snap.get("darstellung") or ""
|
||||
cur_darst = elemente.get_aktive_darstellung(doc) or ""
|
||||
if new_darst != cur_darst:
|
||||
elemente.set_aktive_darstellung(doc, new_darst)
|
||||
elemente.regenerate_all_oeffnungen(doc)
|
||||
# Oberleiste-Topbar muss neuen Wert spiegeln
|
||||
try:
|
||||
b = sc.sticky.get("oberleiste_bridge")
|
||||
if b is not None: b._send_state(force=True)
|
||||
except Exception: pass
|
||||
except Exception as ex:
|
||||
print("[AUSSCHNITTE] darstellung apply:", ex)
|
||||
# Overrides: nur anwenden wenn das Snap "applyOverrides" gesetzt hat.
|
||||
# Sonst bleibt der aktuelle User-Override-State unangetastet.
|
||||
if snap.get("applyOverrides"):
|
||||
@@ -786,6 +811,7 @@ class AusschnittBridge(panel_base.BaseBridge):
|
||||
"overridesEnabled": bool(sn.get("overridesEnabled", False)),
|
||||
"overridesPreset": sn.get("overridesPreset") or "",
|
||||
"layerCombination": sn.get("layerCombination") or "",
|
||||
"darstellung": sn.get("darstellung") or "",
|
||||
},
|
||||
"displayModes": display_modes,
|
||||
"overridesPresets": overrides_presets,
|
||||
@@ -815,6 +841,9 @@ class AusschnittBridge(panel_base.BaseBridge):
|
||||
target["overridesPreset"] = (settings.get("overridesPreset") or "").strip()
|
||||
# Ebenenkombi
|
||||
target["layerCombination"] = (settings.get("layerCombination") or "").strip()
|
||||
# Darstellung (SIA-400 LoD Override fuer diesen Ausschnitt)
|
||||
darst = (settings.get("darstellung") or "").strip()
|
||||
target["darstellung"] = darst if darst in ("einfach", "standard", "detail") else ""
|
||||
outer._store(d, snaps)
|
||||
outer._send_list()
|
||||
print("[AUSSCHNITTE] Settings fuer '{}' aktualisiert".format(target.get("name")))
|
||||
|
||||
+491
-41
@@ -59,6 +59,182 @@ _KEY_OEFF_SIMS_AUS = "dossier_oeff_sims_aus" # Style: "ohne"|"schmal"|"
|
||||
_KEY_OEFF_SIMS_IN = "dossier_oeff_sims_in" # Style: "ohne"|"schmal"|"standard"|"breit"
|
||||
_KEY_OEFF_GLAS = "dossier_oeff_glas" # "1"|"0" — sichtbare Glas-Scheibe
|
||||
_KEY_OEFF_REFERENZ = "dossier_oeff_referenz" # "mid" | "links" | "rechts" — Lage des Klick-Punkts in der Oeffnung
|
||||
_KEY_OEFF_DARSTELLUNG = "dossier_oeff_darstellung" # "einfach" | "standard" | "detail" — SIA-400 LoD
|
||||
_KEY_OEFF_AUSSENSEITE = "dossier_oeff_aussenseite" # "links" | "rechts" — welche Wand-Seite ist aussen
|
||||
_KEY_OEFF_TUER_RAHMEN = "dossier_oeff_tuer_rahmen" # "zarge" | "block" — Tueren-Rahmen-Typ
|
||||
_KEY_OEFF_RAHMEN_OFFSET = "dossier_oeff_rahmen_offset" # float (m): Abstand Rahmen-Innenkante von Wand-Innenseite
|
||||
_KEY_AKTIVE_DARSTELLUNG = "dossier_aktive_darstellung" # doc-level global override: einfach|standard|detail|"" (= per-object)
|
||||
_KEY_OEFF_STYLE_ID = "dossier_oeff_style_id" # per-Object: referenziert einen Style aus dossier_oeff_styles
|
||||
_KEY_OEFF_STYLES = "dossier_oeff_styles" # JSON-Liste aller Fenster/Tueren-Styles
|
||||
_KEY_OEFF_STYLE_ACTIVE = "dossier_oeff_style_active" # zuletzt benutzte Style-ID (pro typ)
|
||||
|
||||
_OEFF_DARSTELLUNGEN = ("einfach", "standard", "detail")
|
||||
|
||||
|
||||
def get_aktive_darstellung(doc):
|
||||
"""Liest die doc-level Darstellungs-Override. Leer → per-object."""
|
||||
if doc is None: return ""
|
||||
try:
|
||||
v = doc.Strings.GetValue(_KEY_AKTIVE_DARSTELLUNG) or ""
|
||||
except Exception:
|
||||
v = ""
|
||||
return v if v in _OEFF_DARSTELLUNGEN else ""
|
||||
|
||||
|
||||
def set_aktive_darstellung(doc, value):
|
||||
"""Setzt die doc-level Darstellungs-Override. Leer → clear."""
|
||||
if doc is None: return
|
||||
try:
|
||||
if value and value in _OEFF_DARSTELLUNGEN:
|
||||
doc.Strings.SetString(_KEY_AKTIVE_DARSTELLUNG, value)
|
||||
else:
|
||||
doc.Strings.Delete(_KEY_AKTIVE_DARSTELLUNG)
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] set_aktive_darstellung:", ex)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fenster/Tueren-Styles (Presets) — analog text_create.list_styles
|
||||
|
||||
_OEFF_STYLE_FIELDS = (
|
||||
"typ", "breite", "hoehe", "brueest",
|
||||
"rahmenB", "rahmenTiefe", "rahmenOffset",
|
||||
"fluegel", "simsAus", "glas",
|
||||
"darstellung", "tuerRahmen",
|
||||
)
|
||||
|
||||
_OEFF_DEFAULT_STYLES = [
|
||||
{"name": "Fenster Standard", "typ": "fenster",
|
||||
"breite": 1.20, "hoehe": 1.40, "brueest": 0.90,
|
||||
"rahmenB": 0.06, "rahmenTiefe": 0.08, "rahmenOffset": 0.05,
|
||||
"fluegel": 2, "simsAus": "standard", "glas": True,
|
||||
"darstellung": "standard"},
|
||||
{"name": "Fenster Gross", "typ": "fenster",
|
||||
"breite": 2.00, "hoehe": 1.80, "brueest": 0.40,
|
||||
"rahmenB": 0.08, "rahmenTiefe": 0.10, "rahmenOffset": 0.05,
|
||||
"fluegel": 3, "simsAus": "breit", "glas": True,
|
||||
"darstellung": "standard"},
|
||||
{"name": "Fenster Bandlage (boden)", "typ": "fenster",
|
||||
"breite": 3.00, "hoehe": 0.60, "brueest": 0.00,
|
||||
"rahmenB": 0.06, "rahmenTiefe": 0.08, "rahmenOffset": 0.05,
|
||||
"fluegel": 3, "simsAus": "schmal", "glas": True,
|
||||
"darstellung": "standard"},
|
||||
{"name": "Tuer Innen", "typ": "tuer",
|
||||
"breite": 0.90, "hoehe": 2.10, "brueest": 0.00,
|
||||
"rahmenB": 0.05, "rahmenTiefe": 0.08, "rahmenOffset": 0.05,
|
||||
"fluegel": 1, "simsAus": "ohne", "glas": False,
|
||||
"darstellung": "standard", "tuerRahmen": "zarge"},
|
||||
{"name": "Tuer Eingang", "typ": "tuer",
|
||||
"breite": 1.00, "hoehe": 2.20, "brueest": 0.00,
|
||||
"rahmenB": 0.08, "rahmenTiefe": 0.10, "rahmenOffset": 0.05,
|
||||
"fluegel": 1, "simsAus": "ohne", "glas": False,
|
||||
"darstellung": "standard", "tuerRahmen": "block"},
|
||||
{"name": "Tuer Verglast", "typ": "tuer",
|
||||
"breite": 0.90, "hoehe": 2.10, "brueest": 0.00,
|
||||
"rahmenB": 0.06, "rahmenTiefe": 0.08, "rahmenOffset": 0.05,
|
||||
"fluegel": 1, "simsAus": "ohne", "glas": True,
|
||||
"darstellung": "standard", "tuerRahmen": "zarge"},
|
||||
]
|
||||
|
||||
|
||||
def _normalize_oeff_style(s):
|
||||
"""Filtert auf erlaubte Felder + setzt sinnvolle Defaults."""
|
||||
out = {}
|
||||
for k in _OEFF_STYLE_FIELDS:
|
||||
if k in s: out[k] = s[k]
|
||||
return out
|
||||
|
||||
|
||||
def list_oeff_styles(doc, typ=None):
|
||||
"""Liste aller Window/Tuer-Styles. typ='fenster'|'tuer' filtert.
|
||||
Seedet Defaults beim ersten Zugriff."""
|
||||
if doc is None: return []
|
||||
import json, uuid
|
||||
try:
|
||||
raw = doc.Strings.GetValue(_KEY_OEFF_STYLES)
|
||||
if not raw:
|
||||
seeded = []
|
||||
for i, s in enumerate(_OEFF_DEFAULT_STYLES):
|
||||
norm = _normalize_oeff_style(s)
|
||||
norm["id"] = "os_default_" + str(i)
|
||||
norm["name"] = s["name"]
|
||||
seeded.append(norm)
|
||||
try: doc.Strings.SetString(_KEY_OEFF_STYLES, json.dumps(seeded))
|
||||
except Exception: pass
|
||||
items = seeded
|
||||
else:
|
||||
parsed = json.loads(raw)
|
||||
items = parsed if isinstance(parsed, list) else []
|
||||
out = []
|
||||
for it in items:
|
||||
if not isinstance(it, dict): continue
|
||||
n = _normalize_oeff_style(it)
|
||||
n["id"] = it.get("id") or ("os_" + uuid.uuid4().hex[:8])
|
||||
n["name"] = it.get("name") or "Stil"
|
||||
out.append(n)
|
||||
if typ in ("fenster", "tuer"):
|
||||
out = [s for s in out if s.get("typ") == typ]
|
||||
return out
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] list_oeff_styles:", ex)
|
||||
return []
|
||||
|
||||
|
||||
def save_oeff_style(doc, name, settings):
|
||||
"""Speichert (oder updated) einen Style unter `name`. Returns ID."""
|
||||
if doc is None or not name: return None
|
||||
import json, uuid
|
||||
items_all = []
|
||||
try:
|
||||
raw = doc.Strings.GetValue(_KEY_OEFF_STYLES)
|
||||
if raw:
|
||||
parsed = json.loads(raw)
|
||||
if isinstance(parsed, list):
|
||||
items_all = [it for it in parsed if isinstance(it, dict)]
|
||||
except Exception: pass
|
||||
sid = None
|
||||
for it in items_all:
|
||||
if it.get("name") == name:
|
||||
sid = it.get("id"); break
|
||||
norm = _normalize_oeff_style(settings or {})
|
||||
norm["id"] = sid or ("os_" + uuid.uuid4().hex[:8])
|
||||
norm["name"] = name
|
||||
if sid:
|
||||
items_all = [norm if it.get("id") == sid else it for it in items_all]
|
||||
else:
|
||||
items_all.append(norm)
|
||||
try: doc.Strings.SetString(_KEY_OEFF_STYLES, json.dumps(items_all))
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] save_oeff_style:", ex)
|
||||
return norm["id"]
|
||||
|
||||
|
||||
def delete_oeff_style(doc, sid):
|
||||
if doc is None or not sid: return
|
||||
import json
|
||||
try:
|
||||
items = list_oeff_styles(doc)
|
||||
items = [it for it in items if it.get("id") != sid]
|
||||
doc.Strings.SetString(_KEY_OEFF_STYLES, json.dumps(items))
|
||||
except Exception: pass
|
||||
|
||||
|
||||
def get_active_oeff_style_id(doc, typ):
|
||||
if doc is None: return None
|
||||
try:
|
||||
raw = doc.Strings.GetValue(_KEY_OEFF_STYLE_ACTIVE + "_" + typ)
|
||||
return raw or None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def set_active_oeff_style_id(doc, typ, sid):
|
||||
if doc is None or typ not in ("fenster", "tuer"): return
|
||||
try:
|
||||
doc.Strings.SetString(_KEY_OEFF_STYLE_ACTIVE + "_" + typ, sid or "")
|
||||
except Exception: pass
|
||||
_OEFF_AUSSENSEITEN = ("links", "rechts")
|
||||
_OEFF_TUER_RAHMEN = ("zarge", "block")
|
||||
|
||||
_OEFF_REFERENZ_OPTIONS = ("mid", "links", "rechts")
|
||||
|
||||
@@ -1705,7 +1881,9 @@ def _attach_meta(obj_attrs, wall_id, type_, geschoss, dicke, uk_over, ok_over,
|
||||
oeff_rahmen_b=None, oeff_rahmen_tiefe=None, oeff_rahmen_pos=None,
|
||||
oeff_fluegel=None,
|
||||
oeff_sims_aus=None, oeff_sims_in=None, oeff_glas=None,
|
||||
oeff_referenz=None,
|
||||
oeff_referenz=None, oeff_darstellung=None,
|
||||
oeff_aussenseite=None, oeff_tuer_rahmen=None,
|
||||
oeff_rahmen_offset=None, oeff_style_id=None,
|
||||
geschoss_end=None, treppe_breite=None,
|
||||
treppe_n=None, treppe_referenz=None,
|
||||
treppe_modus=None, treppe_lauf_d=None, treppe_art=None,
|
||||
@@ -1775,6 +1953,20 @@ def _attach_meta(obj_attrs, wall_id, type_, geschoss, dicke, uk_over, ok_over,
|
||||
obj_attrs.SetUserString(_KEY_OEFF_GLAS, "1" if bool(oeff_glas) else "0")
|
||||
if oeff_referenz is not None and oeff_referenz in _OEFF_REFERENZ_OPTIONS:
|
||||
obj_attrs.SetUserString(_KEY_OEFF_REFERENZ, oeff_referenz)
|
||||
if oeff_darstellung is not None and oeff_darstellung in _OEFF_DARSTELLUNGEN:
|
||||
obj_attrs.SetUserString(_KEY_OEFF_DARSTELLUNG, oeff_darstellung)
|
||||
if oeff_aussenseite is not None and oeff_aussenseite in _OEFF_AUSSENSEITEN:
|
||||
obj_attrs.SetUserString(_KEY_OEFF_AUSSENSEITE, oeff_aussenseite)
|
||||
if oeff_tuer_rahmen is not None and oeff_tuer_rahmen in _OEFF_TUER_RAHMEN:
|
||||
obj_attrs.SetUserString(_KEY_OEFF_TUER_RAHMEN, oeff_tuer_rahmen)
|
||||
if oeff_rahmen_offset is not None:
|
||||
try:
|
||||
v = max(0.0, float(oeff_rahmen_offset))
|
||||
obj_attrs.SetUserString(_KEY_OEFF_RAHMEN_OFFSET, "{:.4f}".format(v))
|
||||
except Exception: pass
|
||||
if oeff_style_id is not None:
|
||||
try: obj_attrs.SetUserString(_KEY_OEFF_STYLE_ID, str(oeff_style_id) or "")
|
||||
except Exception: pass
|
||||
# --- Treppen-Felder ---
|
||||
if geschoss_end is not None:
|
||||
obj_attrs.SetUserString(_KEY_GESCHOSS_END, geschoss_end or "")
|
||||
@@ -1925,6 +2117,19 @@ def _read_meta(obj):
|
||||
ogl = (og_raw == "1") if og_raw in ("0", "1") else is_fenster
|
||||
oref = a.GetUserString(_KEY_OEFF_REFERENZ) or "mid"
|
||||
if oref not in _OEFF_REFERENZ_OPTIONS: oref = "mid"
|
||||
odarst = a.GetUserString(_KEY_OEFF_DARSTELLUNG) or "standard"
|
||||
if odarst not in _OEFF_DARSTELLUNGEN: odarst = "standard"
|
||||
oauss = a.GetUserString(_KEY_OEFF_AUSSENSEITE) or "rechts"
|
||||
if oauss not in _OEFF_AUSSENSEITEN: oauss = "rechts"
|
||||
otrah = a.GetUserString(_KEY_OEFF_TUER_RAHMEN) or "zarge"
|
||||
if otrah not in _OEFF_TUER_RAHMEN: otrah = "zarge"
|
||||
# Rahmen-Offset (m, von Wand-Innenseite). Default 5cm. Wenn Legacy-
|
||||
# Wert (rahmen_pos) gesetzt aber kein offset, benutzt build-Logik
|
||||
# weiterhin den Preset.
|
||||
try: oro = float(a.GetUserString(_KEY_OEFF_RAHMEN_OFFSET) or "0.05")
|
||||
except Exception: oro = 0.05
|
||||
if oro < 0: oro = 0.0
|
||||
ostyle = a.GetUserString(_KEY_OEFF_STYLE_ID) or ""
|
||||
# Treppen-Felder
|
||||
gend = a.GetUserString(_KEY_GESCHOSS_END) or ""
|
||||
try: tb = float(a.GetUserString(_KEY_TREPPE_BREITE) or "1.0")
|
||||
@@ -2041,6 +2246,11 @@ def _read_meta(obj):
|
||||
"oeff_sims_in": osi,
|
||||
"oeff_glas": ogl,
|
||||
"oeff_referenz": oref,
|
||||
"oeff_darstellung": odarst,
|
||||
"oeff_aussenseite": oauss,
|
||||
"oeff_tuer_rahmen": otrah,
|
||||
"oeff_rahmen_offset": oro,
|
||||
"oeff_style_id": ostyle,
|
||||
"geschoss_end": gend,
|
||||
"treppe_breite": tb,
|
||||
"treppe_n": tn,
|
||||
@@ -2281,6 +2491,26 @@ def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base
|
||||
sims_in_style = oeff_meta.get("oeff_sims_in", "ohne")
|
||||
has_glas = bool(oeff_meta.get("oeff_glas", False))
|
||||
is_tuer = (oeff_meta.get("oeff_typ") == "tuer")
|
||||
# Doc-level Darstellungs-Override gewinnt vor per-Object-Setting —
|
||||
# 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
|
||||
# -Plane.ZAxis (Wand "umgedreht"). Wird verwendet um Sims aus/in
|
||||
# konsistent zu platzieren unabhaengig von der Wand-Achsen-Richtung.
|
||||
aussenseite = oeff_meta.get("oeff_aussenseite", "rechts")
|
||||
aus_sign = +1 if aussenseite == "rechts" else -1
|
||||
# Tueren-Rahmen-Typ: 'zarge' (klassisch, sitzt IM Wandquerschnitt) oder
|
||||
# 'block' (Blockrahmen, sitzt UM die Oeffnung und ragt seitlich raus).
|
||||
tuer_rahmen = oeff_meta.get("oeff_tuer_rahmen", "zarge")
|
||||
# Rahmen-Offset (m): Abstand der Rahmen-Innenkante von der Wand-
|
||||
# Innenseite. 0 = bündig innen, wand_dicke-rahmen_t = bündig aussen.
|
||||
try: rahmen_offset = max(0.0, float(oeff_meta.get("oeff_rahmen_offset", 0.05)))
|
||||
except Exception: rahmen_offset = 0.05
|
||||
|
||||
half_b = breite * 0.5
|
||||
half_d = float(wall_dicke) * 0.5
|
||||
@@ -2301,14 +2531,60 @@ def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base
|
||||
if inner_l >= inner_r - 1e-6 or payload_z_lo >= payload_z_hi - 1e-6:
|
||||
return [] # Rahmen-Profil zu dick fuer Oeffnung
|
||||
|
||||
frame_perp_lo, frame_perp_hi = _resolve_rahmen_perp_range(
|
||||
half_d, rahmen_t, rahmen_pos)
|
||||
# Rahmen-Position aus Offset bestimmen. aus_sign=+1 -> Innen ist
|
||||
# -perp-Seite. Inside-Edge = -half_d + offset. Outside-Edge = inside
|
||||
# + rahmen_t. Maximaler Offset = wand_dicke - rahmen_t (sonst ragt
|
||||
# der Rahmen raus). Clamping mit 1mm Inset gegen Z-Fight.
|
||||
rt = max(0.01, float(rahmen_t))
|
||||
rt = min(rt, 2.0 * half_d - 0.002)
|
||||
max_offset = 2.0 * half_d - rt - 0.001
|
||||
eff_offset = min(max(0.0, rahmen_offset), max(0.0, max_offset))
|
||||
if aus_sign > 0:
|
||||
frame_perp_lo = -half_d + eff_offset
|
||||
frame_perp_hi = frame_perp_lo + rt
|
||||
else:
|
||||
frame_perp_hi = +half_d - eff_offset
|
||||
frame_perp_lo = frame_perp_hi - rt
|
||||
|
||||
# --- EINFACH (1:100): nur eine flache Scheibe OHNE Tiefe in der
|
||||
# Rahmen-Mittelebene (= dort wo das Glas im Standard sitzt). Im
|
||||
# 2D-Plan ergibt das eine einzelne Linie quer durch die Oeffnung,
|
||||
# auf dem korrekten Tiefen-Offset.
|
||||
if darstellung == "einfach":
|
||||
try:
|
||||
pane_perp = (frame_perp_lo + frame_perp_hi) * 0.5
|
||||
# Plane.Origin ans Wand-Achsenpunkt verschoben um pane_perp
|
||||
# entlang der Plane-Normale (=tan x Z = (0,0,1) cross tan).
|
||||
normal = rg.Vector3d.CrossProduct(tan, rg.Vector3d(0, 0, 1))
|
||||
try: normal.Unitize()
|
||||
except Exception: pass
|
||||
origin = rg.Point3d(pt.X + normal.X * pane_perp,
|
||||
pt.Y + normal.Y * pane_perp,
|
||||
0)
|
||||
plane = rg.Plane(origin, tan, rg.Vector3d(0, 0, 1))
|
||||
surf = rg.PlaneSurface(plane,
|
||||
rg.Interval(-half_b, +half_b),
|
||||
rg.Interval(z_lo, z_hi))
|
||||
brep = surf.ToBrep()
|
||||
return [brep] if brep is not None else []
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] einfach pane:", ex)
|
||||
return []
|
||||
|
||||
pieces = []
|
||||
|
||||
# --- RAHMEN: outer box - inner box, sauberer single-Brep
|
||||
# --- RAHMEN: outer box - inner box, sauberer single-Brep.
|
||||
# Tueren mit 'block'-Rahmen: outer box ist breiter+hoeher als die
|
||||
# Oeffnung (Blockrahmen sitzt UM den Wanddurchbruch). Sonst klassische
|
||||
# Zarge: outer = Oeffnungsmasse.
|
||||
is_block = (is_tuer and tuer_rahmen == "block")
|
||||
block_overlap = 0.05 # 5cm seitlich + oben
|
||||
out_l = (-half_b - block_overlap) if is_block else -half_b
|
||||
out_r = (+half_b + block_overlap) if is_block else +half_b
|
||||
out_z_hi = (z_hi + block_overlap) if is_block else z_hi
|
||||
out_z_lo = z_lo # unten immer bei z_lo (Boden / Bruestung)
|
||||
try:
|
||||
outer_box = _make_oeff_box(pt, tan, -half_b, +half_b, z_lo, z_hi,
|
||||
outer_box = _make_oeff_box(pt, tan, out_l, out_r, out_z_lo, out_z_hi,
|
||||
frame_perp_lo, frame_perp_hi)
|
||||
# Inner box leicht laenger in perp Richtung damit der Diff sauber
|
||||
# durchschneidet (keine Hauchschicht uebrig).
|
||||
@@ -2348,8 +2624,21 @@ def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base
|
||||
|
||||
if fill_t > 0:
|
||||
fill_mid = (frame_perp_lo + frame_perp_hi) * 0.5
|
||||
fill_lo = fill_mid - fill_t * 0.5
|
||||
fill_hi = fill_mid + fill_t * 0.5
|
||||
# DETAIL (1:20): Doppelverglasung — 2 Scheiben a 6mm, 16mm SZR
|
||||
is_double_glas = (darstellung == "detail" and has_glas and not is_tuer)
|
||||
if is_double_glas:
|
||||
single_t = 0.006
|
||||
szr = 0.016
|
||||
total = single_t * 2 + szr
|
||||
outer_lo = fill_mid - total * 0.5
|
||||
pane_specs = [
|
||||
(outer_lo, outer_lo + single_t),
|
||||
(outer_lo + single_t + szr,
|
||||
outer_lo + single_t + szr + single_t),
|
||||
]
|
||||
else:
|
||||
pane_specs = [(fill_mid - fill_t * 0.5, fill_mid + fill_t * 0.5)]
|
||||
for (fl, fh) in pane_specs:
|
||||
if fluegel > 1:
|
||||
span = inner_r - inner_l
|
||||
for i in range(fluegel):
|
||||
@@ -2359,34 +2648,28 @@ def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base
|
||||
if i < fluegel - 1: fx_hi -= rahmen_b * 0.5
|
||||
fp = _make_oeff_box(pt, tan, fx_lo, fx_hi,
|
||||
payload_z_lo, payload_z_hi,
|
||||
fill_lo, fill_hi)
|
||||
fl, fh)
|
||||
if fp is not None: pieces.append(fp)
|
||||
else:
|
||||
fp = _make_oeff_box(pt, tan, inner_l, inner_r,
|
||||
payload_z_lo, payload_z_hi,
|
||||
fill_lo, fill_hi)
|
||||
fl, fh)
|
||||
if fp is not None: pieces.append(fp)
|
||||
|
||||
# --- SIMS AUSSEN (+Plane.ZAxis-Seite) — Platte unter der Oeffnung
|
||||
# --- SIMS — nur AUSSEN. aus_sign=+1 -> sims auf +perp, =-1 -> auf
|
||||
# -perp. Drinnen nie. Sim ist gleichzeitig der visuelle Indikator
|
||||
# fuer die Aussenseite (Test: aussenseite togglen → Sim wechselt).
|
||||
sa = _OEFF_SIMS_STYLES.get(sims_aus_style)
|
||||
if sa is not None:
|
||||
s_t = sa["dicke"]; s_pr = sa["aus"]; s_oh = sa["ueberhang"]
|
||||
s_lo = z_lo - s_t
|
||||
if aus_sign > 0:
|
||||
p_lo, p_hi = +half_d, +half_d + s_pr
|
||||
else:
|
||||
p_lo, p_hi = -half_d - s_pr, -half_d
|
||||
sb = _make_oeff_box(pt, tan,
|
||||
-half_b - s_oh, +half_b + s_oh,
|
||||
s_lo, z_lo,
|
||||
+half_d, +half_d + s_pr)
|
||||
if sb is not None: pieces.append(sb)
|
||||
|
||||
# --- SIMS INNEN (-Plane.ZAxis-Seite) — Platte unter der Oeffnung
|
||||
si = _OEFF_SIMS_STYLES.get(sims_in_style)
|
||||
if si is not None:
|
||||
s_t = si["dicke"]; s_pr = si["aus"]; s_oh = si["ueberhang"]
|
||||
s_lo = z_lo - s_t
|
||||
sb = _make_oeff_box(pt, tan,
|
||||
-half_b - s_oh, +half_b + s_oh,
|
||||
s_lo, z_lo,
|
||||
-half_d - s_pr, -half_d)
|
||||
s_lo, z_lo, p_lo, p_hi)
|
||||
if sb is not None: pieces.append(sb)
|
||||
|
||||
return pieces
|
||||
@@ -4206,7 +4489,12 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
||||
oeff_sims_aus=op_meta.get("oeff_sims_aus"),
|
||||
oeff_sims_in=op_meta.get("oeff_sims_in"),
|
||||
oeff_glas=op_meta.get("oeff_glas"),
|
||||
oeff_referenz=op_meta.get("oeff_referenz"))
|
||||
oeff_referenz=op_meta.get("oeff_referenz"),
|
||||
oeff_darstellung=op_meta.get("oeff_darstellung"),
|
||||
oeff_aussenseite=op_meta.get("oeff_aussenseite"),
|
||||
oeff_tuer_rahmen=op_meta.get("oeff_tuer_rahmen"),
|
||||
oeff_rahmen_offset=op_meta.get("oeff_rahmen_offset"),
|
||||
oeff_style_id=op_meta.get("oeff_style_id"))
|
||||
doc.Objects.AddBrep(pbrep, op_attrs)
|
||||
|
||||
# Source-Layer migrieren + Volumen-Layer-Index ermitteln
|
||||
@@ -4684,6 +4972,18 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
elif t == "DELETE_WALL": self._delete_wall(p.get("id"))
|
||||
elif t == "DELETE_ELEMENT": self._delete_wall(p.get("id"))
|
||||
elif t == "REGENERATE_ALL": self._regenerate_all()
|
||||
elif t == "SAVE_OEFF_STYLE":
|
||||
try:
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
save_oeff_style(doc, p.get("name") or "Stil", p.get("settings") or {})
|
||||
except Exception as ex: print("[ELEMENTE] save oeff style:", ex)
|
||||
self._send_state()
|
||||
elif t == "DELETE_OEFF_STYLE":
|
||||
try:
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
delete_oeff_style(doc, p.get("id"))
|
||||
except Exception as ex: print("[ELEMENTE] del oeff style:", ex)
|
||||
self._send_state()
|
||||
elif t == "OPEN_ELEMENTE_UEBERSICHT":
|
||||
try:
|
||||
import elemente_uebersicht
|
||||
@@ -4802,6 +5102,11 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
"simsIn": meta.get("oeff_sims_in", "ohne"),
|
||||
"glas": bool(meta.get("oeff_glas", False)),
|
||||
"oeffReferenz": meta.get("oeff_referenz", "mid"),
|
||||
"darstellung": meta.get("oeff_darstellung", "standard"),
|
||||
"aussenseite": meta.get("oeff_aussenseite", "rechts"),
|
||||
"tuerRahmen": meta.get("oeff_tuer_rahmen", "zarge"),
|
||||
"rahmenOffset": meta.get("oeff_rahmen_offset", 0.05),
|
||||
"styleId": meta.get("oeff_style_id", ""),
|
||||
})
|
||||
elif meta["type"] == "treppe_axis":
|
||||
gs = _geschoss_by_id(doc, meta["geschoss"])
|
||||
@@ -4911,6 +5216,7 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
{"name": n, "color": m["color"],
|
||||
"hatch": m.get("hatch", ""), "scale": m.get("scale", 1.0)}
|
||||
for n, m in _MATERIAL_LIBRARY.items()],
|
||||
"oeffStyles": list_oeff_styles(doc),
|
||||
}
|
||||
self.send("STATE", payload)
|
||||
# An Properties-Satellite-Window forwarden falls offen
|
||||
@@ -5691,6 +5997,37 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
try: preview_base_z = float(axis_curve.PointAtStart.Z)
|
||||
except Exception: preview_base_z = 0.0
|
||||
|
||||
# Entwurfs-Defaults pro Typ — VOR der Loop damit der Stil-Picker
|
||||
# sie ueberschreiben kann.
|
||||
is_fenster = (typ == "fenster")
|
||||
rahmen_b_def = _last("oeff_rahmen_b", 0.06)
|
||||
rahmen_t_def = _last("oeff_rahmen_tiefe", 0.08)
|
||||
rahmen_offset_def = _last("oeff_rahmen_offset", 0.05)
|
||||
fluegel_def = _last("{}_fluegel".format(typ), 2 if is_fenster else 1)
|
||||
simsa_def = "standard" if is_fenster else "ohne"
|
||||
glas_def = is_fenster
|
||||
referenz_def = _last("oeff_referenz", "mid")
|
||||
darst_def = "standard"
|
||||
tuer_rahmen_def = "zarge"
|
||||
# Pending-Style-ID aus sticky (von Stil-Picker gesetzt). Falls noch
|
||||
# kein Style gepickt, nutzen wir den zuletzt-aktiven fuer diesen typ
|
||||
# als Default-Source.
|
||||
active_sid = get_active_oeff_style_id(doc, typ)
|
||||
if active_sid:
|
||||
for s in list_oeff_styles(doc, typ):
|
||||
if s.get("id") == active_sid:
|
||||
if "rahmenB" in s: rahmen_b_def = float(s["rahmenB"])
|
||||
if "rahmenTiefe" in s: rahmen_t_def = float(s["rahmenTiefe"])
|
||||
if "rahmenOffset" in s: rahmen_offset_def = float(s["rahmenOffset"])
|
||||
if "fluegel" in s: fluegel_def = int(s["fluegel"])
|
||||
if "simsAus" in s: simsa_def = s["simsAus"]
|
||||
if "glas" in s: glas_def = bool(s["glas"])
|
||||
if "darstellung" in s: darst_def = s["darstellung"]
|
||||
if typ == "tuer" and "tuerRahmen" in s:
|
||||
tuer_rahmen_def = s["tuerRahmen"]
|
||||
break
|
||||
pending_sid = active_sid or ""
|
||||
|
||||
# 2) Punkt auf der Achse — constrained an die Wand-Achse
|
||||
try:
|
||||
while True:
|
||||
@@ -5701,8 +6038,12 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
prompt += ", Br={:.2f}".format(brueest)
|
||||
prompt += "]"
|
||||
gp.SetCommandPrompt(prompt)
|
||||
try: gp.Constrain(axis_curve, False)
|
||||
except Exception: pass
|
||||
# KEINE Constrain mehr — der User soll perpendicular zur
|
||||
# Achse klicken koennen damit wir die Aussenseite ableiten
|
||||
# koennen. Position wird via ClosestPoint auf die Achse
|
||||
# projiziert. Das DynamicDraw-Preview projiziert intern
|
||||
# auch, der Quader sitzt also weiterhin sauber auf der
|
||||
# Achse — nur die Cursor-Position bleibt frei.
|
||||
# Live-Preview: gruener Oeffnungs-Quader mit Glas-Diagonalen,
|
||||
# Brueest-Marker und Mass-Label oberhalb des Sturzes
|
||||
try:
|
||||
@@ -5717,9 +6058,47 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
opt_b = gp.AddOption("Breite")
|
||||
opt_h = gp.AddOption("Hoehe")
|
||||
opt_br = gp.AddOption("Bruestung") if typ == "fenster" else None
|
||||
# Stil-Picker: zeigt verfuegbare Styles als Sub-Optionen
|
||||
opt_st = gp.AddOption("Stil")
|
||||
rp = gp.Get()
|
||||
if rp == GetResult.Option:
|
||||
idx = gp.OptionIndex()
|
||||
if idx == opt_st:
|
||||
styles = list_oeff_styles(doc, typ)
|
||||
if not styles:
|
||||
print("[ELEMENTE] Keine Styles fuer {}".format(typ))
|
||||
else:
|
||||
try:
|
||||
go = ric.GetOption()
|
||||
go.SetCommandPrompt("Stil waehlen")
|
||||
opt_map = []
|
||||
for s in styles:
|
||||
safe = s["name"].replace(" ", "_")
|
||||
opt_map.append((go.AddOption(safe), s))
|
||||
if go.Get() == GetResult.Option:
|
||||
oi = go.OptionIndex()
|
||||
chosen = next((s for (i, s) in opt_map
|
||||
if i == oi), None)
|
||||
if chosen is not None:
|
||||
if "breite" in chosen: breite = float(chosen["breite"])
|
||||
if "hoehe" in chosen: hoehe = float(chosen["hoehe"])
|
||||
if typ == "fenster" and "brueest" in chosen:
|
||||
brueest = float(chosen["brueest"])
|
||||
if "rahmenB" in chosen: rahmen_b_def = float(chosen["rahmenB"])
|
||||
if "rahmenTiefe" in chosen: rahmen_t_def = float(chosen["rahmenTiefe"])
|
||||
if "rahmenOffset" in chosen: rahmen_offset_def = float(chosen["rahmenOffset"])
|
||||
if "fluegel" in chosen: fluegel_def = int(chosen["fluegel"])
|
||||
if "simsAus" in chosen: simsa_def = chosen["simsAus"]
|
||||
if "glas" in chosen: glas_def = bool(chosen["glas"])
|
||||
if "darstellung" in chosen: darst_def = chosen["darstellung"]
|
||||
if typ == "tuer" and "tuerRahmen" in chosen:
|
||||
tuer_rahmen_def = chosen["tuerRahmen"]
|
||||
pending_sid = chosen["id"]
|
||||
set_active_oeff_style_id(doc, typ, chosen["id"])
|
||||
print("[ELEMENTE] Stil '{}' geladen".format(chosen["name"]))
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] Stil-Picker:", ex)
|
||||
continue
|
||||
if idx == opt_b:
|
||||
gn = ric.GetNumber()
|
||||
gn.SetCommandPrompt("Breite")
|
||||
@@ -5753,6 +6132,22 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] ClosestPoint:", ex); return
|
||||
|
||||
# Aussenseite aus Click-Richtung ableiten: Vektor on_axis→click_pt
|
||||
# mit der Perp-Richtung der Achse vergleichen. Vorzeichen entscheidet
|
||||
# ob aussen=+perp (=rechts) oder aussen=-perp (=links). Bei Klick
|
||||
# direkt auf der Achse: Default 'rechts'.
|
||||
detected_aussen = "rechts"
|
||||
try:
|
||||
tan_at = axis_curve.TangentAt(t)
|
||||
perp = rg.Vector3d.CrossProduct(tan_at, rg.Vector3d(0, 0, 1))
|
||||
dx = click_pt.X - on_axis.X
|
||||
dy = click_pt.Y - on_axis.Y
|
||||
side = perp.X * dx + perp.Y * dy
|
||||
if side < -1e-6: detected_aussen = "links"
|
||||
elif side > 1e-6: detected_aussen = "rechts"
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] aussenseite detect:", ex)
|
||||
|
||||
# Point-Objekt mit Metadaten anlegen
|
||||
prefix = "fenster_" if typ == "fenster" else "tuer_"
|
||||
oeff_id = prefix + uuid.uuid4().hex[:10]
|
||||
@@ -5761,17 +6156,6 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
geschoss_name = g.get("name", "EG") if g else "EG"
|
||||
layer = _ensure_layer(doc, _layer_path_axis(doc, geschoss_name))
|
||||
|
||||
# Entwurfs-Defaults pro Typ
|
||||
is_fenster = (typ == "fenster")
|
||||
rahmen_b_def = _last("oeff_rahmen_b", 0.06)
|
||||
rahmen_t_def = _last("oeff_rahmen_tiefe", 0.08)
|
||||
rahmen_p_def = _last("oeff_rahmen_pos", "mid")
|
||||
fluegel_def = _last("{}_fluegel".format(typ), 2 if is_fenster else 1)
|
||||
simsa_def = "standard" if is_fenster else "ohne"
|
||||
simsi_def = "standard" if is_fenster else "ohne"
|
||||
glas_def = is_fenster
|
||||
referenz_def = _last("oeff_referenz", "mid")
|
||||
|
||||
attrs = Rhino.DocObjects.ObjectAttributes()
|
||||
attrs.LayerIndex = layer
|
||||
_attach_meta(attrs, oeff_id, "oeffnung_point", geschoss,
|
||||
@@ -5781,12 +6165,16 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
oeff_brueest=brueest,
|
||||
oeff_rahmen_b=rahmen_b_def,
|
||||
oeff_rahmen_tiefe=rahmen_t_def,
|
||||
oeff_rahmen_pos=rahmen_p_def,
|
||||
oeff_rahmen_offset=rahmen_offset_def,
|
||||
oeff_fluegel=fluegel_def,
|
||||
oeff_sims_aus=simsa_def,
|
||||
oeff_sims_in=simsi_def,
|
||||
oeff_sims_in="ohne",
|
||||
oeff_glas=glas_def,
|
||||
oeff_referenz=referenz_def)
|
||||
oeff_referenz=referenz_def,
|
||||
oeff_aussenseite=detected_aussen,
|
||||
oeff_darstellung=darst_def,
|
||||
oeff_tuer_rahmen=tuer_rahmen_def,
|
||||
oeff_style_id=pending_sid)
|
||||
# Oeffnungs-Punkt auf UK+Brueestung-Hoehe platzieren (= visuell auf
|
||||
# Unterkante Oeffnung). Constraint vergleicht spaeter pt.Z mit
|
||||
# UK+brueest — wenn der Punkt am axis.Z=0 saesse, wuerde der erste
|
||||
@@ -8210,6 +8598,41 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
glas = bool(p.get("glas", old_meta.get("oeff_glas", otyp == "fenster")))
|
||||
oref = p.get("oeffReferenz", old_meta.get("oeff_referenz", "mid"))
|
||||
if oref not in _OEFF_REFERENZ_OPTIONS: oref = "mid"
|
||||
odarst = p.get("darstellung", old_meta.get("oeff_darstellung", "standard"))
|
||||
if odarst not in _OEFF_DARSTELLUNGEN: odarst = "standard"
|
||||
oauss = p.get("aussenseite", old_meta.get("oeff_aussenseite", "rechts"))
|
||||
if oauss not in _OEFF_AUSSENSEITEN: oauss = "rechts"
|
||||
otrah = p.get("tuerRahmen", old_meta.get("oeff_tuer_rahmen", "zarge"))
|
||||
if otrah not in _OEFF_TUER_RAHMEN: otrah = "zarge"
|
||||
try: oro = float(p.get("rahmenOffset",
|
||||
old_meta.get("oeff_rahmen_offset", 0.05)))
|
||||
except Exception: oro = 0.05
|
||||
if oro < 0: oro = 0.0
|
||||
# Style-Apply: wenn ein styleId im Patch ist, alle Felder
|
||||
# aus dem Style ueberschreiben (User-Patch in derselben
|
||||
# Operation gewinnt aber ueber Style — sonst koennte ein
|
||||
# Stil das eben gemachte Field-Edit wegmaecken).
|
||||
o_style_id = p.get("styleId",
|
||||
old_meta.get("oeff_style_id", "")) or ""
|
||||
if p.get("styleId") and p.get("styleId") != old_meta.get("oeff_style_id"):
|
||||
# Style wurde NEU gewaehlt → seine Werte applizieren
|
||||
styles = list_oeff_styles(doc)
|
||||
stl = next((s for s in styles if s.get("id") == p["styleId"]), None)
|
||||
if stl is not None and stl.get("typ") == otyp:
|
||||
if "breite" in stl: breite = float(stl["breite"])
|
||||
if "hoehe" in stl: hoehe = float(stl["hoehe"])
|
||||
if otyp == "fenster" and "brueest" in stl:
|
||||
brueest = float(stl["brueest"])
|
||||
if "rahmenB" in stl: rahmen_b = float(stl["rahmenB"])
|
||||
if "rahmenTiefe" in stl: rahmen_t = float(stl["rahmenTiefe"])
|
||||
if "rahmenOffset" in stl: oro = float(stl["rahmenOffset"])
|
||||
if "fluegel" in stl: fluegel = int(stl["fluegel"])
|
||||
if "simsAus" in stl: simsa = stl["simsAus"]
|
||||
if "glas" in stl: glas = bool(stl["glas"])
|
||||
if "darstellung" in stl: odarst = stl["darstellung"]
|
||||
if otyp == "tuer" and "tuerRahmen" in stl:
|
||||
otrah = stl["tuerRahmen"]
|
||||
set_active_oeff_style_id(doc, otyp, p["styleId"])
|
||||
attrs = axis_obj.Attributes
|
||||
_attach_meta(attrs, wall_id, "oeffnung_point",
|
||||
old_meta["geschoss"], old_meta["dicke"],
|
||||
@@ -8224,7 +8647,12 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
oeff_fluegel=fluegel,
|
||||
oeff_sims_aus=simsa, oeff_sims_in=simsi,
|
||||
oeff_glas=glas,
|
||||
oeff_referenz=oref)
|
||||
oeff_referenz=oref,
|
||||
oeff_darstellung=odarst,
|
||||
oeff_aussenseite=oauss,
|
||||
oeff_tuer_rahmen=otrah,
|
||||
oeff_rahmen_offset=oro,
|
||||
oeff_style_id=o_style_id)
|
||||
axis_obj.Attributes = attrs
|
||||
axis_obj.CommitChanges()
|
||||
parent_id = old_meta.get("oeff_parent", "")
|
||||
@@ -8345,6 +8773,28 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
self._send_state()
|
||||
|
||||
|
||||
def regenerate_all_oeffnungen(doc):
|
||||
"""Modul-API: regen aller Oeffnungen + ihrer Parent-Waende. Wird vom
|
||||
Ausschnitt-Restore / Oberleiste-Darstellungs-Switch gerufen damit
|
||||
der doc-level Darstellungs-Override sofort wirkt."""
|
||||
if doc is None: return 0
|
||||
seen_walls = set()
|
||||
n = 0
|
||||
for obj in list(doc.Objects):
|
||||
meta = _read_meta(obj)
|
||||
if meta is None: continue
|
||||
if meta.get("type") != "oeffnung_point": continue
|
||||
parent = meta.get("oeff_parent") or ""
|
||||
if parent and parent not in seen_walls:
|
||||
seen_walls.add(parent)
|
||||
_regenerate_element(doc, parent)
|
||||
n += 1
|
||||
try: doc.Views.Redraw()
|
||||
except Exception: pass
|
||||
print("[ELEMENTE] regen all oeffnungen via {} Waende".format(n))
|
||||
return n
|
||||
|
||||
|
||||
# --- Event-Listener ---------------------------------------------------------
|
||||
|
||||
# Re-Entry-Guard: wenn _regenerate_volume die Brep ersetzt, feuert das
|
||||
|
||||
@@ -981,6 +981,18 @@ class OberleisteBridge(panel_base.BaseBridge):
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] open masse:", ex)
|
||||
|
||||
# --- Darstellung (SIA-400 LoD globaler Override) -----------------
|
||||
elif t == "SET_DARSTELLUNG":
|
||||
try:
|
||||
import elemente
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
new_v = p.get("darstellung") or ""
|
||||
elemente.set_aktive_darstellung(doc, new_v)
|
||||
elemente.regenerate_all_oeffnungen(doc)
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] set darstellung:", ex)
|
||||
self._send_state(force=True)
|
||||
|
||||
# --- Display-Mode -----------------------------------------------
|
||||
elif t == "SET_DISPLAY_MODE":
|
||||
n = p.get("name")
|
||||
@@ -1226,6 +1238,12 @@ class OberleisteBridge(panel_base.BaseBridge):
|
||||
info["textFonts"] = []
|
||||
info["textStyles"] = []
|
||||
info["textStyleActiveId"] = None
|
||||
# Aktive Darstellung (SIA-400 LoD globaler Override)
|
||||
try:
|
||||
import elemente
|
||||
info["aktiveDarstellung"] = elemente.get_aktive_darstellung(doc) or ""
|
||||
except Exception:
|
||||
info["aktiveDarstellung"] = ""
|
||||
# Norden-Rotation fuer N/O/S/W-Buttons
|
||||
try:
|
||||
import kamera
|
||||
|
||||
@@ -98,6 +98,8 @@ def _broadcast_state(doc=None, hatch_patterns=None):
|
||||
"projectZeroMum": zero_mum,
|
||||
"hatchPatterns": hatch_patterns if hatch_patterns is not None
|
||||
else _hatch_pattern_names(doc),
|
||||
"layerCombinations": list_layer_preset_names(doc),
|
||||
"layerCombinationActive": get_active_comb_name(doc),
|
||||
}
|
||||
except Exception as ex:
|
||||
print("[EBENEN] broadcast prepare:", ex)
|
||||
@@ -470,6 +472,31 @@ class EbenenBridge(panel_base.BaseBridge):
|
||||
p.get("hatchPatterns") or [])
|
||||
elif t == "OPEN_GESCHOSS_DIALOG":
|
||||
self._open_geschoss_dialog(p.get("zeichnungsebenen") or [])
|
||||
elif t == "PICK_LAYER_COMBINATION":
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
name = (p.get("name") or "").strip()
|
||||
if name:
|
||||
apply_layer_preset_by_name(doc, name)
|
||||
else:
|
||||
set_active_comb_name(doc, None)
|
||||
_broadcast_state(doc)
|
||||
_notify_oberleiste_combs()
|
||||
elif t == "SAVE_LAYER_COMBINATION":
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
name = (p.get("name") or "").strip()
|
||||
if name:
|
||||
save_current_as_layer_preset(doc, name)
|
||||
_broadcast_state(doc)
|
||||
_notify_oberleiste_combs()
|
||||
elif t == "DELETE_LAYER_COMBINATION":
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
delete_layer_preset(doc, p.get("name") or "")
|
||||
_broadcast_state(doc)
|
||||
_notify_oberleiste_combs()
|
||||
elif t == "OPEN_LAYER_COMBINATIONS_DIALOG":
|
||||
try: open_layer_combinations_window()
|
||||
except Exception as ex:
|
||||
print("[EBENEN] open layer-combinations:", ex)
|
||||
|
||||
# ---- Helpers ----
|
||||
|
||||
|
||||
+8
-1
@@ -32,11 +32,16 @@ export default function App() {
|
||||
const [appliedE, setAppliedE] = useState(INITIAL_EBENEN)
|
||||
const [eMode, setEMode] = useState('all')
|
||||
const [hatchPatterns, setHatchPatterns] = useState(['Solid', 'Hatch1', 'Hatch2', 'Hatch3', 'Plus', 'Squares', 'Grid', 'Grid60'])
|
||||
const [layerCombinations, setLayerCombinations] = useState([])
|
||||
const [activeKombi, setActiveKombi] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
onMessage('STATE_SYNC', ({ ebenen: e, hatchPatterns: hp }) => {
|
||||
onMessage('STATE_SYNC', ({ ebenen: e, hatchPatterns: hp,
|
||||
layerCombinations: lc, layerCombinationActive: ac }) => {
|
||||
if (e) { setEbenen(e); setAppliedE(e) }
|
||||
if (Array.isArray(hp) && hp.length > 0) setHatchPatterns(hp)
|
||||
if (Array.isArray(lc)) setLayerCombinations(lc)
|
||||
if (ac !== undefined) setActiveKombi(ac)
|
||||
})
|
||||
onMessage('FIRST_RUN', ({ defaultEbenen } = {}) => {
|
||||
// Wenn der Dossier-Launcher ein eigenes Schema definiert hat, nutzen wir
|
||||
@@ -132,6 +137,8 @@ export default function App() {
|
||||
mode={eMode}
|
||||
onModeChange={setEMode}
|
||||
hatchPatterns={hatchPatterns}
|
||||
layerCombinations={layerCombinations}
|
||||
activeKombi={activeKombi}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -65,6 +65,7 @@ export default function AusschnittSettingsApp() {
|
||||
overridesEnabled: !!snap.overridesEnabled,
|
||||
overridesPreset: snap.overridesPreset || '',
|
||||
layerCombination: snap.layerCombination || '',
|
||||
darstellung: snap.darstellung || '',
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -88,6 +89,20 @@ export default function AusschnittSettingsApp() {
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="DARSTELLUNG"
|
||||
hint="SIA-400 Detaillierungsgrad — leer = per-Element-Setting respektieren">
|
||||
<select
|
||||
value={snap.darstellung || ''}
|
||||
onChange={(ev) => set({ darstellung: ev.target.value })}
|
||||
style={{ flex: 1, fontSize: 11, minWidth: 0 }}
|
||||
>
|
||||
<option value="">— per Element —</option>
|
||||
<option value="einfach">Einfach (1:100)</option>
|
||||
<option value="standard">Standard (1:50)</option>
|
||||
<option value="detail">Detail (1:20)</option>
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
<Field label="BILDSCHIRMMODUS"
|
||||
hint="Display-Mode des Viewports beim Wiederherstellen">
|
||||
<select
|
||||
|
||||
+118
-44
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import Icon from './components/Icon'
|
||||
import { BarToggle, BarButton } from './components/BarControls'
|
||||
import { BarToggle, BarButton, BarCombo } from './components/BarControls'
|
||||
import {
|
||||
onMessage, notifyReady,
|
||||
createWall, createDecke, createDach,
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
createStuetze, createTraeger, createRaum,
|
||||
openSwisstopo, openSwisstopoDialog, openOsmDialog,
|
||||
updateElement, deleteElement, openElementeUebersicht, openElementeProperties,
|
||||
saveOeffStyle, deleteOeffStyle,
|
||||
} from './lib/rhinoBridge'
|
||||
|
||||
const labelXs = {
|
||||
@@ -494,7 +495,7 @@ function NeuesElementSection({ noGeschoss, activeName, elementsCount }) {
|
||||
|
||||
// PropertiesView: gemeinsame Komponente, rendert die passende Property-
|
||||
// Form je nach Element-Typ. Wiederverwendbar in Inline + Satellite-Window.
|
||||
export function PropertiesView({ selected, geschosse, materials, hatchPatterns }) {
|
||||
export function PropertiesView({ selected, geschosse, materials, hatchPatterns, oeffStyles }) {
|
||||
if (!selected) return null
|
||||
const upd = (p) => updateElement(selected.id, p)
|
||||
const del = (label) => () => { if (window.confirm(`${label} löschen?`)) deleteElement(selected.id) }
|
||||
@@ -521,6 +522,7 @@ export function PropertiesView({ selected, geschosse, materials, hatchPatterns }
|
||||
return <AussparungProperties aussp={selected} onDelete={del('Aussparung')} />
|
||||
// fenster/tuer
|
||||
return <OeffnungProperties oeff={selected} onUpdate={upd}
|
||||
oeffStyles={oeffStyles || []}
|
||||
onDelete={del(selected.kind === 'fenster' ? 'Fenster' : 'Tür')} />
|
||||
}
|
||||
|
||||
@@ -566,7 +568,8 @@ export default function ElementeApp() {
|
||||
selected={selected}
|
||||
geschosse={geschosse}
|
||||
materials={state.materials || []}
|
||||
hatchPatterns={state.hatchPatterns} />
|
||||
hatchPatterns={state.hatchPatterns}
|
||||
oeffStyles={state.oeffStyles || []} />
|
||||
</div>
|
||||
)}
|
||||
<NeuesElementSection
|
||||
@@ -615,7 +618,6 @@ function TragwerkProperties({ el, onUpdate, onDelete }) {
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -717,7 +719,6 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns })
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -858,7 +859,6 @@ function AussparungProperties({ aussp, onDelete }) {
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -910,7 +910,6 @@ function WallProperties({ wall, geschosse, materials, onUpdate, onDelete }) {
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -1123,7 +1122,6 @@ function DeckenProperties({ decke, geschosse, onUpdate, onDelete }) {
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -1199,7 +1197,6 @@ function DachProperties({ dach, geschosse, onUpdate, onDelete }) {
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -1517,7 +1514,6 @@ function TreppeProperties({ treppe, geschosse, onUpdate, onDelete }) {
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -1684,7 +1680,7 @@ function TreppeProperties({ treppe, geschosse, onUpdate, onDelete }) {
|
||||
)
|
||||
}
|
||||
|
||||
function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
function OeffnungProperties({ oeff, onUpdate, onDelete, oeffStyles = [] }) {
|
||||
const isFenster = oeff.kind === 'fenster'
|
||||
const label = isFenster ? 'Fenster' : 'Tür'
|
||||
const icon = isFenster ? 'window' : 'sensor_door'
|
||||
@@ -1709,16 +1705,13 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
}
|
||||
|
||||
const fluegel = oeff.fluegel ?? 1
|
||||
const rahmenPos = oeff.rahmenPos ?? 'mid'
|
||||
const simsAus = oeff.simsAus ?? 'ohne'
|
||||
const simsIn = oeff.simsIn ?? 'ohne'
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -1731,6 +1724,97 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Stil-Picker — Liste passender Styles (gefiltert nach typ) */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Stil — gespeicherte Properties-Sets fuer Fenster/Tueren">
|
||||
Stil
|
||||
</span>
|
||||
<div style={{ flex: 1, display: 'flex' }}>
|
||||
<BarCombo
|
||||
value={oeff.styleId || ''}
|
||||
onChange={(v) => {
|
||||
if (v === '__save__') {
|
||||
const sugg = (oeffStyles.find(s => s.id === oeff.styleId) || {}).name || ''
|
||||
const n = (window.prompt('Name fuer neuen Stil:', sugg || (isFenster ? 'Mein Fenster' : 'Meine Tuer')) || '').trim()
|
||||
if (!n) return
|
||||
saveOeffStyle(n, {
|
||||
typ: isFenster ? 'fenster' : 'tuer',
|
||||
breite: oeff.breite, hoehe: oeff.hoehe, brueest: oeff.brueest,
|
||||
rahmenB: oeff.rahmenB, rahmenTiefe: oeff.rahmenTiefe,
|
||||
rahmenOffset: oeff.rahmenOffset,
|
||||
fluegel: oeff.fluegel, simsAus: oeff.simsAus,
|
||||
glas: oeff.glas, darstellung: oeff.darstellung,
|
||||
tuerRahmen: oeff.tuerRahmen,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (v === '__delete__') {
|
||||
if (oeff.styleId && window.confirm('Aktiven Stil loeschen?'))
|
||||
deleteOeffStyle(oeff.styleId)
|
||||
return
|
||||
}
|
||||
onUpdate({ styleId: v })
|
||||
}}
|
||||
title="Stil anwenden — alle Properties werden gesetzt">
|
||||
<option value="">— Eigene Werte —</option>
|
||||
{oeffStyles
|
||||
.filter(s => s.typ === (isFenster ? 'fenster' : 'tuer'))
|
||||
.map(s => <option key={s.id} value={s.id}>{s.name}</option>)}
|
||||
<option disabled>──────────</option>
|
||||
<option value="__save__">+ Aktuelle als Stil speichern…</option>
|
||||
{oeff.styleId && <option value="__delete__">🗑 Aktiven Stil loeschen</option>}
|
||||
</BarCombo>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="SIA-400 Detaillierungsgrad. Einfach=1:100, Standard=1:50, Detail=1:20">
|
||||
Darstell.
|
||||
</span>
|
||||
<div style={{ flex: 1, display: 'flex' }}>
|
||||
<BarCombo
|
||||
value={oeff.darstellung || 'standard'}
|
||||
onChange={(v) => onUpdate({ darstellung: v })}
|
||||
title="Detaillierungsgrad — beeinflusst die generierte Geometrie">
|
||||
<option value="einfach">Einfach (1:100)</option>
|
||||
<option value="standard">Standard (1:50)</option>
|
||||
<option value="detail">Detail (1:20)</option>
|
||||
</BarCombo>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Orientierung — welche Seite der Wand ist aussen. Beim Setzen aus der Click-Richtung erkannt, hier umkehren falls falsch.">
|
||||
Orient.
|
||||
</span>
|
||||
<div style={{ flex: 1, display: 'flex' }}>
|
||||
<BarToggle label="Umkehren"
|
||||
onClick={() => onUpdate({ aussenseite:
|
||||
(oeff.aussenseite || 'rechts') === 'rechts' ? 'links' : 'rechts' })}
|
||||
title="Aussenseite auf die andere Wandseite umkehren" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!isFenster && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Tueren-Rahmen-Typ. Zarge sitzt in der Oeffnung, Blockrahmen sitzt aussen herum">
|
||||
Rahmen
|
||||
</span>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 3 }}>
|
||||
<BarToggle label="Zarge"
|
||||
active={(oeff.tuerRahmen || 'zarge') === 'zarge'}
|
||||
onClick={() => onUpdate({ tuerRahmen: 'zarge' })} />
|
||||
<BarToggle label="Block"
|
||||
active={(oeff.tuerRahmen || 'zarge') === 'block'}
|
||||
onClick={() => onUpdate({ tuerRahmen: 'block' })} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Breite</span>
|
||||
<input type="text" value={breite}
|
||||
@@ -1811,21 +1895,21 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)' }}>m</span>
|
||||
</div>
|
||||
|
||||
{/* Rahmen-Lage im Wandquerschnitt */}
|
||||
{/* Rahmen-Lage: Abstand der Rahmen-Innenkante von der Wand-Innenseite */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Lage des Rahmens im Wandquerschnitt">
|
||||
title="Abstand der Rahmen-Innenkante von der Wand-Innenseite (Aussenseite-Flag oben bestimmt welche Seite innen ist)">
|
||||
Lage
|
||||
</span>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 3 }}>
|
||||
{RAHMEN_POS_OPTIONS.map(o => (
|
||||
<BarToggle key={o.code}
|
||||
label={o.label}
|
||||
active={rahmenPos === o.code}
|
||||
onClick={() => onUpdate({ rahmenPos: o.code })}
|
||||
title={o.hint} />
|
||||
))}
|
||||
</div>
|
||||
<input type="text"
|
||||
value={String(oeff.rahmenOffset ?? 0.05)}
|
||||
onChange={(e) => {
|
||||
const v = parseFloat(e.target.value.replace(',', '.'))
|
||||
if (!Number.isNaN(v) && v >= 0) onUpdate({ rahmenOffset: v })
|
||||
}}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
|
||||
style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)' }}>m v. innen</span>
|
||||
</div>
|
||||
|
||||
{/* Fluegel-Anzahl — nur fuer Fenster (Tueren haben ein einzelnes Tuerblatt) */}
|
||||
@@ -1846,34 +1930,24 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sims-Stile (aussen / innen) — nur fuer Fenster */}
|
||||
{/* Sims — nur aussen. Innen gibt's bewusst nicht. Dient zugleich
|
||||
als visueller Indikator fuer die Aussenseite-Einstellung. */}
|
||||
{isFenster && (
|
||||
<>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Aussensims — Platte unter Öffnung, ragt aussen heraus">
|
||||
Sims a.
|
||||
Sims
|
||||
</span>
|
||||
<select value={simsAus}
|
||||
onChange={(e) => onUpdate({ simsAus: e.target.value })}
|
||||
style={{ flex: 1, fontSize: 11 }}>
|
||||
<div style={{ flex: 1, display: 'flex' }}>
|
||||
<BarCombo
|
||||
value={simsAus}
|
||||
onChange={(v) => onUpdate({ simsAus: v })}
|
||||
title="Sims-Stil">
|
||||
{SIMS_OPTIONS.map(o =>
|
||||
<option key={o.code} value={o.code}>{o.label}</option>)}
|
||||
</select>
|
||||
</BarCombo>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Innensims — Platte unter Öffnung, ragt innen heraus">
|
||||
Sims i.
|
||||
</span>
|
||||
<select value={simsIn}
|
||||
onChange={(e) => onUpdate({ simsIn: e.target.value })}
|
||||
style={{ flex: 1, fontSize: 11 }}>
|
||||
{SIMS_OPTIONS.map(o =>
|
||||
<option key={o.code} value={o.code}>{o.label}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Glas-Toggle: bei Tueren ersetzt Glas das Tuerblatt (verglaste Tuer) */}
|
||||
|
||||
@@ -33,6 +33,7 @@ export default function ElementePropertiesApp() {
|
||||
geschosse={state.geschosse || []}
|
||||
materials={state.materials || []}
|
||||
hatchPatterns={state.hatchPatterns}
|
||||
oeffStyles={state.oeffStyles || []}
|
||||
/>
|
||||
) : (
|
||||
<div style={{
|
||||
|
||||
+10
-38
@@ -14,6 +14,7 @@ import {
|
||||
setMasseActive, openMasseSettings,
|
||||
openAbout, createText, setTextSettings,
|
||||
applyTextStyle, saveTextStyle, deleteTextStyle,
|
||||
setDarstellung,
|
||||
} from './lib/rhinoBridge'
|
||||
|
||||
const PRESETS = [
|
||||
@@ -399,47 +400,18 @@ export default function OberleisteApp() {
|
||||
<option key={dm.id} value={dm.name}>{dm.name}</option>
|
||||
))}
|
||||
</BarCombo>
|
||||
{/* Reihe 1, Spalte 2: Ebenenkombination */}
|
||||
{/* Reihe 1, Spalte 2: Modelldarstellung (SIA-400 LoD) */}
|
||||
<BarCombo
|
||||
icon="layers"
|
||||
value={state.layerCombinationActive || '__none__'}
|
||||
onChange={(v) => {
|
||||
if (v === '__configure__') { openLayerCombinationsDialog(); return }
|
||||
if (v === '__save__') {
|
||||
const suggested = state.layerCombinationActive
|
||||
|| `Kombi ${(state.layerCombinations || []).length + 1}`
|
||||
const name = (window.prompt('Name für Ebenenkombination:', suggested) || '').trim()
|
||||
if (!name) return
|
||||
if ((state.layerCombinations || []).includes(name) &&
|
||||
!window.confirm(`"${name}" überschreiben?`)) return
|
||||
saveLayerCombination(name)
|
||||
return
|
||||
}
|
||||
if (v === '__delete__') {
|
||||
if (state.layerCombinationActive &&
|
||||
window.confirm(`Kombination "${state.layerCombinationActive}" löschen?`))
|
||||
deleteLayerCombination(state.layerCombinationActive)
|
||||
return
|
||||
}
|
||||
pickLayerCombination(v === '__none__' ? null : v)
|
||||
}}
|
||||
title={state.layerCombinationActive
|
||||
? `Aktive Kombi: ${state.layerCombinationActive}`
|
||||
: 'Keine Kombination — manuelle Sichtbarkeit'}
|
||||
icon="tune"
|
||||
value={state.aktiveDarstellung || ''}
|
||||
onChange={(v) => setDarstellung(v)}
|
||||
title="Darstellungs-Override fuer Fenster/Tueren (SIA-400 LoD)"
|
||||
width={PRESET_W}
|
||||
onGear={openLayerCombinationsDialog}
|
||||
gearTitle="Ebenenkombinationen bearbeiten"
|
||||
>
|
||||
<option value="__none__">— Eigene —</option>
|
||||
{(state.layerCombinations || []).map(name => (
|
||||
<option key={name} value={name}>{name}</option>
|
||||
))}
|
||||
<option disabled>──────────</option>
|
||||
<option value="__save__">+ Aktuelle speichern…</option>
|
||||
{state.layerCombinationActive && (
|
||||
<option value="__delete__">🗑 Aktuelle löschen</option>
|
||||
)}
|
||||
<option value="__configure__">Bearbeiten…</option>
|
||||
<option value="">— per Element —</option>
|
||||
<option value="einfach">Einfach (1:100)</option>
|
||||
<option value="standard">Standard (1:50)</option>
|
||||
<option value="detail">Detail (1:20)</option>
|
||||
</BarCombo>
|
||||
{/* Reihe 2, Spalte 1: Overrides (Toggle als Icon links) */}
|
||||
<BarCombo
|
||||
|
||||
@@ -14,12 +14,18 @@ export const BAR_H = 22
|
||||
export function BarCombo({
|
||||
icon, iconActive, iconClickable, onIconClick, iconTitle,
|
||||
value, onChange, width, title, children, disabled,
|
||||
onGear, gearTitle, valueAccent,
|
||||
onGear, gearTitle, gearIcon, valueAccent,
|
||||
onSecond, secondIcon, secondTitle,
|
||||
stretch,
|
||||
}) {
|
||||
return (
|
||||
<div style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 5,
|
||||
opacity: disabled ? 0.5 : 1, flexShrink: 0,
|
||||
display: stretch ? 'flex' : 'inline-flex',
|
||||
alignItems: 'center', gap: 5,
|
||||
opacity: disabled ? 0.5 : 1,
|
||||
flex: stretch ? 1 : 'none',
|
||||
flexShrink: 0,
|
||||
minWidth: 0,
|
||||
}}>
|
||||
{icon && (iconClickable ? (
|
||||
<button onClick={onIconClick} title={iconTitle}
|
||||
@@ -51,8 +57,12 @@ export function BarCombo({
|
||||
e.currentTarget.style.background = 'var(--bg-input)'
|
||||
}}
|
||||
style={{
|
||||
display: 'inline-flex', alignItems: 'stretch',
|
||||
height: BAR_H + 2, width, boxSizing: 'border-box',
|
||||
display: stretch ? 'flex' : 'inline-flex', alignItems: 'stretch',
|
||||
height: BAR_H + 2,
|
||||
width: stretch ? '100%' : width,
|
||||
flex: stretch ? 1 : 'none',
|
||||
minWidth: 0,
|
||||
boxSizing: 'border-box',
|
||||
background: 'var(--bg-input)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 999,
|
||||
@@ -74,20 +84,36 @@ export function BarCombo({
|
||||
appearance: 'none', WebkitAppearance: 'none',
|
||||
backgroundImage: 'var(--select-arrow)',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: onGear ? 'right 1px center' : 'right 10px center',
|
||||
backgroundPosition: onGear ? 'right 6px center' : 'right 10px center',
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
letterSpacing: 0,
|
||||
}}
|
||||
>{children}</select>
|
||||
{/* Trailing-Slots in DOM-Reihenfolge (von Select-Caret nach
|
||||
aussen rechts): zuerst onGear (Settings), dann onSecond (Add).
|
||||
Konvention: Settings sitzt immer DIREKT nach dem Caret,
|
||||
"+" sitzt immer GANZ AUSSEN rechts. */}
|
||||
{onGear && (
|
||||
<button onClick={onGear} title={gearTitle}
|
||||
style={{
|
||||
background: 'transparent', border: 'none',
|
||||
padding: '0 8px', cursor: 'pointer',
|
||||
padding: '0 4px', marginLeft: 3, cursor: 'pointer',
|
||||
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<Icon name="settings" size={12}
|
||||
<Icon name={gearIcon || 'settings'} size={12}
|
||||
style={{ color: 'var(--text-muted)' }} />
|
||||
</button>
|
||||
)}
|
||||
{onSecond && (
|
||||
<button onClick={onSecond} title={secondTitle}
|
||||
style={{
|
||||
background: 'transparent', border: 'none',
|
||||
padding: '0 8px 0 4px', marginLeft: 2, cursor: 'pointer',
|
||||
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<Icon name={secondIcon || 'settings'} size={12}
|
||||
style={{ color: 'var(--text-muted)' }} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,9 @@ import Icon from './Icon'
|
||||
import ConfirmDeleteEbene from './ConfirmDeleteEbene'
|
||||
import ContextMenu from './ContextMenu'
|
||||
import { BarCombo, BarButton } from './BarControls'
|
||||
import { setLayerStyle, deleteEbene, moveSelectionToEbene, openEbenenSettings } from '../lib/rhinoBridge'
|
||||
import { setLayerStyle, deleteEbene, moveSelectionToEbene, openEbenenSettings,
|
||||
pickLayerCombination, saveLayerCombination, deleteLayerCombination,
|
||||
openLayerCombinationsDialog } from '../lib/rhinoBridge'
|
||||
|
||||
const MODES = [
|
||||
{ value: 'all_force', label: 'Alle anzeigen' },
|
||||
@@ -245,8 +247,8 @@ function EbeneRow({ e, depth, hasChildren, expanded, onToggleExpand, active, mod
|
||||
onContextMenu={onContextMenu}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: 4,
|
||||
padding: '1px 8px',
|
||||
paddingLeft: 6 + (depth || 0) * 10,
|
||||
padding: '1px 12px 1px 0',
|
||||
paddingLeft: (depth || 0) * 12,
|
||||
margin: 0,
|
||||
background: active ? 'var(--active-dim)'
|
||||
: (e.visible !== false) ? 'var(--bg-item)'
|
||||
@@ -345,6 +347,7 @@ function SortHeader({ label, sortKey, sortBy, sortDir, onSort, style }) {
|
||||
|
||||
export default function EbenenManager({
|
||||
ebenen, activeCode, onActiveChange, onChange, mode, onModeChange, hatchPatterns,
|
||||
layerCombinations = [], activeKombi = null,
|
||||
}) {
|
||||
const [sortBy, setSortBy] = useState('code')
|
||||
const [sortDir, setSortDir] = useState('asc')
|
||||
@@ -548,70 +551,114 @@ export default function EbenenManager({
|
||||
background: 'var(--bg-section)',
|
||||
borderBottom: '1px solid var(--border-light)',
|
||||
}}>
|
||||
<span className="label-xs">Sichtbarkeit</span>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<div style={{ flex: 1, minWidth: 0, display: 'flex' }}>
|
||||
<span className="label-xs">Ebenenkombination</span>
|
||||
<div style={{ display: 'flex', width: '100%' }}>
|
||||
<BarCombo
|
||||
icon="visibility"
|
||||
value={mode}
|
||||
onChange={onModeChange}
|
||||
title="Sichtbarkeits-Modus"
|
||||
stretch
|
||||
icon="layers"
|
||||
value={activeKombi || '__none__'}
|
||||
onChange={(v) => {
|
||||
if (v === '__configure__') { openLayerCombinationsDialog(); return }
|
||||
if (v === '__save__') {
|
||||
const suggested = activeKombi || `Kombi ${layerCombinations.length + 1}`
|
||||
const name = (window.prompt('Name für Ebenenkombination:', suggested) || '').trim()
|
||||
if (!name) return
|
||||
if (layerCombinations.includes(name) &&
|
||||
!window.confirm(`"${name}" überschreiben?`)) return
|
||||
saveLayerCombination(name)
|
||||
return
|
||||
}
|
||||
if (v === '__delete__') {
|
||||
if (activeKombi &&
|
||||
window.confirm(`Kombination "${activeKombi}" löschen?`))
|
||||
deleteLayerCombination(activeKombi)
|
||||
return
|
||||
}
|
||||
pickLayerCombination(v === '__none__' ? null : v)
|
||||
}}
|
||||
title={activeKombi
|
||||
? `Aktive Kombi: ${activeKombi}`
|
||||
: 'Keine Kombination — manuelle Sichtbarkeit'}
|
||||
onGear={openLayerCombinationsDialog}
|
||||
gearTitle="Ebenenkombinationen bearbeiten"
|
||||
>
|
||||
{MODES.map(m => <option key={m.value} value={m.value}>{m.label}</option>)}
|
||||
<option value="__none__">— Eigene —</option>
|
||||
{layerCombinations.map(n => (
|
||||
<option key={n} value={n}>{n}</option>
|
||||
))}
|
||||
<option disabled>──────────</option>
|
||||
<option value="__save__">+ Aktuelle speichern…</option>
|
||||
{activeKombi && (
|
||||
<option value="__delete__">🗑 Aktuelle löschen</option>
|
||||
)}
|
||||
<option value="__configure__">Bearbeiten…</option>
|
||||
</BarCombo>
|
||||
</div>
|
||||
<BarButton icon="add" onClick={addNew} title="Ebene hinzufügen" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', gap: 4,
|
||||
padding: '2px 8px 2px 9px',
|
||||
display: 'flex', flexDirection: 'column', gap: 4,
|
||||
padding: '6px 14px',
|
||||
background: 'var(--bg-section)',
|
||||
borderBottom: '1px solid var(--border)',
|
||||
borderBottom: '1px solid var(--border-light)',
|
||||
}}>
|
||||
{/* Master-Eye: alle Ebenen sichtbar/unsichtbar */}
|
||||
<span className="label-xs">Sichtbarkeit</span>
|
||||
<div style={{ display: 'flex', width: '100%' }}>
|
||||
<BarCombo
|
||||
stretch
|
||||
icon="visibility"
|
||||
value={mode}
|
||||
onChange={onModeChange}
|
||||
title="Sichtbarkeits-Modus"
|
||||
onSecond={addNew}
|
||||
secondIcon="add"
|
||||
secondTitle="Ebene hinzufügen"
|
||||
>
|
||||
{MODES.map(m => <option key={m.value} value={m.value}>{m.label}</option>)}
|
||||
</BarCombo>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sort-Header-Row + Master-Eye/Lock. Padding-Left identisch zu
|
||||
den Data-Rows damit Eye-Icons aligned sind. Erste 12px-Spanne
|
||||
spiegelt den Expand-Chevron-Slot der Data-Rows wider. */}
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', gap: 4,
|
||||
padding: '2px 12px 2px 0',
|
||||
background: 'var(--bg-section)',
|
||||
borderBottom: '1px solid var(--border-light)',
|
||||
}}>
|
||||
<span style={{ width: 12, flexShrink: 0 }} />
|
||||
<button
|
||||
className="btn-icon-xs"
|
||||
onClick={() => {
|
||||
const anyVisible = ebenen.some(e => e.visible !== false)
|
||||
// Wenn irgendeine sichtbar -> alle aus. Wenn keine sichtbar -> alle an.
|
||||
onChange(ebenen.map(e => ({ ...e, visible: !anyVisible })))
|
||||
if (mode === 'active' || mode === 'all_force') onModeChange('all')
|
||||
}}
|
||||
title={ebenen.every(e => e.visible !== false)
|
||||
? 'Alle Ebenen ausblenden'
|
||||
: 'Alle Ebenen einblenden'}
|
||||
style={{ width: 18, height: 18,
|
||||
? 'Alle Ebenen ausblenden' : 'Alle Ebenen einblenden'}
|
||||
style={{ width: 16, height: 16,
|
||||
opacity: (mode === 'active' || mode === 'all_force') ? 0.5 : 1 }}
|
||||
>
|
||||
<Icon
|
||||
name={ebenen.every(e => e.visible !== false) ? 'visibility' : 'visibility_off'}
|
||||
size={12}
|
||||
/>
|
||||
<Icon name={ebenen.every(e => e.visible !== false) ? 'visibility' : 'visibility_off'} size={11} />
|
||||
</button>
|
||||
<SortHeader label="Cd" sortKey="code" sortBy={sortBy} sortDir={sortDir} onSort={toggleSort} style={{ width: 24 }} />
|
||||
<SortHeader label="N" sortKey="code" sortBy={sortBy} sortDir={sortDir} onSort={toggleSort} style={{ width: 22 }} />
|
||||
<div style={{ width: 12 }} />
|
||||
<SortHeader label="Name" sortKey="name" sortBy={sortBy} sortDir={sortDir} onSort={toggleSort} style={{ flex: 1 }} />
|
||||
<SortHeader label="Lw" sortKey="lw" sortBy={sortBy} sortDir={sortDir} onSort={toggleSort} style={{ width: 42, textAlign: 'right', display: 'block' }} />
|
||||
{/* Master-Lock: alle Ebenen sperren/entsperren */}
|
||||
<button
|
||||
className="btn-icon-xs"
|
||||
onClick={() => {
|
||||
const anyLocked = ebenen.some(e => e.locked === true)
|
||||
onChange(ebenen.map(e => ({ ...e, locked: !anyLocked })))
|
||||
}}
|
||||
title={ebenen.every(e => e.locked === true)
|
||||
? 'Alle Ebenen entsperren'
|
||||
: 'Alle Ebenen sperren'}
|
||||
style={{ width: 18, height: 18 }}
|
||||
title={ebenen.every(e => e.locked === true) ? 'Alle Ebenen entsperren' : 'Alle Ebenen sperren'}
|
||||
style={{ width: 14, height: 14 }}
|
||||
>
|
||||
<Icon
|
||||
name={ebenen.every(e => e.locked === true) ? 'lock' : 'lock_open'}
|
||||
size={11}
|
||||
/>
|
||||
<Icon name={ebenen.every(e => e.locked === true) ? 'lock' : 'lock_open'} size={11} />
|
||||
</button>
|
||||
<div style={{ width: 18 }} />
|
||||
<div style={{ width: 14 }} />
|
||||
</div>
|
||||
|
||||
{(() => {
|
||||
|
||||
@@ -48,7 +48,7 @@ function ZeichnungsebeneRow({
|
||||
onContextMenu={onContextMenu}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: 4,
|
||||
padding: '1px 12px',
|
||||
padding: '1px 12px 1px 0',
|
||||
margin: 0,
|
||||
background: active ? 'var(--active-dim)' : 'var(--bg-item)',
|
||||
borderRadius: active ? 999 : 0,
|
||||
@@ -58,6 +58,9 @@ function ZeichnungsebeneRow({
|
||||
minHeight: 24,
|
||||
}}
|
||||
>
|
||||
{/* Spacer-Slot — spiegelt den Chevron-Slot bei Ebenen-Rows wider
|
||||
damit die Eye-Icons beider Panels untereinander stehen. */}
|
||||
<span style={{ width: 12, flexShrink: 0 }} />
|
||||
<button
|
||||
className={`btn-icon-sm ${eyeOn ? 'is-on' : ''}`}
|
||||
onClick={(ev) => { ev.stopPropagation(); onToggleVisible() }}
|
||||
@@ -213,32 +216,37 @@ export default function GeschossManager({
|
||||
borderBottom: '1px solid var(--border-light)',
|
||||
}}>
|
||||
<span className="label-xs">Sichtbarkeit</span>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<div style={{ flex: 1, minWidth: 0, display: 'flex' }}>
|
||||
<div style={{ display: 'flex', width: '100%' }}>
|
||||
<BarCombo
|
||||
stretch
|
||||
icon="visibility"
|
||||
value={mode}
|
||||
onChange={onModeChange}
|
||||
title="Sichtbarkeits-Modus"
|
||||
onGear={() => openGeschossDialog(zeichnungsebenen)}
|
||||
gearIcon="settings"
|
||||
gearTitle="Einstellungen"
|
||||
onSecond={addQuick}
|
||||
secondIcon="add"
|
||||
secondTitle="Zeichnungsebene hinzufügen"
|
||||
>
|
||||
{MODES.map(m => (
|
||||
<option key={m.value} value={m.value}>{m.label}</option>
|
||||
))}
|
||||
</BarCombo>
|
||||
</div>
|
||||
<BarButton icon="add" onClick={addQuick} title="Zeichnungsebene hinzufügen" />
|
||||
<BarButton icon="settings" onClick={() => openGeschossDialog(zeichnungsebenen)} title="Einstellungen" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Master-Row: Master-Eye links + Master-Lock rechts (analog
|
||||
EbenenManager). */}
|
||||
{/* Master-Row analog EbenenManager. Padding + Icon-Sizes identisch
|
||||
damit beide Panels visuell kohaerent sind. Erste 12px-Spanne
|
||||
spiegelt den Chevron-Slot der Ebenen-Daten-Rows wider. */}
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', gap: 5,
|
||||
padding: '2px 14px',
|
||||
display: 'flex', alignItems: 'center', gap: 4,
|
||||
padding: '2px 12px 2px 0',
|
||||
background: 'var(--bg-section)',
|
||||
borderBottom: '1px solid var(--border)',
|
||||
borderBottom: '1px solid var(--border-light)',
|
||||
}}>
|
||||
<span style={{ width: 12, flexShrink: 0 }} />
|
||||
<button
|
||||
className="btn-icon-xs"
|
||||
onClick={() => {
|
||||
@@ -249,12 +257,12 @@ export default function GeschossManager({
|
||||
title={zeichnungsebenen.every(z => z.visible !== false)
|
||||
? 'Alle Zeichnungsebenen ausblenden'
|
||||
: 'Alle Zeichnungsebenen einblenden'}
|
||||
style={{ width: 18, height: 18,
|
||||
style={{ width: 16, height: 16,
|
||||
opacity: (mode === 'active' || mode === 'all_force') ? 0.5 : 1 }}
|
||||
>
|
||||
<Icon
|
||||
name={zeichnungsebenen.every(z => z.visible !== false) ? 'visibility' : 'visibility_off'}
|
||||
size={12}
|
||||
size={11}
|
||||
/>
|
||||
</button>
|
||||
<span style={{ flex: 1 }} />
|
||||
@@ -267,14 +275,14 @@ export default function GeschossManager({
|
||||
title={zeichnungsebenen.every(z => z.locked === true)
|
||||
? 'Alle Zeichnungsebenen entsperren'
|
||||
: 'Alle Zeichnungsebenen sperren'}
|
||||
style={{ width: 18, height: 18 }}
|
||||
style={{ width: 14, height: 14 }}
|
||||
>
|
||||
<Icon
|
||||
name={zeichnungsebenen.every(z => z.locked === true) ? 'lock' : 'lock_open'}
|
||||
size={11}
|
||||
/>
|
||||
</button>
|
||||
<div style={{ width: 18 }} />
|
||||
<div style={{ width: 14 }} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -172,6 +172,11 @@ export function openOverridesPanel() { send('OPEN_OVERRIDES_PANEL', {}) }
|
||||
export function openKameraPanel() { send('OPEN_KAMERA_PANEL', {}) }
|
||||
export function openElementeUebersicht() { send('OPEN_ELEMENTE_UEBERSICHT', {}) }
|
||||
export function openElementeProperties() { send('OPEN_ELEMENTE_PROPERTIES', {}) }
|
||||
export function setDarstellung(d) { send('SET_DARSTELLUNG', { darstellung: d || '' }) }
|
||||
export function saveOeffStyle(name, settings) {
|
||||
send('SAVE_OEFF_STYLE', { name, settings })
|
||||
}
|
||||
export function deleteOeffStyle(id) { send('DELETE_OEFF_STYLE', { id }) }
|
||||
export function setSectionStyle(enabled, source, color, pattern, scale, rotation) {
|
||||
send('SET_SECTION_STYLE', { enabled, source, color, pattern, scale, rotation })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user