Swisstopo Iter 2 + hierarchische Ebenen + 0-Kote m.ü.M
Swisstopo
- swissBUILDINGS3D 3.0 + Variant-Toggle (separated/solid) im Dialog
- Auto-Fallback auf 2.0 wenn 3.0-Tiles ueber 200 MB sind (Stadt-Fall)
- Defensiver Variant-Filter auf 3 Ebenen (Item, Asset, ZIP-Extract) — keine
Doppelimporte mehr
- Auto-Skala korrigiert jetzt die importierten Objekte (×1000) statt die
User-bbox zu schrumpfen — Buildings bleiben in m-Doc-Skala
- merge_grids: XYZ-Tiles werden vor dem Mesh-Bau vereint, kein 1m-Streifen
zwischen Tiles mehr
- Layer-Konsolidierung: Build_*/Roof_*/Wall_*/Floor_* DWG-Source-Layer
werden auf Sub-Sub-Layer unter 81_Swissbuildings/{Build,Roof,Wall,Floor}
gemappt; solid-Variante landet flach direkt auf dem Parent
- 0-Kote m.ü.M (Projekt-Nullpunkt) wird beim Import als Z-Offset angewandt
Hierarchische Ebenen
- dossier_ebenen unterstuetzt jetzt 'children'-Array (rekursiv)
- layer_builder.build_layers rekursiv (Parent + Children unter jedem Geschoss)
- apply_visibility/update_layer_style/set_ebene_visible/set_ebene_locked
walken den Tree (Sub-Sub-Layer mit gleichem Code-Prefix werden mit-gepflegt)
- EbenenManager mit Chevron-Toggle + Indent pro Level + Context-Menue-Item
'Sub-Ebene hinzufuegen'
- rhinoBridge.applyVisibility schickt Children-Tree (nicht nur Top-Level) —
sonst kommen Sub-Toggles nicht beim Backend an
- Visibility-Key in App.jsx rekursiv durch Children — useEffect feuert jetzt
auch bei Sub-Eye-Toggles
0-Kote m.ü.M
- Eingabefeld im Geschoss-Settings-Dialog (projektweit)
- Speicherung als dossier_project_zero_mum in doc.Strings
- Wird im Swisstopo-Import als Z-Offset (m + doc-units) angewandt
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+20
-4
@@ -62,9 +62,18 @@ export default function App() {
|
||||
|
||||
// Sichtbarkeit live anwenden bei Layer-Aenderungen. Zeichnungsebenen-Slice
|
||||
// bleibt leer — Backend mergt mit doc.Strings.
|
||||
// Rekursiv durch Children — sonst feuert das useEffect nicht wenn nur die
|
||||
// Visibility/Lock einer Sub-Ebene geaendert wurde.
|
||||
const visKeyFor = (e) => {
|
||||
const own = `${e.code}:${e.visible !== false ? 1 : 0}:${e.locked === true ? 1 : 0}`
|
||||
const kids = Array.isArray(e.children) && e.children.length
|
||||
? '(' + e.children.map(visKeyFor).join(',') + ')'
|
||||
: ''
|
||||
return own + kids
|
||||
}
|
||||
const visibilityKey = useMemo(() => (
|
||||
activeCode + '|' + eMode + '|' +
|
||||
ebenen.map(e => `${e.code}:${e.visible !== false ? 1 : 0}:${e.locked === true ? 1 : 0}`).join(',')
|
||||
ebenen.map(visKeyFor).join(',')
|
||||
), [activeCode, eMode, ebenen])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -73,17 +82,24 @@ export default function App() {
|
||||
}, [visibilityKey])
|
||||
|
||||
// Auto-Apply bei strukturellen Aenderungen (name, fill) — wieder nur unsere
|
||||
// Slice, Backend mergt.
|
||||
// Slice, Backend mergt. Rekursiv durch Children.
|
||||
const fillSig = (e) => {
|
||||
const f = e.fill
|
||||
if (!f || !f.pattern || f.pattern === 'None') return ''
|
||||
return [f.pattern, f.source || 'layer', f.color || '', f.scale ?? 1, f.rotation ?? 0].join('|')
|
||||
}
|
||||
const structKeyFor = (e) => {
|
||||
const own = `${e.code}:${e.name}:${fillSig(e)}`
|
||||
const kids = Array.isArray(e.children) && e.children.length
|
||||
? '(' + e.children.map(structKeyFor).join(',') + ')'
|
||||
: ''
|
||||
return own + kids
|
||||
}
|
||||
const structureKey = useMemo(() => (
|
||||
ebenen.map(e => `${e.code}:${e.name}:${fillSig(e)}`).join(',')
|
||||
ebenen.map(structKeyFor).join(',')
|
||||
), [ebenen])
|
||||
const appliedStructureKey = useMemo(() => (
|
||||
appliedE.map(e => `${e.code}:${e.name}:${fillSig(e)}`).join(',')
|
||||
appliedE.map(structKeyFor).join(',')
|
||||
), [appliedE])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
+16
-1
@@ -57,6 +57,7 @@ export default function SwisstopoApp() {
|
||||
// Optionen
|
||||
const [radius, setRadius] = useState(100)
|
||||
const [getBuild, setGetBuild] = useState(true)
|
||||
const [buildVariant, setBuildVariant] = useState('separated')
|
||||
const [getTerrain, setGetTerrain] = useState(false)
|
||||
const [getOrtho, setGetOrtho] = useState(false)
|
||||
const [shift, setShift] = useState(true)
|
||||
@@ -141,6 +142,7 @@ export default function SwisstopoApp() {
|
||||
replaceExisting,
|
||||
clipToBbox,
|
||||
terrainResolution: terrainRes,
|
||||
buildVariant,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -235,9 +237,22 @@ export default function SwisstopoApp() {
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 6,
|
||||
fontSize: 11, cursor: 'pointer' }}>
|
||||
<input type="checkbox" checked={getBuild} onChange={(e) => setGetBuild(e.target.checked)} />
|
||||
<Icon name="location_city" size={13} /> Bestand-Gebäude (swissBUILDINGS3D, DWG)
|
||||
<Icon name="location_city" size={13} /> Bestand-Gebäude (swissBUILDINGS3D 3.0, DWG)
|
||||
</label>
|
||||
</Field>
|
||||
{getBuild && (
|
||||
<Field label="GEBÄUDE-VARIANTE"
|
||||
hint="Solid: ein geschlossenes Solid pro Gebäude (klein, schnell). Separated: Dach/Fassade/Wand als separate Objekte (mehr Detail, ermoeglicht z.B. Dach auszublenden).">
|
||||
<Radio
|
||||
value={buildVariant}
|
||||
options={[
|
||||
{ value: 'separated', label: 'Separated (Dach/Fassade getrennt)' },
|
||||
{ value: 'solid', label: 'Solid (ein Volumen pro Gebäude)' },
|
||||
]}
|
||||
onChange={setBuildVariant}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
<Field label="">
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 6,
|
||||
fontSize: 11, cursor: 'pointer' }}>
|
||||
|
||||
@@ -145,7 +145,69 @@ function EditableText({ value, onCommit, style, fontWeight, fontSize, autoEditTr
|
||||
)
|
||||
}
|
||||
|
||||
function EbeneRow({ e, active, mode, onClick, onContextMenu, onToggleVisible, onToggleLock, onColorChange, onLwChange, onNameChange, onCodeChange, onDelete, autoEditCode, autoEditName, rowRef }) {
|
||||
// --- Tree-Helper -----------------------------------------------------------
|
||||
// Rekursive Updates: code ist global eindeutig (Children duerfen keinen
|
||||
// bestehenden Top-Level Code haben). Helper finden/aendern den passenden
|
||||
// Eintrag irgendwo im Tree.
|
||||
function _updateInTree(ebenen, code, patch) {
|
||||
return ebenen.map(e => {
|
||||
if (e.code === code) return { ...e, ...patch }
|
||||
if (Array.isArray(e.children) && e.children.length) {
|
||||
return { ...e, children: _updateInTree(e.children, code, patch) }
|
||||
}
|
||||
return e
|
||||
})
|
||||
}
|
||||
|
||||
function _removeFromTree(ebenen, code) {
|
||||
const out = []
|
||||
for (const e of ebenen) {
|
||||
if (e.code === code) continue
|
||||
if (Array.isArray(e.children) && e.children.length) {
|
||||
out.push({ ...e, children: _removeFromTree(e.children, code) })
|
||||
} else {
|
||||
out.push(e)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function _addChildInTree(ebenen, parentCode, child) {
|
||||
return ebenen.map(e => {
|
||||
if (e.code === parentCode) {
|
||||
const kids = Array.isArray(e.children) ? e.children : []
|
||||
return { ...e, children: [...kids, child] }
|
||||
}
|
||||
if (Array.isArray(e.children) && e.children.length) {
|
||||
return { ...e, children: _addChildInTree(e.children, parentCode, child) }
|
||||
}
|
||||
return e
|
||||
})
|
||||
}
|
||||
|
||||
function _findInTree(ebenen, code) {
|
||||
for (const e of ebenen) {
|
||||
if (e.code === code) return e
|
||||
if (Array.isArray(e.children) && e.children.length) {
|
||||
const f = _findInTree(e.children, code)
|
||||
if (f) return f
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function _allCodes(ebenen) {
|
||||
const out = []
|
||||
for (const e of ebenen) {
|
||||
out.push(e.code)
|
||||
if (Array.isArray(e.children) && e.children.length) {
|
||||
out.push(..._allCodes(e.children))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function EbeneRow({ e, depth, hasChildren, expanded, onToggleExpand, active, mode, onClick, onContextMenu, onToggleVisible, onToggleLock, onColorChange, onLwChange, onNameChange, onCodeChange, onDelete, autoEditCode, autoEditName, rowRef }) {
|
||||
// Auge zeigt den Eye-State (User-Intention) — auch fuer die aktive Ebene.
|
||||
// So sieht man auf einen Blick ob sie "normalerweise" sichtbar waere.
|
||||
// Aktive Ebene rendert Rhino zwar immer sichtbar, das visible-Flag bleibt
|
||||
@@ -160,6 +222,7 @@ function EbeneRow({ e, active, mode, onClick, onContextMenu, onToggleVisible, on
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: 5,
|
||||
padding: '3px 12px',
|
||||
paddingLeft: 12 + (depth || 0) * 14,
|
||||
margin: active ? '1px 6px' : '0',
|
||||
background: active ? 'var(--active-dim)'
|
||||
: (e.visible !== false) ? 'var(--bg-item)'
|
||||
@@ -174,6 +237,16 @@ function EbeneRow({ e, active, mode, onClick, onContextMenu, onToggleVisible, on
|
||||
userSelect: 'none',
|
||||
}}
|
||||
>
|
||||
{hasChildren ? (
|
||||
<button
|
||||
className="btn-icon-xs"
|
||||
onClick={(ev) => { ev.stopPropagation(); onToggleExpand() }}
|
||||
title={expanded ? 'Einklappen' : 'Aufklappen'}
|
||||
style={{ width: 14, height: 14 }}
|
||||
><Icon name={expanded ? 'expand_more' : 'chevron_right'} size={12} /></button>
|
||||
) : (
|
||||
<span style={{ width: 14, flexShrink: 0 }} />
|
||||
)}
|
||||
{eyeShown ? (
|
||||
<button
|
||||
className={`btn-icon-sm ${e.visible !== false ? 'is-on' : ''}`}
|
||||
@@ -262,6 +335,7 @@ export default function EbenenManager({
|
||||
const [ctxMenu, setCtxMenu] = useState(null) // { x, y, code }
|
||||
const [clipboard, setClipboard] = useState(null) // { color, lw }
|
||||
const [autoEdit, setAutoEdit] = useState(null) // { code, field, token }
|
||||
const [expanded, setExpanded] = useState({}) // { code: true }
|
||||
// Settings-Dialog laeuft jetzt in einem echten Rhino-Fenster (Satellite-
|
||||
// Window via Eto.Form + WebView). State hier nicht mehr noetig.
|
||||
|
||||
@@ -279,9 +353,9 @@ export default function EbenenManager({
|
||||
else { setSortBy(key); setSortDir('asc') }
|
||||
}
|
||||
|
||||
const sortedEbenen = useMemo(() => {
|
||||
const arr = [...ebenen]
|
||||
arr.sort((a, b) => {
|
||||
const sortByCurrent = (arr) => {
|
||||
const sorted = [...arr]
|
||||
sorted.sort((a, b) => {
|
||||
let cmp = 0
|
||||
if (sortBy === 'code') {
|
||||
const ca = parseInt(a.code, 10), cb = parseInt(b.code, 10)
|
||||
@@ -293,19 +367,24 @@ export default function EbenenManager({
|
||||
}
|
||||
return sortDir === 'desc' ? -cmp : cmp
|
||||
})
|
||||
return arr
|
||||
}, [ebenen, sortBy, sortDir])
|
||||
return sorted
|
||||
}
|
||||
|
||||
// Sort wirkt innerhalb jeder Ebene des Baums — Children behalten ihre
|
||||
// Beziehung zum Parent, werden aber unter sich sortiert.
|
||||
const sortedEbenen = useMemo(() => sortByCurrent(ebenen),
|
||||
[ebenen, sortBy, sortDir])
|
||||
|
||||
const updateByCode = (code, patch) => {
|
||||
onChange(ebenen.map(e => e.code === code ? { ...e, ...patch } : e))
|
||||
onChange(_updateInTree(ebenen, code, patch))
|
||||
}
|
||||
|
||||
const handleToggleVisible = (code) => {
|
||||
const cur = ebenen.find(e => e.code === code)
|
||||
const cur = _findInTree(ebenen, code)
|
||||
if (cur) updateByCode(code, { visible: !(cur.visible !== false) })
|
||||
}
|
||||
const handleToggleLock = (code) => {
|
||||
const cur = ebenen.find(e => e.code === code)
|
||||
const cur = _findInTree(ebenen, code)
|
||||
if (cur) updateByCode(code, { locked: !cur.locked })
|
||||
}
|
||||
const handleColorChange = (code, color) => {
|
||||
@@ -324,8 +403,9 @@ export default function EbenenManager({
|
||||
}
|
||||
}
|
||||
const handleCodeChange = (oldCode, newCode) => {
|
||||
if (ebenen.some(e => e.code === newCode && e.code !== oldCode)) return
|
||||
onChange(ebenen.map(e => e.code === oldCode ? { ...e, code: newCode } : e))
|
||||
// Code muss global eindeutig sein (sonst gibt es mehrdeutige Layer-Matches)
|
||||
if (_allCodes(ebenen).some(c => c === newCode && c !== oldCode)) return
|
||||
onChange(_updateInTree(ebenen, oldCode, { code: newCode }))
|
||||
// Phase weiterschalten: Code -> Name
|
||||
if (autoEdit && autoEdit.code === oldCode && autoEdit.field === 'code') {
|
||||
setAutoEdit({ code: newCode, field: 'name', token: Date.now() })
|
||||
@@ -339,25 +419,27 @@ export default function EbenenManager({
|
||||
const confirmDelete = (moveToCode) => {
|
||||
const code = deleteTarget
|
||||
deleteEbene(code, moveToCode)
|
||||
onChange(ebenen.filter(e => e.code !== code))
|
||||
onChange(_removeFromTree(ebenen, code))
|
||||
if (activeCode === code) {
|
||||
const next = ebenen.find(e => e.code !== code)
|
||||
const flat = ebenen.flatMap(e =>
|
||||
[e, ...(Array.isArray(e.children) ? e.children : [])])
|
||||
const next = flat.find(e => e.code !== code)
|
||||
if (next) onActiveChange(next.code)
|
||||
}
|
||||
setDeleteTarget(null)
|
||||
}
|
||||
|
||||
const nextFreeAfter = (afterCode) => {
|
||||
// Naechste freie Nummer NACH afterCode (= activeCode). Wenn afterCode
|
||||
// = "20", probiert "21", dann "22", etc. Fallback: max+1.
|
||||
const existing = new Set(ebenen.map(e => e.code))
|
||||
// Naechste freie Nummer NACH afterCode. Codes sind global eindeutig
|
||||
// (auch ueber Children) — also alle Codes als Konfliktraum.
|
||||
const existing = new Set(_allCodes(ebenen))
|
||||
let n = parseInt(afterCode, 10)
|
||||
if (isNaN(n)) n = 49
|
||||
for (let i = 1; i < 100; i++) {
|
||||
for (let i = 1; i < 1000; i++) {
|
||||
const c = String(n + i).padStart(2, '0')
|
||||
if (!existing.has(c)) return c
|
||||
}
|
||||
const codes = ebenen.map(e => parseInt(e.code, 10)).filter(x => !isNaN(x))
|
||||
const codes = _allCodes(ebenen).map(c => parseInt(c, 10)).filter(x => !isNaN(x))
|
||||
return String((codes.length ? Math.max(...codes) : 49) + 1).padStart(2, '0')
|
||||
}
|
||||
|
||||
@@ -379,11 +461,28 @@ export default function EbenenManager({
|
||||
}
|
||||
|
||||
const duplicateEbene = (code) => {
|
||||
const src = ebenen.find(e => e.code === code)
|
||||
const src = _findInTree(ebenen, code)
|
||||
if (!src) return
|
||||
onChange([...ebenen, {
|
||||
...src, code: nextFreeCode(), name: src.name + ' KOPIE',
|
||||
}])
|
||||
const dupCode = nextFreeAfter(code)
|
||||
const dup = { ...src, code: dupCode, name: src.name + ' KOPIE' }
|
||||
// Top-Level Eintrag — wir haengen Duplikat einfach hinten an
|
||||
onChange([...ebenen, dup])
|
||||
}
|
||||
|
||||
const addChild = (parentCode) => {
|
||||
const code = nextFreeAfter(parentCode)
|
||||
const child = {
|
||||
code, name: 'NEU',
|
||||
color: '#888888', lw: 0.18, visible: true, locked: false,
|
||||
}
|
||||
onChange(_addChildInTree(ebenen, parentCode, child))
|
||||
// Parent expanden damit der neue Eintrag sichtbar ist
|
||||
setExpanded(s => ({ ...s, [parentCode]: true }))
|
||||
setAutoEdit({ code, field: 'code', token: Date.now() })
|
||||
}
|
||||
|
||||
const toggleExpand = (code) => {
|
||||
setExpanded(s => ({ ...s, [code]: !s[code] }))
|
||||
}
|
||||
|
||||
const copyProps = (code) => {
|
||||
@@ -405,10 +504,11 @@ export default function EbenenManager({
|
||||
|
||||
const ctxItems = (code) => [
|
||||
{ label: 'Ebeneneinstellungen…', icon: 'settings', onClick: () => {
|
||||
const target = ebenen.find(e => e.code === code)
|
||||
const target = _findInTree(ebenen, code)
|
||||
if (target) openEbenenSettings(target, hatchPatterns)
|
||||
} },
|
||||
{ divider: true },
|
||||
{ label: 'Sub-Ebene hinzufügen…', icon: 'add', onClick: () => addChild(code) },
|
||||
{ label: 'Selektion hierher übertragen', icon: 'move_down', onClick: () => moveSelectionToEbene(code) },
|
||||
{ divider: true },
|
||||
{ label: 'Duplizieren', icon: 'content_copy', onClick: () => duplicateEbene(code) },
|
||||
@@ -490,25 +590,44 @@ export default function EbenenManager({
|
||||
<div style={{ width: 18 }} />
|
||||
</div>
|
||||
|
||||
{sortedEbenen.map(e => (
|
||||
<EbeneRow
|
||||
key={e.code}
|
||||
e={e}
|
||||
active={e.code === activeCode}
|
||||
mode={mode}
|
||||
onClick={() => onActiveChange(e.code)}
|
||||
onContextMenu={(ev) => openContextMenu(ev, e.code)}
|
||||
onToggleVisible={() => handleToggleVisible(e.code)}
|
||||
onToggleLock={() => handleToggleLock(e.code)}
|
||||
onColorChange={(c) => handleColorChange(e.code, c)}
|
||||
onLwChange={(lw) => handleLwChange(e.code, lw)}
|
||||
onNameChange={(n) => handleNameChange(e.code, n)}
|
||||
onCodeChange={(c) => handleCodeChange(e.code, c)}
|
||||
onDelete={() => handleDelete(e.code)}
|
||||
autoEditCode={autoEdit && autoEdit.code === e.code && autoEdit.field === 'code' ? autoEdit.token : null}
|
||||
autoEditName={autoEdit && autoEdit.code === e.code && autoEdit.field === 'name' ? autoEdit.token : null}
|
||||
/>
|
||||
))}
|
||||
{(() => {
|
||||
// Rekursives Rendern: jede Ebene + sortierte Children (falls expanded)
|
||||
const renderRow = (e, depth) => {
|
||||
const kids = Array.isArray(e.children) ? e.children : []
|
||||
const hasChildren = kids.length > 0
|
||||
const isExpanded = !!expanded[e.code]
|
||||
const rows = [
|
||||
<EbeneRow
|
||||
key={e.code}
|
||||
e={e}
|
||||
depth={depth}
|
||||
hasChildren={hasChildren}
|
||||
expanded={isExpanded}
|
||||
onToggleExpand={() => toggleExpand(e.code)}
|
||||
active={e.code === activeCode}
|
||||
mode={mode}
|
||||
onClick={() => onActiveChange(e.code)}
|
||||
onContextMenu={(ev) => openContextMenu(ev, e.code)}
|
||||
onToggleVisible={() => handleToggleVisible(e.code)}
|
||||
onToggleLock={() => handleToggleLock(e.code)}
|
||||
onColorChange={(c) => handleColorChange(e.code, c)}
|
||||
onLwChange={(lw) => handleLwChange(e.code, lw)}
|
||||
onNameChange={(n) => handleNameChange(e.code, n)}
|
||||
onCodeChange={(c) => handleCodeChange(e.code, c)}
|
||||
onDelete={() => handleDelete(e.code)}
|
||||
autoEditCode={autoEdit && autoEdit.code === e.code && autoEdit.field === 'code' ? autoEdit.token : null}
|
||||
autoEditName={autoEdit && autoEdit.code === e.code && autoEdit.field === 'name' ? autoEdit.token : null}
|
||||
/>
|
||||
]
|
||||
if (hasChildren && isExpanded) {
|
||||
for (const child of sortByCurrent(kids)) {
|
||||
rows.push(...renderRow(child, depth + 1))
|
||||
}
|
||||
}
|
||||
return rows
|
||||
}
|
||||
return sortedEbenen.flatMap(e => renderRow(e, 0))
|
||||
})()}
|
||||
|
||||
{ctxMenu && (
|
||||
<ContextMenu
|
||||
|
||||
@@ -245,6 +245,7 @@ export default function GeschossManager({
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Master-Row: Master-Eye links + Master-Lock rechts (analog
|
||||
EbenenManager). */}
|
||||
<div style={{
|
||||
|
||||
@@ -146,6 +146,20 @@ export default function GeschossSettingsDialog({ geschoss, onSave, onClose, embe
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div style={{ height: 1, background: 'var(--border-light)', margin: '6px 0' }} />
|
||||
|
||||
<Field
|
||||
label="0-KOTE m.ü.M (PROJEKTWEIT)"
|
||||
hint="Höhe ü. Meer am OKFF=0. Wird beim Swisstopo-Import als Z-Offset benutzt — alle Real-Welt-Höhen werden um diesen Wert runtergeschoben. Gilt projektweit (nicht nur dieses Geschoss).">
|
||||
<input
|
||||
type="number" step="0.01"
|
||||
value={draft.projectZeroMum ?? 0}
|
||||
onChange={(ev) => set({ projectZeroMum: parseFloat(ev.target.value) || 0 })}
|
||||
style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0,
|
||||
fontFamily: 'var(--font-mono)' }}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
|
||||
+14
-3
@@ -318,9 +318,20 @@ export function applyVisibility(activeZ, zeichnungsebenen, activeCode, ebenen, z
|
||||
visible: z.visible !== false,
|
||||
locked: z.locked === true,
|
||||
}))
|
||||
const slimE = eList.map(e => ({
|
||||
code: e.code, visible: e.visible !== false, locked: e.locked === true,
|
||||
}))
|
||||
// Rekursiv durch Children — sonst landen Sub-Ebenen-Toggles nicht beim
|
||||
// Backend.
|
||||
const slimEbene = (e) => {
|
||||
const out = {
|
||||
code: e.code,
|
||||
visible: e.visible !== false,
|
||||
locked: e.locked === true,
|
||||
}
|
||||
if (Array.isArray(e.children) && e.children.length) {
|
||||
out.children = e.children.map(slimEbene)
|
||||
}
|
||||
return out
|
||||
}
|
||||
const slimE = eList.map(slimEbene)
|
||||
send('SET_VISIBILITY', {
|
||||
activeZ: a.activeZ ? { id: a.activeZ.id } : null,
|
||||
activeCode: a.activeCode,
|
||||
|
||||
Reference in New Issue
Block a user