import { useState } from 'react' import Icon from './Icon' function Field({ label, hint, children }) { return (
{label}
{children}
{hint && ( {hint} )}
) } function SectionLabel({ children }) { return (
{children}
) } export default function EbenenSettingsDialog({ ebene, hatchPatterns = ['Solid'], onSave, onClose, embedded = false, pickerEbenen = null, pickerSelected = null, onPickEbene = null, }) { const [draft, setDraft] = useState({ ...ebene, fill: { pattern: 'None', source: 'layer', color: null, scale: 1.0, rotation: 0, lw: null, ...(ebene.fill || {}), }, // Section Style: was Rhino bei Clipping-Plane-Schnitten anzeigt. // Spiegelt das native Section-Style-Dialog (Options → Layer → Section Style). section: { hatchPattern: 'None', // None / Solid / hatchColor: null, // null = ByObject (Layer-Farbe) hatchRotation: 0, hatchScale: 1.0, background: 'viewport', // viewport / object boundaryShow: true, boundaryLinetype: 'byLayer', boundaryWidthScale: 1.0, boundaryColor: null, // null = ByObject sectionOpenObjects: true, ...(ebene.section || {}), }, }) const set = (patch) => setDraft({ ...draft, ...patch }) const setFill = (patch) => setDraft({ ...draft, fill: { ...draft.fill, ...patch } }) const setSection = (patch) => setDraft({ ...draft, section: { ...draft.section, ...patch } }) const fill = draft.fill const isFilled = fill.pattern !== 'None' const isPattern = isFilled && fill.pattern !== 'Solid' const fillFromLayer = fill.source === 'layer' const previewColor = (fillFromLayer || !fill.color) ? draft.color : fill.color const sec = draft.section const secHatched = sec.hatchPattern !== 'None' const secHatchPat = secHatched && sec.hatchPattern !== 'Solid' const secHatchColor = sec.hatchColor || draft.color const secBoundColor = sec.boundaryColor || draft.color // Pattern-Optionen: None + Solid + Patterns const patternOptions = [ 'None', 'Solid', ...hatchPatterns.filter(p => p !== 'Solid' && p !== 'None'), ] const wrapperStyle = embedded ? { width: '100%', height: '100%', background: 'var(--bg-dialog)', display: 'flex', flexDirection: 'column', overflow: 'hidden', } : { position: 'absolute', inset: 0, zIndex: 150, background: 'var(--bg-overlay)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', paddingTop: 30, } const innerStyle = embedded ? { width: '100%', height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden', } : { background: 'var(--bg-dialog)', border: '1px solid var(--border)', borderRadius: 'var(--r-lg)', boxShadow: 'var(--shadow-3)', width: 'calc(100% - 16px)', maxWidth: 300, display: 'flex', flexDirection: 'column', overflow: 'hidden', maxHeight: 'calc(100vh - 60px)', } return (
{/* Header — embedded zeigt nur das Ebenen-Picker-Dropdown (kein Title + kein Close, dafuer hat das Fenster seine native Title- Bar). Modal-Variante zeigt den klassischen Header. */} {embedded && pickerEbenen ? (
Ebene
) : !embedded && (
{ebene.code} — {ebene.name}
)} {/* Body */}
set({ code: ev.target.value })} maxLength={4} style={{ flex: 1, fontSize: 11, fontFamily: 'var(--font-mono)', fontWeight: 600, minWidth: 0 }} /> set({ name: ev.target.value })} style={{ flex: 1, fontSize: 11, fontWeight: 600, minWidth: 0 }} /> set({ color: ev.target.value })} style={{ width: 32, height: 22, padding: 0, border: '1px solid var(--border)', borderRadius: 'var(--r)', cursor: 'pointer', background: 'transparent', }} /> set({ color: ev.target.value })} style={{ flex: 1, fontSize: 10, fontFamily: 'var(--font-mono)', minWidth: 0 }} /> set({ lw: parseFloat(ev.target.value) || draft.lw })} style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }} /> Schraffur „Nach Ebene" Wird auf neue geschlossene Kurven angewandt die auf dieser Ebene gezeichnet werden. {isFilled && ( <> setFill({ color: ev.target.value, source: 'object' })} style={{ width: 32, height: 22, padding: 0, border: '1px solid var(--border)', borderRadius: 'var(--r)', cursor: fillFromLayer ? 'default' : 'pointer', background: 'transparent', opacity: fillFromLayer ? 0.6 : 1, }} /> {previewColor} {isPattern && ( <> setFill({ scale: parseFloat(ev.target.value) || 1.0 })} style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }} /> setFill({ rotation: parseFloat(ev.target.value) || 0 })} style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }} /> )} { const v = ev.target.value if (v === '' || v === null) setFill({ lw: null }) else { const f = parseFloat(v) if (!isNaN(f) && f >= 0) setFill({ lw: f }) } }} style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }} /> )} {/* === SECTION STYLE (Clipping-Plane-Schnitt) === */} Section Style (Clipping-Schnitt) Wird gezeigt wenn ein Objekt auf dieser Ebene von einer Clipping-Plane geschnitten wird. {secHatched && ( <> setSection({ hatchColor: ev.target.value })} style={{ width: 32, height: 22, padding: 0, border: '1px solid var(--border)', borderRadius: 'var(--r)', cursor: 'pointer', background: 'transparent', }} /> {sec.hatchColor || 'By Object'} {sec.hatchColor && ( )} {secHatchPat && ( <> setSection({ hatchScale: parseFloat(ev.target.value) || 1.0 })} style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }} /> setSection({ hatchRotation: parseFloat(ev.target.value) || 0 })} style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }} /> )} )} {/* --- Boundary --- */}
Boundary (Schnittkante)
setSection({ boundaryShow: ev.target.checked })} style={{ marginRight: 6 }} /> {sec.boundaryShow ? 'Schnittkante wird gezeichnet' : 'Schnittkante unsichtbar'} {sec.boundaryShow && ( <> setSection({ boundaryColor: ev.target.value })} style={{ width: 32, height: 22, padding: 0, border: '1px solid var(--border)', borderRadius: 'var(--r)', cursor: 'pointer', background: 'transparent', }} /> {sec.boundaryColor || 'By Object'} {sec.boundaryColor && ( )} setSection({ boundaryWidthScale: parseFloat(ev.target.value) || 1.0 })} style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }} /> )} setSection({ sectionOpenObjects: ev.target.checked })} style={{ marginRight: 6 }} /> Auch nicht-geschlossene Geometrie schneiden
{/* Footer */}
) }