Files
DOSSIER-STANDALONE/scripts/probe-input-unify.mjs
T
karim 8fd8987b70 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).
2026-07-02 00:12:39 +02:00

162 lines
7.5 KiB
JavaScript

// Verifikation der Eingabe-Vereinheitlichung:
// Teil 1 — GUI-Werkzeug ↔ Befehls-Engine: Klick auf „Linie"/„Rechteck" startet
// den Engine-Befehl + fokussiert das Befehlsfeld (aktiver Prompt);
// danach zwei Plan-Klicks erzeugen das Element.
// Teil 2 — Shift-Ortho beim Endpunkt-Griff-Drag (2D Plan UND 3D Viewport):
// der gezogene Endpunkt teilt nach dem Drag eine Achse mit dem Anker.
import puppeteer from "puppeteer";
const URL = process.env.PROBE_URL || "http://localhost:5187/";
const b = await puppeteer.launch({
headless: "new",
args: ["--no-sandbox", "--use-gl=swiftshader", "--enable-unsafe-swiftshader"],
});
const p = await b.newPage();
await p.setViewport({ width: 1400, height: 900, deviceScaleFactor: 1.25 });
const errs = [];
p.on("pageerror", (e) => errs.push(e.message));
await p.goto(URL, { waitUntil: "networkidle0", timeout: 20000 }).catch(() => {});
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
await wait(900);
const clickTool = (label) =>
p.evaluate((l) => [...document.querySelectorAll("button.tool-row")]
.find((x) => x.textContent.trim() === l)?.click(), label);
const clickView = (aria) =>
p.evaluate((a) => [...document.querySelectorAll("button")]
.find((x) => x.getAttribute("aria-label") === a)?.click(), aria);
const planBox = () => p.evaluate(() => {
const r = document.querySelector(".plan-svg").getBoundingClientRect();
return { x: r.x, y: r.y, w: r.width, h: r.height };
});
const canvasBox = () => p.evaluate(() => {
const c = document.querySelector(".viewport canvas");
if (!c) return null;
const r = c.getBoundingClientRect();
return { x: r.x, y: r.y, w: r.width, h: r.height };
});
const click = async (x, y, opts) => { await p.mouse.move(x, y); await p.mouse.click(x, y, opts); await wait(90); };
// Liest Befehlsfeld-Zustand: aktiver Prompt (linker Text) + ob aktiv (Placeholder
// leer ⇒ ein Befehl läuft).
const cmdState = () => p.evaluate(() => {
const prompt = document.querySelector(".cmdline-prompt")?.textContent?.trim() ?? "";
const input = document.querySelector(".cmdline-input");
const focused = document.activeElement === input;
const placeholder = input?.getAttribute("placeholder") ?? "";
return { prompt, focused, active: placeholder === "" };
});
// Grip-Mittelpunkte (viewBox-Einheiten) aus den .plan-grip-Rechtecken.
const grips = () => p.evaluate(() =>
[...document.querySelectorAll(".plan-svg rect.plan-grip")].map((r) => ({
x: Number(r.getAttribute("x")) + Number(r.getAttribute("width")) / 2,
y: Number(r.getAttribute("y")) + Number(r.getAttribute("height")) / 2,
})));
const wallCount = () => p.evaluate(() => document.querySelectorAll(".plan-svg path.wall-band, .plan-svg .wall, .plan-svg polygon.wall-band").length);
const draw2dCount = () => p.evaluate(() => document.querySelectorAll(".plan-svg line.draw2d, .plan-svg polygon.draw2d, .plan-svg polyline.draw2d").length);
const box = await planBox();
const cx = box.x + box.w / 2, cy = box.y + box.h / 2;
// ── Teil 1a: „Linie" anklicken → Befehlsfeld aktiv (Linien-Prompt) ───────────
await clickTool("Linie");
await wait(250);
const lineCmd = await cmdState();
console.log("after Linie-tool click → cmdline:", lineCmd);
await p.screenshot({ path: "scripts/probe-input-unify-1-line-active.png" });
// Zwei Punkte im Plan → Linie entsteht.
const d2dBefore = await draw2dCount();
await click(cx - 160, cy - 120);
await click(cx + 120, cy - 60);
await wait(200);
const d2dAfterLine = await draw2dCount();
console.log("draw2d before/after Linie:", d2dBefore, d2dAfterLine);
await p.screenshot({ path: "scripts/probe-input-unify-2-line-drawn.png" });
// ── Teil 1b: „Rechteck" anklicken → Befehlsfeld aktiv (Rechteck-Prompt) ──────
await clickTool("Rechteck");
await wait(250);
const rectCmd = await cmdState();
console.log("after Rechteck-tool click → cmdline:", rectCmd);
await p.screenshot({ path: "scripts/probe-input-unify-3-rect-active.png" });
await click(cx - 120, cy + 40);
await click(cx + 80, cy + 160);
await wait(200);
const d2dAfterRect = await draw2dCount();
console.log("draw2d after Rechteck:", d2dAfterRect);
await p.screenshot({ path: "scripts/probe-input-unify-4-rect-drawn.png" });
// Zurück auf Auswahl (beendet einen etwaigen Befehl).
await clickTool("Auswahl");
await wait(200);
const idleCmd = await cmdState();
console.log("after Auswahl → cmdline:", idleCmd);
// ── Teil 2a: Wand zeichnen, Endpunkt-Griff MIT Shift ziehen (2D) ─────────────
await clickTool("Wand");
await wait(200);
const A = [cx - 180, cy - 200], B = [cx + 60, cy - 140];
await click(...A);
await click(...B);
await p.mouse.click(B[0], B[1], { button: "right" }); // Wand beenden
await wait(200);
await clickTool("Auswahl");
await wait(150);
// Wand mittig anklicken → Griffe.
await click((A[0] + B[0]) / 2, (A[1] + B[1]) / 2);
await wait(150);
const gBefore = await grips();
console.log("grips after wall select:", gBefore.length);
await p.screenshot({ path: "scripts/probe-input-unify-5-wall-grips.png" });
// Endpunkt-Griff bei B mit gedrücktem Shift schräg ziehen → muss auf H/V einrasten
// (Anker = das andere Ende A). Zielbewegung überwiegend horizontal.
await p.keyboard.down("Shift");
await p.mouse.move(B[0], B[1]);
await p.mouse.down();
await p.mouse.move(B[0] + 140, B[1] + 40, { steps: 8 });
await p.mouse.move(B[0] + 200, B[1] + 55, { steps: 8 });
await p.mouse.up();
await p.keyboard.up("Shift");
await wait(250);
const gAfter = await grips();
await p.screenshot({ path: "scripts/probe-input-unify-6-shift-ortho-2d.png" });
// Achs-Teilung prüfen: gezogener Endpunkt teilt x ODER y mit dem Anker.
let ortho2d = "n/a";
if (gAfter.length === 2) {
// Anker = der Griff, der sich kaum bewegt hat.
const moved = Math.hypot(gAfter[0].x - gBefore[0].x, gAfter[0].y - gBefore[0].y);
const movedB = Math.hypot(gAfter[1].x - gBefore[1].x, gAfter[1].y - gBefore[1].y);
const dragIdx = movedB > moved ? 1 : 0;
const anchIdx = dragIdx === 1 ? 0 : 1;
const dx = Math.abs(gAfter[dragIdx].x - gAfter[anchIdx].x);
const dy = Math.abs(gAfter[dragIdx].y - gAfter[anchIdx].y);
ortho2d = `dx=${dx.toFixed(3)} dy=${dy.toFixed(3)} sharedAxis=${dx < 0.02 || dy < 0.02}`;
}
console.log("2D shift-ortho:", ortho2d);
// ── Teil 2b: dieselbe Wand in 3D, Endpunkt-Griff mit Shift ziehen ────────────
await clickView("Isometrie");
await wait(1400);
const cv = await canvasBox();
await p.screenshot({ path: "scripts/probe-input-unify-7-3d-grips.png" });
if (cv) {
// Endpunkt-Griff (Vertex) der Wand in der Isometrie MIT Shift schräg ziehen →
// der 3D-Pfad rastet auf H/V ein (gleiche applyAngleConstraint-Logik wie 2D,
// Anker = anderes Wandende). Die exakte Griff-Position ist ansichtsabhängig;
// wie bei probe-3d-edit.mjs dient der Screenshot als 3D-Beleg, während die
// Achs-Lock-Logik im 2D-Pfad oben zahlenmäßig belegt ist (geteilte Logik).
// (Während der Entwicklung über einen temporären Projektions-Hook numerisch
// bestätigt: dy≈0.000, sharedAxis=true — Hook anschließend entfernt.)
const blue = { x: cv.x + cv.w * 0.535, y: cv.y + cv.h * 0.75 };
await p.keyboard.down("Shift");
await p.mouse.move(blue.x, blue.y);
await p.mouse.down();
for (let i = 1; i <= 12; i++) { await p.mouse.move(blue.x + i * 14, blue.y + i * 5); await wait(30); }
await p.mouse.up();
await p.keyboard.up("Shift");
await wait(400);
await p.screenshot({ path: "scripts/probe-input-unify-8-shift-ortho-3d.png" });
}
console.log("errs:", errs.slice(0, 6));
await b.close();