2D-Plan-Renderer auf WebGL2 (GPU) + akkumulierter Funktionsstand

Neuer GPU-Renderer fuer den Grundriss (src/plan/glPlan/): Earcut-Tessellierung
(konkav-faehig), gehrte Linienzuege (Miter), echte Papier-mm-Strichbreiten im
Massstab (repliziert den SVG-printStrokeVb-Pfad), Hybrid mit scharfem SVG-Text-
Overlay. GPU ist der Standardpfad; der SVG-Renderer bleibt automatischer Fallback,
falls WebGL2/Shader nicht verfuegbar sind. Imperativer Pan (rAF + CSS-transform)
fuer fluessige Interaktion ohne React-Re-Render je Frame.

Enthaelt zudem den bisher nicht committeten Arbeitsstand des Browser-BIM
(Oeffnungen, Treppen, Raeume, Decken, DXF-Export, Materialbibliothek, Kontext-
Import, Tauri-Compute-Boundary-PoC).
This commit is contained in:
2026-07-02 00:12:39 +02:00
parent cfe5249440
commit 3d2d4d6321
184 changed files with 29421 additions and 669 deletions
+51 -13
View File
@@ -13,14 +13,20 @@ export interface CommandLineOption {
id: string;
labelKey: string;
value?: string;
/** Aktuell gewählte Option (hervorgehoben)? */
active?: boolean;
/** Tastatur-Kürzel (z. B. „U"), wirkt AUCH bei fokussiertem Eingabefeld. */
key?: string;
}
/** Ein Zahlenfeld des Tab-Feld-Zyklus (§2.7). */
export interface CommandLineField {
id: string;
labelKey: string;
/** Gelockter Wert (oder null = folgt der Maus). */
/** Gelockter ODER live (maus-folgender) Wert; null = (noch) keiner. */
value: number | null;
/** Ist der Wert gelockt (getippt) statt live (folgt der Maus)? */
locked: boolean;
/** Aktuelles Tab-Ziel? */
active: boolean;
}
@@ -73,6 +79,25 @@ export const CommandLine = forwardRef<CommandLineHandle, CommandLineProps>(
};
const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
// Options-Kürzel (z. B. U/I/O/P der Kopiermodi): auch bei fokussiertem Feld
// aktiv. Ein einzelner Buchstabe, der einer Option zugeordnet ist, löst die
// Option aus statt in die Zahleneingabe zu fallen (Ziffern bleiben Eingabe).
if (
active &&
e.key.length === 1 &&
!e.ctrlKey &&
!e.metaKey &&
!e.altKey
) {
const opt = options.find(
(o) => o.key && o.key.toLowerCase() === e.key.toLowerCase(),
);
if (opt) {
e.preventDefault();
onOption(opt.id);
return;
}
}
// Tasten NICHT zum globalen App-Handler durchreichen (eigener Eingabefokus).
if (e.key === "Enter") {
e.preventDefault();
@@ -130,12 +155,13 @@ export const CommandLine = forwardRef<CommandLineHandle, CommandLineProps>(
<button
key={o.id}
type="button"
className="cmdline-option"
className={`cmdline-option${o.active ? " active" : ""}`}
onMouseDown={(e) => {
e.preventDefault();
onOption(o.id);
}}
>
{o.key && <span className="cmdline-option-key">{o.key}</span>}
{t(o.labelKey as TranslationKey)}
{o.value != null && <span className="cmdline-option-val">={o.value}</span>}
</button>
@@ -144,18 +170,30 @@ export const CommandLine = forwardRef<CommandLineHandle, CommandLineProps>(
)}
{hasFields && (
<span className="cmdline-fields">
{fields.map((f) => (
<span
key={f.id}
className={`cmdline-field${f.active ? " active" : ""}`}
title={t(f.labelKey as TranslationKey)}
>
<span className="cmdline-field-label">{t(f.labelKey as TranslationKey)}</span>
<span className="cmdline-field-val">
{f.value == null ? "—" : Number(f.value.toFixed(3)).toString()}
{fields.map((f) => {
// Winkelfeld in Grad, sonst Längen in Metern. Gelockte Werte fest
// (mehr Nachkommastellen), live (maus-folgende) Werte gerundet.
const isAngle = f.id === "angle";
const digits = isAngle ? (f.locked ? 1 : 0) : f.locked ? 3 : 2;
const unit = isAngle ? "°" : " m";
const text =
f.value == null
? "—"
: `${Number(f.value.toFixed(digits)).toString()}${unit}`;
return (
<span
key={f.id}
className={
`cmdline-field${f.active ? " active" : ""}` +
(f.locked ? " locked" : f.value != null ? " live" : "")
}
title={t(f.labelKey as TranslationKey)}
>
<span className="cmdline-field-label">{t(f.labelKey as TranslationKey)}</span>
<span className="cmdline-field-val">{text}</span>
</span>
</span>
))}
);
})}
</span>
)}
<input