Initial commit — Dossier Rhino 8 Plugin
OpenStudio-Suite Architektur-Plugin fuer Rhino 8 (Mac): - Smart-Elemente: Wand, Decke, Dach (Pult/Sattel/Walm/Mansarde), Oeffnungen (Fenster/Tueren mit Rahmen + Sims + Glas + Fluegel), Treppen (gerade · L · Wendel mit Schrittmass-Validierung) - Live-Previews mit Step-Lines + Soll-Range-Clamping - Bidirektionale Selection-Sync zwischen Source-Linie und Volume - Geschoss-/Ebenen-Verwaltung mit OKFF-Persistenz - Layouts mit PDF-Export - Ausschnitte / Massstab / Override-Regeln - Petrol-Gruen Theme (Rapport-konform) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
import { useState } from 'react'
|
||||
import Section from './Section'
|
||||
import Icon from './Icon'
|
||||
import GeschossDialog from './GeschossDialog'
|
||||
import GeschossSettingsDialog from './GeschossSettingsDialog'
|
||||
|
||||
function GeschossBadge({ name }) {
|
||||
return <span className="chip chip-info">{name}</span>
|
||||
}
|
||||
|
||||
function ZeichnungsebeneRow({ z, active, mode, onClick, onToggleVisible, onSettings }) {
|
||||
// Eye-State auch fuer die aktive Zeichnungsebene anzeigen (User-Intention)
|
||||
const eyeShown = mode !== 'active'
|
||||
const isGeschoss = !!z.isGeschoss
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: 8,
|
||||
padding: '4px 12px',
|
||||
margin: active ? '1px 6px' : '0',
|
||||
background: active ? 'var(--active-dim)' : 'var(--bg-item)',
|
||||
// Pill-Form fuer die aktive Zeichnungsebene
|
||||
borderRadius: active ? 999 : 0,
|
||||
borderLeft: active ? 'none' : '3px solid transparent',
|
||||
borderBottom: active ? 'none' : '1px solid var(--border-light)',
|
||||
boxShadow: active ? 'inset 0 0 0 1px var(--active-light)' : 'none',
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
opacity: (!active && z.visible === false && mode !== 'all') ? 0.45 : 1,
|
||||
}}
|
||||
>
|
||||
<span style={{
|
||||
fontWeight: active ? 700 : 500,
|
||||
fontSize: 12,
|
||||
color: active ? 'var(--active-light)' : 'var(--text-label)',
|
||||
flex: 1,
|
||||
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
||||
}}>{z.name}</span>
|
||||
|
||||
{isGeschoss && (
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>
|
||||
+{(z.okff ?? 0).toFixed(2)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{isGeschoss && <GeschossBadge name={z.name} />}
|
||||
|
||||
{isGeschoss && z.hasClipping && (
|
||||
<Icon name="content_cut" size={12} style={{ color: 'var(--accent)', flexShrink: 0 }} title="Clipping Plane aktiv" />
|
||||
)}
|
||||
|
||||
{eyeShown ? (
|
||||
<button
|
||||
className={`btn-icon-sm ${z.visible !== false ? 'is-on' : ''}`}
|
||||
onClick={(ev) => { ev.stopPropagation(); onToggleVisible() }}
|
||||
title={
|
||||
active
|
||||
? (z.visible !== false
|
||||
? 'Normalerweise sichtbar (aktive Zeichnungsebene wird trotzdem gezeigt)'
|
||||
: 'Normalerweise ausgeblendet — wird nur sichtbar weil aktiv')
|
||||
: (z.visible !== false ? 'Ausblenden' : 'Einblenden')
|
||||
}
|
||||
><Icon name={z.visible !== false ? 'visibility' : 'visibility_off'} size={14} /></button>
|
||||
) : (
|
||||
<span style={{ width: 18, flexShrink: 0 }} />
|
||||
)}
|
||||
|
||||
<button
|
||||
className="btn-icon-xs"
|
||||
onClick={(ev) => { ev.stopPropagation(); onSettings() }}
|
||||
title="Einstellungen"
|
||||
><Icon name="settings" size={12} /></button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const MODES = [
|
||||
{ value: 'all', label: 'Alle anzeigen' },
|
||||
{ value: 'active', label: 'Nur aktive' },
|
||||
{ value: 'grey', label: 'Andere grau' },
|
||||
{ value: 'grey_locked', label: 'Andere grau & gesperrt' },
|
||||
]
|
||||
|
||||
export default function GeschossManager({
|
||||
zeichnungsebenen, activeId, onActiveChange, onChange, recalcOkff,
|
||||
mode, onModeChange,
|
||||
}) {
|
||||
const [dialogOpen, setDialogOpen] = useState(false)
|
||||
const [settingsFor, setSettingsFor] = useState(null) // Geschoss-Objekt oder null
|
||||
|
||||
const sorted = [...zeichnungsebenen].reverse()
|
||||
const gesamthoehe = zeichnungsebenen
|
||||
.filter(z => z.isGeschoss)
|
||||
.reduce((s, z) => s + (z.hoehe ?? 0), 0)
|
||||
|
||||
const addQuick = () => {
|
||||
const newZ = {
|
||||
id: `z_${Date.now()}`,
|
||||
name: `Neu ${zeichnungsebenen.length + 1}`,
|
||||
isGeschoss: false,
|
||||
visible: true,
|
||||
}
|
||||
onChange([...zeichnungsebenen, newZ])
|
||||
}
|
||||
|
||||
const actions = (
|
||||
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
|
||||
<button className="btn-add" onClick={addQuick} title="Zeichnungsebene hinzufügen">
|
||||
<Icon name="add" size={16} />
|
||||
</button>
|
||||
<button className="btn-icon-tonal" onClick={() => setDialogOpen(true)} title="Bearbeiten">
|
||||
<Icon name="edit" size={14} />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
||||
const toggleVisible = (id) => {
|
||||
onChange(zeichnungsebenen.map(z => z.id === id ? { ...z, visible: !(z.visible !== false) } : z))
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Section title="Zeichnungsebenen" badge={zeichnungsebenen.length} action={actions}>
|
||||
<div style={{
|
||||
display: 'flex', flexDirection: 'column', gap: 4,
|
||||
padding: '6px 14px',
|
||||
background: 'var(--bg-section)',
|
||||
borderBottom: '1px solid var(--border-light)',
|
||||
}}>
|
||||
<span className="label-xs">Sichtbarkeit</span>
|
||||
<select value={mode} onChange={ev => onModeChange(ev.target.value)} style={{ width: '100%' }}>
|
||||
{MODES.map(m => (
|
||||
<option key={m.value} value={m.value}>{m.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
||||
padding: '3px 14px',
|
||||
background: 'var(--bg-section)',
|
||||
borderBottom: '1px solid var(--border-light)',
|
||||
}}>
|
||||
<span className="label-xs">Gebäudehöhe</span>
|
||||
<span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-secondary)' }}>
|
||||
{gesamthoehe.toFixed(2)} m
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{sorted.map(z => (
|
||||
<ZeichnungsebeneRow
|
||||
key={z.id}
|
||||
z={z}
|
||||
active={z.id === activeId}
|
||||
mode={mode}
|
||||
onClick={() => onActiveChange(z.id)}
|
||||
onToggleVisible={() => toggleVisible(z.id)}
|
||||
onSettings={() => setSettingsFor(z)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{dialogOpen && (
|
||||
<GeschossDialog
|
||||
zeichnungsebenen={zeichnungsebenen}
|
||||
recalcOkff={recalcOkff}
|
||||
onSave={(updated) => { onChange(updated); setDialogOpen(false) }}
|
||||
onClose={() => setDialogOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{settingsFor && (
|
||||
<GeschossSettingsDialog
|
||||
geschoss={settingsFor}
|
||||
onSave={(updated) => {
|
||||
onChange(zeichnungsebenen.map(z => z.id === updated.id ? updated : z))
|
||||
setSettingsFor(null)
|
||||
}}
|
||||
onClose={() => setSettingsFor(null)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user