Raumstempel-Stile (Presets) — speichern + anwenden per Doc
Damit User wiederkehrende Stempel-Configs (Wettbewerb / Bauantrag /
Mobiliar etc.) nicht jedes Mal neu klicken muss.
Backend (elemente.py):
- Storage in doc.Strings dossier_raum_stempel_stile als JSON-Array
- load_raum_stempel_stile / save_raum_stempel_stile Helpers
- Bridge-Handler:
- SAVE_RAUM_STIL: upsert by id (neu wenn leer)
- DELETE_RAUM_STIL: remove by id
- APPLY_RAUM_STIL: schreibt Stil-Felder auf Raum-IDs + Regen
- _RAUM_STIL_FIELDS umfasst font/bold/italic/txtH/txtModus/align/
rundung/fuellung/showSia/layout (alles was die Optik bestimmt)
- raumStempelStile im STATE-Emit zum Frontend
Frontend:
- saveRaumStil/deleteRaumStil/applyRaumStil in rhinoBridge.js
- RaumProperties: neue "Stil"-Sektion oben mit Dropdown
* gespeicherte Stile + "+ Aktuelle Settings als Stil speichern" +
"🗑 Aktiven Stil loeschen"
* Klick auf Stil → applyRaumStil mit aktueller Raum-ID
- Beide PropertiesView-Aufrufe (inline + satellite) bekommen die Liste
This commit is contained in:
@@ -330,6 +330,64 @@ _RAUM_FIELD_IDS = ("nummer", "name", "funktion", "area", "sia")
|
|||||||
# Default-Layout (entspricht dem alten Verhalten)
|
# Default-Layout (entspricht dem alten Verhalten)
|
||||||
_RAUM_LAYOUT_DEFAULT = [["nummer", "name"], ["funktion"], ["area"]]
|
_RAUM_LAYOUT_DEFAULT = [["nummer", "name"], ["funktion"], ["area"]]
|
||||||
|
|
||||||
|
# Per-Document Storage-Key fuer Raumstempel-Stile (Presets).
|
||||||
|
_KEY_RAUM_STILE = "dossier_raum_stempel_stile"
|
||||||
|
|
||||||
|
# Felder die ein Stil persistiert (= alles was die Optik bestimmt). Nicht
|
||||||
|
# enthalten: Name/Nummer/Funktion (inhaltlich pro Raum), area/umfang (aus
|
||||||
|
# Geometrie berechnet), Geschoss (Raum-Position).
|
||||||
|
_RAUM_STIL_FIELDS = (
|
||||||
|
"font", "bold", "italic",
|
||||||
|
"txtH", "txtModus",
|
||||||
|
"align", "rundung",
|
||||||
|
"fuellung",
|
||||||
|
"showSia",
|
||||||
|
"layout",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_raum_stempel_stile(doc):
|
||||||
|
"""Liest alle Raumstempel-Stile aus doc.Strings als Liste of dicts.
|
||||||
|
Schema pro Stil: {id, name, <_RAUM_STIL_FIELDS>}. Leer wenn keine.
|
||||||
|
"""
|
||||||
|
if doc is None: return []
|
||||||
|
try:
|
||||||
|
raw = doc.Strings.GetValue(_KEY_RAUM_STILE) or ""
|
||||||
|
except Exception: raw = ""
|
||||||
|
if not raw: return []
|
||||||
|
try:
|
||||||
|
import json as _json
|
||||||
|
data = _json.loads(raw)
|
||||||
|
if not isinstance(data, list): return []
|
||||||
|
out = []
|
||||||
|
for s in data:
|
||||||
|
if not isinstance(s, dict): continue
|
||||||
|
if not s.get("id") or not s.get("name"): continue
|
||||||
|
out.append(s)
|
||||||
|
return out
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] load_raum_stempel_stile:", ex)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def save_raum_stempel_stile(doc, stile):
|
||||||
|
"""Persistiert die Stil-Liste in doc.Strings."""
|
||||||
|
if doc is None or not isinstance(stile, list): return False
|
||||||
|
try:
|
||||||
|
import json as _json
|
||||||
|
clean = []
|
||||||
|
for s in stile:
|
||||||
|
if not isinstance(s, dict): continue
|
||||||
|
if not s.get("id") or not s.get("name"): continue
|
||||||
|
clean.append(s)
|
||||||
|
doc.Strings.SetString(_KEY_RAUM_STILE,
|
||||||
|
_json.dumps(clean, ensure_ascii=False))
|
||||||
|
return True
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] save_raum_stempel_stile:", ex)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
_RAUM_RUNDUNGEN = ("exakt", "0.01", "0.1", "0.5", "1")
|
_RAUM_RUNDUNGEN = ("exakt", "0.01", "0.1", "0.5", "1")
|
||||||
|
|
||||||
|
|
||||||
@@ -6382,6 +6440,17 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
delete_oeff_style(doc, p.get("id"))
|
delete_oeff_style(doc, p.get("id"))
|
||||||
except Exception as ex: print("[ELEMENTE] del oeff style:", ex)
|
except Exception as ex: print("[ELEMENTE] del oeff style:", ex)
|
||||||
self._send_state()
|
self._send_state()
|
||||||
|
elif t == "SAVE_RAUM_STIL":
|
||||||
|
# Speichert aktuelle Raum-Stil-Settings (vom Frontend gepacked)
|
||||||
|
# unter id (neu wenn leer) + name. Liste persistiert in
|
||||||
|
# doc.Strings, state-emit pushed neue Liste zum UI.
|
||||||
|
self._cmd_save_raum_stil(p)
|
||||||
|
elif t == "DELETE_RAUM_STIL":
|
||||||
|
self._cmd_delete_raum_stil(p)
|
||||||
|
elif t == "APPLY_RAUM_STIL":
|
||||||
|
# Wendet einen gespeicherten Stil auf die im Patch genannten
|
||||||
|
# raum_outline element_ids an. p = {stilId, ids=[…]}.
|
||||||
|
self._cmd_apply_raum_stil(p)
|
||||||
elif t == "OPEN_ELEMENTE_UEBERSICHT":
|
elif t == "OPEN_ELEMENTE_UEBERSICHT":
|
||||||
try:
|
try:
|
||||||
import elemente_uebersicht
|
import elemente_uebersicht
|
||||||
@@ -6647,6 +6716,7 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
{"name": n, "color": m["color"]}
|
{"name": n, "color": m["color"]}
|
||||||
for n, m in _get_all_materials(doc).items()],
|
for n, m in _get_all_materials(doc).items()],
|
||||||
"oeffStyles": list_oeff_styles(doc),
|
"oeffStyles": list_oeff_styles(doc),
|
||||||
|
"raumStempelStile": load_raum_stempel_stile(doc),
|
||||||
}
|
}
|
||||||
self.send("STATE", payload)
|
self.send("STATE", payload)
|
||||||
# An Properties-Satellite-Window forwarden falls offen
|
# An Properties-Satellite-Window forwarden falls offen
|
||||||
@@ -8464,6 +8534,84 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
size=(640, 560),
|
size=(640, 560),
|
||||||
bridge=b)
|
bridge=b)
|
||||||
|
|
||||||
|
def _cmd_save_raum_stil(self, p):
|
||||||
|
"""Speichert einen Raumstempel-Stil. Payload:
|
||||||
|
{id?: string, name: string, settings: {font, bold, italic, txtH,
|
||||||
|
txtModus, align, rundung, fuellung, showSia, layout}}
|
||||||
|
Wenn id leer → neuer Stil mit uuid. State-Emit anschliessend.
|
||||||
|
"""
|
||||||
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
|
if doc is None: return
|
||||||
|
name = (p.get("name") or "").strip()
|
||||||
|
if not name:
|
||||||
|
print("[ELEMENTE] SAVE_RAUM_STIL: name leer")
|
||||||
|
return
|
||||||
|
settings = p.get("settings") or {}
|
||||||
|
sid = (p.get("id") or "").strip() or ("stil_" + uuid.uuid4().hex[:8])
|
||||||
|
stile = load_raum_stempel_stile(doc)
|
||||||
|
# Upsert by id
|
||||||
|
new_stil = {"id": sid, "name": name}
|
||||||
|
for f in _RAUM_STIL_FIELDS:
|
||||||
|
if f in settings:
|
||||||
|
new_stil[f] = settings[f]
|
||||||
|
found = False
|
||||||
|
for i, s in enumerate(stile):
|
||||||
|
if s.get("id") == sid:
|
||||||
|
stile[i] = new_stil; found = True; break
|
||||||
|
if not found:
|
||||||
|
stile.append(new_stil)
|
||||||
|
if save_raum_stempel_stile(doc, stile):
|
||||||
|
print("[ELEMENTE] Stempel-Stil '{}' ({}) gespeichert".format(
|
||||||
|
name, sid))
|
||||||
|
self._send_state()
|
||||||
|
|
||||||
|
def _cmd_delete_raum_stil(self, p):
|
||||||
|
"""Loescht einen Stil per id. Payload: {id}."""
|
||||||
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
|
if doc is None: return
|
||||||
|
sid = (p.get("id") or "").strip()
|
||||||
|
if not sid: return
|
||||||
|
stile = load_raum_stempel_stile(doc)
|
||||||
|
stile = [s for s in stile if s.get("id") != sid]
|
||||||
|
if save_raum_stempel_stile(doc, stile):
|
||||||
|
print("[ELEMENTE] Stempel-Stil {} geloescht".format(sid))
|
||||||
|
self._send_state()
|
||||||
|
|
||||||
|
def _cmd_apply_raum_stil(self, p):
|
||||||
|
"""Wendet einen Stil auf eine Liste von raum_outline-Element-IDs
|
||||||
|
an. Payload: {stilId, ids: [element_id, ...]}. Schreibt die Stil-
|
||||||
|
Felder auf die Source-Outlines + triggert Regen pro Raum.
|
||||||
|
"""
|
||||||
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
|
if doc is None: return
|
||||||
|
sid = (p.get("stilId") or "").strip()
|
||||||
|
ids = p.get("ids") or []
|
||||||
|
if not sid or not ids: return
|
||||||
|
stile = load_raum_stempel_stile(doc)
|
||||||
|
stil = next((s for s in stile if s.get("id") == sid), None)
|
||||||
|
if stil is None:
|
||||||
|
print("[ELEMENTE] APPLY_RAUM_STIL: Stil {} nicht gefunden".format(sid))
|
||||||
|
return
|
||||||
|
# Stil-Felder → Patch fuer _update_wall-Pfad
|
||||||
|
patch_base = {}
|
||||||
|
for f in _RAUM_STIL_FIELDS:
|
||||||
|
if f in stil:
|
||||||
|
patch_base[f] = stil[f]
|
||||||
|
n_applied = 0
|
||||||
|
for eid in ids:
|
||||||
|
try:
|
||||||
|
# _update_wall braucht id + alle Patch-Felder
|
||||||
|
patch = dict(patch_base)
|
||||||
|
patch["id"] = eid
|
||||||
|
self._update_wall(patch)
|
||||||
|
n_applied += 1
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] apply stil {} -> {}: {}".format(sid, eid, ex))
|
||||||
|
if n_applied > 0:
|
||||||
|
print("[ELEMENTE] Stempel-Stil '{}' auf {} Raum/Raeume angewendet".format(
|
||||||
|
stil.get("name"), n_applied))
|
||||||
|
self._send_state()
|
||||||
|
|
||||||
def _cmd_create_symbol(self, p):
|
def _cmd_create_symbol(self, p):
|
||||||
"""Platziert ein Library-Item (symbol/object) im Doc. Interactive
|
"""Platziert ein Library-Item (symbol/object) im Doc. Interactive
|
||||||
GetPoint im aktiven Viewport — User klickt Position.
|
GetPoint im aktiven Viewport — User klickt Position.
|
||||||
|
|||||||
+58
-3
@@ -11,6 +11,7 @@ import {
|
|||||||
openSwisstopo, openSwisstopoDialog, openOsmDialog,
|
openSwisstopo, openSwisstopoDialog, openOsmDialog,
|
||||||
updateElement, deleteElement, openElementeUebersicht, openElementeProperties,
|
updateElement, deleteElement, openElementeUebersicht, openElementeProperties,
|
||||||
saveOeffStyle, deleteOeffStyle,
|
saveOeffStyle, deleteOeffStyle,
|
||||||
|
saveRaumStil, deleteRaumStil, applyRaumStil,
|
||||||
listLibrary,
|
listLibrary,
|
||||||
} from './lib/rhinoBridge'
|
} from './lib/rhinoBridge'
|
||||||
|
|
||||||
@@ -506,7 +507,7 @@ function NeuesElementSection({ noGeschoss, activeName, elementsCount }) {
|
|||||||
|
|
||||||
// PropertiesView: gemeinsame Komponente, rendert die passende Property-
|
// PropertiesView: gemeinsame Komponente, rendert die passende Property-
|
||||||
// Form je nach Element-Typ. Wiederverwendbar in Inline + Satellite-Window.
|
// Form je nach Element-Typ. Wiederverwendbar in Inline + Satellite-Window.
|
||||||
export function PropertiesView({ selected, geschosse, materials, hatchPatterns, oeffStyles, fonts }) {
|
export function PropertiesView({ selected, geschosse, materials, hatchPatterns, oeffStyles, fonts, raumStempelStile }) {
|
||||||
if (!selected) return null
|
if (!selected) return null
|
||||||
const upd = (p) => updateElement(selected.id, p)
|
const upd = (p) => updateElement(selected.id, p)
|
||||||
const del = (label) => () => { if (window.confirm(`${label} löschen?`)) deleteElement(selected.id) }
|
const del = (label) => () => { if (window.confirm(`${label} löschen?`)) deleteElement(selected.id) }
|
||||||
@@ -529,6 +530,7 @@ export function PropertiesView({ selected, geschosse, materials, hatchPatterns,
|
|||||||
if (selected.kind === 'raum')
|
if (selected.kind === 'raum')
|
||||||
return <RaumProperties raum={selected} geschosse={geschosse}
|
return <RaumProperties raum={selected} geschosse={geschosse}
|
||||||
hatchPatterns={hatchPatterns} fonts={fonts || []}
|
hatchPatterns={hatchPatterns} fonts={fonts || []}
|
||||||
|
raumStempelStile={raumStempelStile || []}
|
||||||
onUpdate={upd} onDelete={del('Raum')} />
|
onUpdate={upd} onDelete={del('Raum')} />
|
||||||
|
|
||||||
if (selected.kind === 'aussparung')
|
if (selected.kind === 'aussparung')
|
||||||
@@ -583,7 +585,8 @@ export default function ElementeApp() {
|
|||||||
materials={state.materials || []}
|
materials={state.materials || []}
|
||||||
hatchPatterns={state.hatchPatterns}
|
hatchPatterns={state.hatchPatterns}
|
||||||
fonts={state.fonts || []}
|
fonts={state.fonts || []}
|
||||||
oeffStyles={state.oeffStyles || []} />
|
oeffStyles={state.oeffStyles || []}
|
||||||
|
raumStempelStile={state.raumStempelStile || []} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<NeuesElementSection
|
<NeuesElementSection
|
||||||
@@ -883,7 +886,40 @@ function StempelLayoutBuilder({ layout, availableFields, onChange }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fonts }) {
|
function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fonts, raumStempelStile }) {
|
||||||
|
const stilList = raumStempelStile || []
|
||||||
|
// Match: aktueller Raum-Stempel-Aktiv-Stil-id wird per UserString
|
||||||
|
// dossier_raum_stil_id gespeichert wenn ein Stil applied wurde. Fuer
|
||||||
|
// jetzt: nicht-persistent — Match anhand visueller Settings (font + layout)
|
||||||
|
// koennten wir tun, aber zu fragil. Default: kein Stil markiert.
|
||||||
|
const activeStilId = raum.stilId || ''
|
||||||
|
const handleStilChange = (val) => {
|
||||||
|
if (val === '__save__') {
|
||||||
|
const n = (window.prompt('Name für neuen Stempel-Stil:', 'Stil') || '').trim()
|
||||||
|
if (!n) return
|
||||||
|
saveRaumStil('', n, {
|
||||||
|
font: raum.font || '',
|
||||||
|
bold: !!raum.bold,
|
||||||
|
italic: !!raum.italic,
|
||||||
|
txtH: raum.txtH,
|
||||||
|
txtModus: raum.txtModus || 'fix',
|
||||||
|
align: raum.align || 'mid',
|
||||||
|
rundung: raum.rundung || '',
|
||||||
|
fuellung: raum.fuellung || '',
|
||||||
|
showSia: !!raum.showSia,
|
||||||
|
layout: Array.isArray(raum.layout) ? raum.layout : [],
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (val === '__delete__') {
|
||||||
|
if (activeStilId &&
|
||||||
|
window.confirm(`Stil "${stilList.find(s => s.id === activeStilId)?.name}" löschen?`))
|
||||||
|
deleteRaumStil(activeStilId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (val) applyRaumStil(val, [raum.id])
|
||||||
|
}
|
||||||
|
|
||||||
const [name, setName] = useState(raum.name || 'Raum')
|
const [name, setName] = useState(raum.name || 'Raum')
|
||||||
const [nummer, setNummer] = useState(raum.nummer || '')
|
const [nummer, setNummer] = useState(raum.nummer || '')
|
||||||
const [funktion, setFunktion] = useState(raum.funktion || '')
|
const [funktion, setFunktion] = useState(raum.funktion || '')
|
||||||
@@ -944,6 +980,25 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Stempel-Stil Preset-Picker — apply einer gespeicherten Visual-Vorlage
|
||||||
|
auf den Raum. "+ Aktuell speichern" frischt die Liste mit den
|
||||||
|
jetzigen Settings als neuen Stil. Stile sind per-Doc. */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
|
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Stil</span>
|
||||||
|
<select value={activeStilId}
|
||||||
|
onChange={(e) => handleStilChange(e.target.value)}
|
||||||
|
style={{ flex: 1, fontSize: 11 }}
|
||||||
|
title="Gespeicherter Stempel-Stil — Klick wendet die Vorlage an">
|
||||||
|
<option value="">— Stil wählen —</option>
|
||||||
|
{stilList.map(s => (
|
||||||
|
<option key={s.id} value={s.id}>{s.name}</option>
|
||||||
|
))}
|
||||||
|
<option disabled>──────────</option>
|
||||||
|
<option value="__save__">+ Aktuelle Settings als Stil speichern…</option>
|
||||||
|
{activeStilId && <option value="__delete__">🗑 Aktiven Stil löschen</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: 60 }}>Geschoss</span>
|
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Geschoss</span>
|
||||||
<select value={raum.geschoss}
|
<select value={raum.geschoss}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export default function ElementePropertiesApp() {
|
|||||||
hatchPatterns={state.hatchPatterns}
|
hatchPatterns={state.hatchPatterns}
|
||||||
fonts={state.fonts || []}
|
fonts={state.fonts || []}
|
||||||
oeffStyles={state.oeffStyles || []}
|
oeffStyles={state.oeffStyles || []}
|
||||||
|
raumStempelStile={state.raumStempelStile || []}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div style={{
|
<div style={{
|
||||||
|
|||||||
@@ -243,6 +243,14 @@ export function saveOeffStyle(name, settings) {
|
|||||||
send('SAVE_OEFF_STYLE', { name, settings })
|
send('SAVE_OEFF_STYLE', { name, settings })
|
||||||
}
|
}
|
||||||
export function deleteOeffStyle(id) { send('DELETE_OEFF_STYLE', { id }) }
|
export function deleteOeffStyle(id) { send('DELETE_OEFF_STYLE', { id }) }
|
||||||
|
// Raumstempel-Stile (Presets pro Doc)
|
||||||
|
export function saveRaumStil(id, name, settings) {
|
||||||
|
send('SAVE_RAUM_STIL', { id, name, settings })
|
||||||
|
}
|
||||||
|
export function deleteRaumStil(id) { send('DELETE_RAUM_STIL', { id }) }
|
||||||
|
export function applyRaumStil(stilId, ids) {
|
||||||
|
send('APPLY_RAUM_STIL', { stilId, ids })
|
||||||
|
}
|
||||||
export function setSectionStyle(enabled, source, color, pattern, scale, rotation,
|
export function setSectionStyle(enabled, source, color, pattern, scale, rotation,
|
||||||
opts = {}) {
|
opts = {}) {
|
||||||
send('SET_SECTION_STYLE', {
|
send('SET_SECTION_STYLE', {
|
||||||
|
|||||||
Reference in New Issue
Block a user