import { useEffect, useState, useRef } from 'react' import Icon from './components/Icon' import { BarToggle, BarButton } from './components/BarControls' import { onMessage, notifyReady, setRefPoint, setCoordSystem, setDimPosition, setDimDimension, setDimRotationZ, setCircleRadius, setLineLength, setRectangleDims, } from './lib/rhinoBridge' // ---- Helpers -------------------------------------------------------------- const REF_CODES = ['min', 'mid', 'max'] // row/col mapping const REF_LABELS = { min: 'min', mid: 'mid', max: 'max' } function fmtNum(v) { if (v == null) return '' if (typeof v !== 'number') return String(v) // 4 Nachkommastellen, aber unnoetige Nullen weg return Number(v.toFixed(4)).toString() } // Input-Komponente: zeigt formatierten Wert, sendet onCommit bei Enter/Blur. // Verhindert Update waehrend des Tippens, damit der Cursor nicht springt. function NumInput({ value, onCommit, disabled, suffix, width }) { const [text, setText] = useState(fmtNum(value)) const [focused, setFocused] = useState(false) useEffect(() => { if (!focused) setText(fmtNum(value)) }, [value, focused]) const commit = () => { const v = parseFloat(text.replace(',', '.')) if (!isNaN(v) && v !== value) onCommit(v) else setText(fmtNum(value)) // ungueltig → zurueck auf alten Wert } return (
setText(e.target.value)} onFocus={(e) => { setFocused(true); e.target.select() }} onBlur={() => { setFocused(false); commit() }} onKeyDown={(e) => { if (e.key === 'Enter') { e.target.blur() } else if (e.key === 'Escape') { setText(fmtNum(value)); e.target.blur() } }} style={{ flex: 1, width: '100%', fontFamily: 'DM Mono, monospace', fontSize: 11, textAlign: 'right' }} /> {suffix && {suffix}}
) } // 9-Punkt-Referenzpunkt-Selektor: sichtbarer BBox-Rahmen, Kreise auf den // Eckpunkten / Kantenmitten / Zentrum. function RefPointGrid({ ref, onChange }) { const SIZE = 28 // Aussenkanten-Quadrat (px) const DOT = 6 // Kreis-Durchmesser (px) const pct = (c) => c === 'min' ? '0%' : c === 'max' ? '100%' : '50%' return (
{REF_CODES.map(yc => REF_CODES.map(xc => { const active = ref.x === xc && ref.y === yc const topPct = yc === 'max' ? '0%' : yc === 'min' ? '100%' : '50%' return (
) } // Z-Referenz-Selektor (Bottom / Mid / Top) — kompakt, nur Icons. function RefZSelector({ z, onChange }) { return (
{[ { code: 'max', icon: 'vertical_align_top', title: 'Z = Top' }, { code: 'mid', icon: 'vertical_align_center', title: 'Z = Mid' }, { code: 'min', icon: 'vertical_align_bottom', title: 'Z = Bottom' }, ].map(opt => ( onChange(opt.code)} title={opt.title} /> ))}
) } // Inline-Label vor einem Input — minimal, knackig function Field({ label, children, style }) { return (
{label} {children}
) } // ---- Hauptkomponente ------------------------------------------------------ export default function DimensionenApp() { const [state, setState] = useState({ selection: { count: 0, type: 'none' }, refPoint: { x: 'min', y: 'min', z: 'mid' }, coordSystem: 'world', position: null, dimensions: null, shape: null, planeName: 'Welt', }) const [rotationDelta, setRotationDelta] = useState(0) useEffect(() => { onMessage('STATE', (s) => setState((prev) => ({ ...prev, ...s }))) notifyReady() }, []) const sel = state.selection || { count: 0, type: 'none' } const ref = state.refPoint || { x: 'min', y: 'min', z: 'mid' } const pos = state.position const dims = state.dimensions const shape = state.shape const hasSelection = sel.count > 0 && pos != null const onRefChange = (next) => setRefPoint(next.x, next.y, next.z) const onCoordChange = (mode) => setCoordSystem(mode) // Selektions-Beschriftung const selLabel = () => { if (sel.count === 0) return 'Keine Selektion' if (sel.count === 1) { const map = { curve: 'Kurve', brep: 'Brep', mesh: 'Mesh', extrusion: 'Extrusion', block: 'Block', point: 'Punkt', text: 'Text', other: 'Objekt', } let base = map[sel.type] || '1 Objekt' if (shape?.type === 'circle') base = 'Kreis' if (shape?.type === 'rectangle') base = 'Rechteck' if (shape?.type === 'line') base = 'Linie' return '1 ' + base } return `${sel.count} Objekte` } return (
{/* Header: Selektions-Info + World/CPlane */}
{selLabel()}
onCoordChange('world')} title="Weltkoordinaten" /> onCoordChange('cplane')} title="Aktive Konstruktionsebene" />
{!hasSelection ? (
Keine Selektion.
In Rhino ein oder mehrere Objekte auswählen.
) : ( <> {/* Referenzpunkt */}
Ref
onRefChange({ ...ref, z })} />
{/* Position + BBox nebeneinander */}
Position {state.planeName}
setDimPosition('x', v)} /> setDimPosition('y', v)} /> setDimPosition('z', v)} />
BBox setDimDimension('width', v)} /> setDimDimension('depth', v)} /> setDimDimension('height', v)} />
{/* Shape-spezifisch */} {shape && (
{shape.type === 'circle' && 'Kreis'} {shape.type === 'rectangle' && 'Rechteck'} {shape.type === 'line' && 'Linie'} {shape.type === 'circle' && ( setCircleRadius(v)} /> )} {shape.type === 'rectangle' && (
setRectangleDims(v, shape.height)} /> setRectangleDims(shape.width, v)} />
)} {shape.type === 'line' && (
setLineLength(v)} /> {}} disabled suffix="°" />
)}
)} {/* Rotation */}
Drehen
{ if (rotationDelta) setDimRotationZ(rotationDelta) }} disabled={!rotationDelta} title="Selektion um Z-Achse der aktiven Plane drehen" />
setDimRotationZ(-90)} title="90° gegen den Uhrzeigersinn" /> setDimRotationZ(90)} title="90° im Uhrzeigersinn" />
)}
) }