Schnitt/Ansicht: SectionOutput aus render3d an die Zeichenebenen anbinden

- render3d/web.rs: GPU-freies WASM-Binding cut_section_json(model_json, p, n)
  → JSON {cutPolygons, visibleEdges, hiddenEdges} in (u,v)-Metern.
- src/plan/toSection.ts (neu): SectionOutput-Typen, sectionPlaneFromLevel()
  leitet die Schnittebene aus linePoints/directionSign ab, computeSection()
  flacht über projectToModel3d ab und ruft das Binding.
- src/engine/engine3d.ts (neu): geteilter memoisierter pkg3d-Loader, damit
  3D-Viewport und Schnittpfad EINE mod.default()-Init teilen.
- generatePlan.ts: generateSectionPlan() übersetzt SectionOutput in
  bestehende Primitive (Cut = polygon mit Poché-Schraffur, sichtbare Kante =
  line solid, verdeckte Kante = line dashed) — keine neue Primitive-Art nötig.
- App.tsx: SectionPlanView ersetzt den Stub für Ebenen vom Typ Schnitt/Ansicht;
  rendert dasselbe PlanView, mit lokalisierten Hinweisen (lädt/keine Linie/Fehler).
- i18n de/en: section.*-Schlüssel.
- Öffnungen erscheinen korrekt (Brüstung/Sturz-Teilrechtecke) über die
  projectToModel3d-Teilkörper-Zerlegung.

Gates: tsc -b 0, build grün (WASM = eigener Lazy-Chunk), render3d cargo test 29/29,
wasm32 --features web check grün.
This commit is contained in:
2026-07-03 18:37:49 +02:00
parent 3402e6ac96
commit 056ca2c288
8 changed files with 514 additions and 30 deletions
+2 -18
View File
@@ -19,6 +19,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import type { Project } from "../model/types";
import { projectToModel3d } from "../plan/toWalls3d";
import { loadEngine3d } from "../engine/engine3d";
/** Fertige Kamera für einen Frame (world-Meter, Y-up — wie render3d::types::Camera). */
export interface Camera3d {
@@ -54,23 +55,6 @@ interface WebModelRendererLike {
render(): void;
}
/**
* WASM-Modul EINMAL laden + initialisieren (memoisiert). `mod.default()` darf
* nur einmal laufen — ein zweiter Init (React-StrictMode-Doppelmount) verwirft
* die Closures des ersten („closure invoked recursively or after being dropped").
*/
let enginePromise: Promise<any> | null = null;
function loadEngine(): Promise<any> {
if (!enginePromise) {
enginePromise = (async () => {
const mod: any = await import("../engine/pkg3d/render3d.js");
await mod.default(); // lädt render3d_bg.wasm
return mod;
})();
}
return enginePromise;
}
/**
* Renderer je Canvas EINMAL erzeugen (memoisiert). Ein Canvas kann nur EINEN
* WebGPU-Kontext tragen; ein zweiter `WebModelRenderer.new` auf demselben
@@ -82,7 +66,7 @@ function getRenderer(canvas: HTMLCanvasElement): Promise<WebModelRendererLike> {
let p = rendererCache.get(canvas);
if (!p) {
p = (async () => {
const mod = await loadEngine();
const mod = await loadEngine3d();
// Canvas VOR dem Surface-Erzeugen auf die Zielpixel (DPR) bringen.
const rect = canvas.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;