Projektdaten in Project-Settings + Swisstopo-Adress-Prefill
Schema-Erweiterung: - _PROJECT_SETTINGS_DEFAULTS hat jetzt 'project'-Block mit name / number / address / bauherr / architekt / notes / projectZeroMum - _normalize_project_meta stripped Strings + clampt mum als float - load/save_project_settings handeln das 'project'-feld - save_project_settings spiegelt projectZeroMum auch in den Legacy-Key dossier_project_zero_mum (fuer Geschoss-Settings-Dialog) - load_project_settings liest Legacy-Key als Fallback wenn neuer Wert noch nicht gesetzt UI: - InlineTextField + TextareaField Helpers (Pill-Stil) - Projektdaten-Section in Voreinstellungen-Tab: Name, Projekt-Nr., Adresse, Bauherrschaft, Architekt:in, EG-Nullpunkt m.ü.M (mit Hinweis auf Swisstopo-Nutzung), Notizen Swisstopo: - _cmd_open_swisstopo_dialog laedt Projekt-Adresse + sendet projectAddress im SWISSTOPO_STATE - SwisstopoApp: vorbelegt searchText mit projectAddress wenn Feld leer ist (User-Input wird nicht ueberschrieben) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -8217,6 +8217,14 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
e_raw = doc.Strings.GetValue("dossier_ebenen") if doc else None
|
e_raw = doc.Strings.GetValue("dossier_ebenen") if doc else None
|
||||||
ebenen = json.loads(e_raw) if e_raw else []
|
ebenen = json.loads(e_raw) if e_raw else []
|
||||||
except Exception: ebenen = []
|
except Exception: ebenen = []
|
||||||
|
# Projekt-Adresse als Vorschlag fuer die Adress-Suche
|
||||||
|
project_address = ""
|
||||||
|
try:
|
||||||
|
import rhinopanel
|
||||||
|
ps = rhinopanel.load_project_settings(doc) if doc else None
|
||||||
|
if isinstance(ps, dict):
|
||||||
|
project_address = (ps.get("project", {}) or {}).get("address") or ""
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
class _SwisstopoBridge(panel_base.BaseBridge):
|
class _SwisstopoBridge(panel_base.BaseBridge):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -8225,6 +8233,7 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
self.send("SWISSTOPO_STATE", {
|
self.send("SWISSTOPO_STATE", {
|
||||||
"ebenen": ebenen,
|
"ebenen": ebenen,
|
||||||
"cacheDir": __import__("swisstopo").CACHE_DIR,
|
"cacheDir": __import__("swisstopo").CACHE_DIR,
|
||||||
|
"projectAddress": project_address,
|
||||||
})
|
})
|
||||||
def _push_log(self, msg):
|
def _push_log(self, msg):
|
||||||
try: self.send("SWISSTOPO_LOG", {"msg": str(msg)})
|
try: self.send("SWISSTOPO_LOG", {"msg": str(msg)})
|
||||||
|
|||||||
+60
-6
@@ -283,8 +283,36 @@ _PROJECT_SETTINGS_DEFAULTS = {
|
|||||||
"schnittHeightMin": -1.0,
|
"schnittHeightMin": -1.0,
|
||||||
"schnittHeightMax": 12.0,
|
"schnittHeightMax": 12.0,
|
||||||
},
|
},
|
||||||
"materials": [], # User-erweiterte Materialien (zusaetzlich zur
|
"materials": [],
|
||||||
# hardcoded _MATERIAL_LIBRARY in elemente.py)
|
"project": {
|
||||||
|
"name": "",
|
||||||
|
"number": "",
|
||||||
|
"address": "",
|
||||||
|
"bauherr": "",
|
||||||
|
"architekt": "",
|
||||||
|
"notes": "",
|
||||||
|
# m.ü.M (Meter ueber Meer) des Rhino-z=0 (= EG-Nullpunkt). Wird
|
||||||
|
# von Swisstopo/Terrain-Import zur Hoehen-Kalibrierung genutzt.
|
||||||
|
"projectZeroMum": 0.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_project_meta(p):
|
||||||
|
"""Garantiert das project-Schema. Strings werden gestripped, mum als
|
||||||
|
float (default 0.0 wenn nicht parsebar)."""
|
||||||
|
if not isinstance(p, dict): p = {}
|
||||||
|
try: mum = float(p.get("projectZeroMum", 0.0) or 0.0)
|
||||||
|
except Exception: mum = 0.0
|
||||||
|
def _s(k): return str(p.get(k) or "")
|
||||||
|
return {
|
||||||
|
"name": _s("name"),
|
||||||
|
"number": _s("number"),
|
||||||
|
"address": _s("address"),
|
||||||
|
"bauherr": _s("bauherr"),
|
||||||
|
"architekt": _s("architekt"),
|
||||||
|
"notes": _s("notes"),
|
||||||
|
"projectZeroMum": mum,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -343,16 +371,17 @@ def _clamp(v, lo, hi):
|
|||||||
|
|
||||||
def load_project_settings(doc):
|
def load_project_settings(doc):
|
||||||
"""Liefert die Project-Settings als dict — mit Defaults-Merge wenn
|
"""Liefert die Project-Settings als dict — mit Defaults-Merge wenn
|
||||||
Felder fehlen. Garantiert dass `defaults` und `materials` immer da
|
Felder fehlen. Garantiert dass `defaults`, `materials` und `project`
|
||||||
sind, und Materialien normalisiert (source + libraryId)."""
|
immer da sind."""
|
||||||
raw = None
|
raw = None
|
||||||
try: raw = doc.Strings.GetValue(_PROJECT_SETTINGS_KEY) if doc else None
|
try: raw = doc.Strings.GetValue(_PROJECT_SETTINGS_KEY) if doc else None
|
||||||
except Exception: raw = None
|
except Exception: raw = None
|
||||||
out = {
|
out = {
|
||||||
"defaults": dict(_PROJECT_SETTINGS_DEFAULTS["defaults"]),
|
"defaults": dict(_PROJECT_SETTINGS_DEFAULTS["defaults"]),
|
||||||
"materials": list(_PROJECT_SETTINGS_DEFAULTS["materials"]),
|
"materials": list(_PROJECT_SETTINGS_DEFAULTS["materials"]),
|
||||||
|
"project": dict(_PROJECT_SETTINGS_DEFAULTS["project"]),
|
||||||
}
|
}
|
||||||
if not raw: return out
|
if raw:
|
||||||
try:
|
try:
|
||||||
data = json.loads(raw)
|
data = json.loads(raw)
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
@@ -366,16 +395,40 @@ def load_project_settings(doc):
|
|||||||
_normalize_material(x) for x in m
|
_normalize_material(x) for x in m
|
||||||
if _normalize_material(x) is not None
|
if _normalize_material(x) is not None
|
||||||
]
|
]
|
||||||
|
pr = data.get("project")
|
||||||
|
if isinstance(pr, dict):
|
||||||
|
out["project"] = _normalize_project_meta(pr)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[PROJECT-SETTINGS] load:", ex)
|
print("[PROJECT-SETTINGS] load:", ex)
|
||||||
|
# Legacy-Sync: alter dossier_project_zero_mum-Wert in project.projectZeroMum
|
||||||
|
# spiegeln wenn Project-Settings noch keinen hat. Geschoss-Settings-Dialog
|
||||||
|
# schreibt diesen Key separat — wir lesen ihn als fallback.
|
||||||
|
try:
|
||||||
|
if doc and out["project"]["projectZeroMum"] == 0.0:
|
||||||
|
legacy = doc.Strings.GetValue("dossier_project_zero_mum")
|
||||||
|
if legacy:
|
||||||
|
out["project"]["projectZeroMum"] = float(legacy)
|
||||||
|
except Exception: pass
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def save_project_settings(doc, settings):
|
def save_project_settings(doc, settings):
|
||||||
"""Persistiert Settings in doc.Strings. settings: dict mit
|
"""Persistiert Settings in doc.Strings. settings: dict mit
|
||||||
'defaults' + 'materials'. Caller broadcastet ggf. selbst."""
|
'defaults' + 'materials' + 'project'. Schreibt auch
|
||||||
|
`dossier_project_zero_mum` separat (legacy-key fuer Geschoss-Settings)."""
|
||||||
if doc is None or not isinstance(settings, dict): return False
|
if doc is None or not isinstance(settings, dict): return False
|
||||||
try:
|
try:
|
||||||
|
# project-Block normalisieren bevor wir speichern
|
||||||
|
if "project" in settings:
|
||||||
|
settings = dict(settings)
|
||||||
|
settings["project"] = _normalize_project_meta(settings["project"])
|
||||||
|
# Legacy-Key spiegeln
|
||||||
|
try:
|
||||||
|
mum = settings["project"].get("projectZeroMum", 0.0)
|
||||||
|
doc.Strings.SetString("dossier_project_zero_mum",
|
||||||
|
"{:.6f}".format(float(mum)))
|
||||||
|
except Exception as ex:
|
||||||
|
print("[PROJECT-SETTINGS] mum sync:", ex)
|
||||||
doc.Strings.SetString(_PROJECT_SETTINGS_KEY,
|
doc.Strings.SetString(_PROJECT_SETTINGS_KEY,
|
||||||
json.dumps(settings, ensure_ascii=False))
|
json.dumps(settings, ensure_ascii=False))
|
||||||
return True
|
return True
|
||||||
@@ -849,6 +902,7 @@ class EbenenBridge(panel_base.BaseBridge):
|
|||||||
new_settings = {
|
new_settings = {
|
||||||
"defaults": updated.get("defaults", {}),
|
"defaults": updated.get("defaults", {}),
|
||||||
"materials": updated.get("materials", []),
|
"materials": updated.get("materials", []),
|
||||||
|
"project": updated.get("project", {}),
|
||||||
}
|
}
|
||||||
save_project_settings(doc2, new_settings)
|
save_project_settings(doc2, new_settings)
|
||||||
_broadcast_state(doc2)
|
_broadcast_state(doc2)
|
||||||
|
|||||||
@@ -88,8 +88,13 @@ export default function SwisstopoApp() {
|
|||||||
const logRef = useRef(null)
|
const logRef = useRef(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onMessage('SWISSTOPO_STATE', ({ ebenen }) => {
|
onMessage('SWISSTOPO_STATE', ({ ebenen, projectAddress }) => {
|
||||||
if (Array.isArray(ebenen)) setEbenen(ebenen)
|
if (Array.isArray(ebenen)) setEbenen(ebenen)
|
||||||
|
// Projekt-Adresse aus Project-Settings als Vorschlag — nur belegen
|
||||||
|
// wenn das Feld noch leer ist (User-Input nicht ueberschreiben).
|
||||||
|
if (projectAddress) {
|
||||||
|
setSearchText(prev => prev && prev.trim() ? prev : projectAddress)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
onMessage('GEOCODE_RESULT', ({ result }) => {
|
onMessage('GEOCODE_RESULT', ({ result }) => {
|
||||||
setSearching(false)
|
setSearching(false)
|
||||||
|
|||||||
@@ -25,6 +25,46 @@ function Field({ label, hint, children, style }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* InlineTextField — Label links, Text-Input rechts (kompakt) */
|
||||||
|
function InlineTextField({ label, value, onChange, placeholder, width = 240 }) {
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '5px 0',
|
||||||
|
display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||||
|
<span style={{ flex: 1, fontSize: 11, color: 'var(--text-primary)' }}>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<input type="text" value={value || ''}
|
||||||
|
placeholder={placeholder || ''}
|
||||||
|
onChange={(ev) => onChange(ev.target.value)}
|
||||||
|
style={{ width, height: BAR_H, padding: '0 12px',
|
||||||
|
fontSize: 11 }} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TextareaField — Label oben, mehrzeiliges Input darunter (full-width) */
|
||||||
|
function TextareaField({ label, value, onChange, rows = 3, placeholder }) {
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '6px 0' }}>
|
||||||
|
<div style={{ fontSize: 11, color: 'var(--text-primary)',
|
||||||
|
marginBottom: 4 }}>{label}</div>
|
||||||
|
<textarea value={value || ''}
|
||||||
|
placeholder={placeholder || ''}
|
||||||
|
rows={rows}
|
||||||
|
onChange={(ev) => onChange(ev.target.value)}
|
||||||
|
style={{ width: '100%', boxSizing: 'border-box',
|
||||||
|
padding: '6px 12px',
|
||||||
|
fontSize: 11, fontFamily: 'var(--font)',
|
||||||
|
background: 'var(--bg-input)',
|
||||||
|
color: 'var(--text-primary)',
|
||||||
|
border: '1px solid var(--border)',
|
||||||
|
borderRadius: 12,
|
||||||
|
resize: 'vertical', outline: 'none',
|
||||||
|
lineHeight: 1.5 }} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/* InlineNumberField — Label links, schmales Number-Input rechts (kompakt) */
|
/* InlineNumberField — Label links, schmales Number-Input rechts (kompakt) */
|
||||||
function InlineNumberField({ label, hint, value, onChange, step, min, max, suffix }) {
|
function InlineNumberField({ label, hint, value, onChange, step, min, max, suffix }) {
|
||||||
return (
|
return (
|
||||||
@@ -579,7 +619,10 @@ export default function ProjectSettingsDialog({
|
|||||||
const [draft, setDraft] = useState(() => ({
|
const [draft, setDraft] = useState(() => ({
|
||||||
defaults: { ...(initial.defaults || {}) },
|
defaults: { ...(initial.defaults || {}) },
|
||||||
materials: [...(initial.materials || [])],
|
materials: [...(initial.materials || [])],
|
||||||
|
project: { ...(initial.project || {}) },
|
||||||
}))
|
}))
|
||||||
|
const setProject = (k, v) =>
|
||||||
|
setDraft(d => ({ ...d, project: { ...(d.project || {}), [k]: v } }))
|
||||||
const [selMat, setSelMat] = useState(() => {
|
const [selMat, setSelMat] = useState(() => {
|
||||||
// Default-Auswahl: erstes Builtin wenn vorhanden, sonst erstes Local
|
// Default-Auswahl: erstes Builtin wenn vorhanden, sonst erstes Local
|
||||||
const b = initial.builtinMaterials || []
|
const b = initial.builtinMaterials || []
|
||||||
@@ -704,7 +747,38 @@ export default function ProjectSettingsDialog({
|
|||||||
<div style={{ flex: 1, minHeight: 0, overflowY: 'auto',
|
<div style={{ flex: 1, minHeight: 0, overflowY: 'auto',
|
||||||
padding: '8px 14px' }}>
|
padding: '8px 14px' }}>
|
||||||
{tab === 'defaults' && (
|
{tab === 'defaults' && (
|
||||||
<div style={{ maxWidth: 520 }}>
|
<div style={{ maxWidth: 560 }}>
|
||||||
|
<DetailSection title="Projektdaten">
|
||||||
|
<InlineTextField label="Projektname"
|
||||||
|
value={draft.project?.name}
|
||||||
|
placeholder="z.B. Wohnhaus Müller"
|
||||||
|
onChange={(v) => setProject('name', v)} />
|
||||||
|
<InlineTextField label="Projekt-Nr."
|
||||||
|
value={draft.project?.number}
|
||||||
|
placeholder="z.B. 2026-014"
|
||||||
|
width={140}
|
||||||
|
onChange={(v) => setProject('number', v)} />
|
||||||
|
<TextareaField label="Adresse"
|
||||||
|
value={draft.project?.address}
|
||||||
|
placeholder="Strasse, PLZ Ort"
|
||||||
|
rows={2}
|
||||||
|
onChange={(v) => setProject('address', v)} />
|
||||||
|
<InlineTextField label="Bauherrschaft"
|
||||||
|
value={draft.project?.bauherr}
|
||||||
|
onChange={(v) => setProject('bauherr', v)} />
|
||||||
|
<InlineTextField label="Architekt:in"
|
||||||
|
value={draft.project?.architekt}
|
||||||
|
onChange={(v) => setProject('architekt', v)} />
|
||||||
|
<InlineNumberField label="EG-Nullpunkt m.ü.M"
|
||||||
|
value={draft.project?.projectZeroMum ?? 0.0}
|
||||||
|
step={0.01} suffix="m"
|
||||||
|
onChange={(v) => setProject('projectZeroMum', v || 0.0)}
|
||||||
|
hint="Höhe von Rhino z=0 (= EG OKFF) über Meeresspiegel. Wird vom Swisstopo-Terrain-Import zur Kalibrierung verwendet." />
|
||||||
|
<TextareaField label="Notizen"
|
||||||
|
value={draft.project?.notes}
|
||||||
|
rows={2}
|
||||||
|
onChange={(v) => setProject('notes', v)} />
|
||||||
|
</DetailSection>
|
||||||
<div style={{ fontSize: 10, color: 'var(--text-muted)',
|
<div style={{ fontSize: 10, color: 'var(--text-muted)',
|
||||||
padding: '6px 0 10px', lineHeight: 1.5 }}>
|
padding: '6px 0 10px', lineHeight: 1.5 }}>
|
||||||
Voreinstellungen fuer neue Elemente. Pro-Element editierte
|
Voreinstellungen fuer neue Elemente. Pro-Element editierte
|
||||||
|
|||||||
Reference in New Issue
Block a user