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:
+123
-49
@@ -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.
|
||||
</span>
|
||||
<select value={simsAus}
|
||||
onChange={(e) => onUpdate({ simsAus: e.target.value })}
|
||||
style={{ flex: 1, fontSize: 11 }}>
|
||||
<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
|
||||
</span>
|
||||
<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>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Glas-Toggle: bei Tueren ersetzt Glas das Tuerblatt (verglaste Tuer) */}
|
||||
|
||||
Reference in New Issue
Block a user