Browser-BIM (cad): semantisches Modell, abgeleitete 2D/3D-Sichten, Zeichenwerkzeuge
Standalone-Browser-Port von DOSSIER. Enthaelt das semantische Modell mit Plan-/3D-Ableitung, Zeichen- und Editierwerkzeuge, Rhino-artiges Befehlssystem, dockbares Panel-System, Resource-Manager, DXF/.lin/.pat-Import, i18n (de/en) sowie Projektdokumentation und Probe-Harness.
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
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 });
|
||||
const errs = [];
|
||||
p.on("pageerror", (e) => errs.push(e.message));
|
||||
p.on("console", (m) => { if (m.type() === "error") errs.push("[console] " + m.text()); });
|
||||
await p.goto(URL, { waitUntil: "networkidle0", timeout: 20000 }).catch(() => {});
|
||||
await new Promise((r) => setTimeout(r, 800));
|
||||
|
||||
const clickTab = (label) =>
|
||||
p.evaluate((x) => {
|
||||
const el = [...document.querySelectorAll("button, .tab, [role=tab]")].find(
|
||||
(b) => b.textContent.trim() === x,
|
||||
);
|
||||
el?.click();
|
||||
return !!el;
|
||||
}, label);
|
||||
|
||||
// Object-Info-Tab aktivieren (falls vorhanden).
|
||||
const tabOk = await clickTab("Objekt-Info");
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
|
||||
// Eine Wand im Grundriss anklicken (obere Bandkante eines Wandpolygons).
|
||||
async function selectWall() {
|
||||
const bb = await p.evaluate(() => {
|
||||
const polys = [...document.querySelectorAll(".plan-svg polygon")];
|
||||
if (!polys.length) return null;
|
||||
let minX = 1e9, minY = 1e9, maxX = -1e9, maxY = -1e9;
|
||||
for (const pl of polys) {
|
||||
const r = pl.getBoundingClientRect();
|
||||
minX = Math.min(minX, r.left); minY = Math.min(minY, r.top);
|
||||
maxX = Math.max(maxX, r.right); maxY = Math.max(maxY, r.bottom);
|
||||
}
|
||||
return { minX, minY, maxX, maxY };
|
||||
});
|
||||
if (!bb) return false;
|
||||
const cx = (bb.minX + bb.maxX) / 2;
|
||||
for (const dy of [4, 8, 12, 16, 20]) {
|
||||
await p.mouse.click(cx, bb.minY + dy);
|
||||
await new Promise((r) => setTimeout(r, 120));
|
||||
const k = await p.evaluate(
|
||||
() => document.querySelector(".objinfo-kind")?.textContent || "",
|
||||
);
|
||||
if (/Wand/.test(k)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const selected = await selectWall();
|
||||
|
||||
// (1) Wand-Abschnitt sichtbar?
|
||||
const hasWallSection = await p.evaluate(() =>
|
||||
[...document.querySelectorAll(".objinfo-section-label")].some(
|
||||
(e) => e.textContent.trim() === "Wand",
|
||||
),
|
||||
);
|
||||
const hasRefDropdown = await p.evaluate(
|
||||
() => !!document.querySelector(".objinfo-head-ref"),
|
||||
);
|
||||
await p.screenshot({ path: "scripts/probe-wallattr-1-panel.png" });
|
||||
|
||||
// Plan-Footprint VORHER (bbox aller Wandpolygone).
|
||||
const planBBox = () =>
|
||||
p.evaluate(() => {
|
||||
const polys = [...document.querySelectorAll(".plan-svg polygon")];
|
||||
let minY = 1e9, maxY = -1e9;
|
||||
for (const pl of polys) {
|
||||
const r = pl.getBoundingClientRect();
|
||||
minY = Math.min(minY, r.top); maxY = Math.max(maxY, r.bottom);
|
||||
}
|
||||
return { minY: Math.round(minY), maxY: Math.round(maxY) };
|
||||
});
|
||||
const beforeRef = await planBBox();
|
||||
|
||||
// (2) Referenzlinie auf „Außen (links)" stellen über das Kopf-Dropdown.
|
||||
async function pickDropdown(triggerSel, optionText) {
|
||||
const ok = await p.evaluate((sel) => {
|
||||
const t = document.querySelector(sel);
|
||||
if (!t) return false;
|
||||
t.click();
|
||||
return true;
|
||||
}, triggerSel);
|
||||
if (!ok) return false;
|
||||
await new Promise((r) => setTimeout(r, 150));
|
||||
const picked = await p.evaluate((txt) => {
|
||||
const item = [...document.querySelectorAll(".tb-dd-menu .tb-dd-item")].find(
|
||||
(i) => i.textContent.trim().includes(txt),
|
||||
);
|
||||
item?.click();
|
||||
return !!item;
|
||||
}, optionText);
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
return picked;
|
||||
}
|
||||
|
||||
const refPicked = await pickDropdown(
|
||||
".objinfo-head-ref .tb-dd-trigger",
|
||||
"Außen",
|
||||
);
|
||||
await new Promise((r) => setTimeout(r, 250));
|
||||
const afterRef = await planBBox();
|
||||
await p.screenshot({ path: "scripts/probe-wallattr-2-refline-aussen.png" });
|
||||
|
||||
// (3) OK auf „eigene Höhe" → Z-Wert ändern, dann 3D.
|
||||
// Den OK-Block (zweite objinfo-wall-anchor) auf „Eigene Höhe" schalten.
|
||||
const okCustom = await p.evaluate(() => {
|
||||
const blocks = [...document.querySelectorAll(".objinfo-wall-anchor")];
|
||||
const okBlock = blocks[1];
|
||||
if (!okBlock) return false;
|
||||
const btn = [...okBlock.querySelectorAll(".objinfo-seg-btn")].find(
|
||||
(b) => b.textContent.trim() === "Eigene Höhe",
|
||||
);
|
||||
btn?.click();
|
||||
return !!btn;
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
// Z-Feld setzen (5.0 m) im OK-Block.
|
||||
const heightBefore = await p.evaluate(
|
||||
() =>
|
||||
[...document.querySelectorAll(".objinfo-field")]
|
||||
.find((f) => f.querySelector(".objinfo-flabel")?.textContent === "Höhe")
|
||||
?.querySelector(".objinfo-fval")?.textContent || "",
|
||||
);
|
||||
const setZ = await p.evaluate(() => {
|
||||
const blocks = [...document.querySelectorAll(".objinfo-wall-anchor")];
|
||||
const okBlock = blocks[1];
|
||||
const input = okBlock?.querySelector("input.objinfo-input");
|
||||
if (!input) return false;
|
||||
const setter = Object.getOwnPropertyDescriptor(
|
||||
window.HTMLInputElement.prototype,
|
||||
"value",
|
||||
).set;
|
||||
setter.call(input, "5");
|
||||
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
|
||||
input.blur();
|
||||
return true;
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 250));
|
||||
const heightAfter = await p.evaluate(
|
||||
() =>
|
||||
[...document.querySelectorAll(".objinfo-field")]
|
||||
.find((f) => f.querySelector(".objinfo-flabel")?.textContent === "Höhe")
|
||||
?.querySelector(".objinfo-fval")?.textContent || "",
|
||||
);
|
||||
await p.screenshot({ path: "scripts/probe-wallattr-3-ok-custom.png" });
|
||||
|
||||
// 3D (Isometrie) umschalten und Screenshot — der Umschalter ist ein Icon-Button
|
||||
// mit title „Isometrie …" in der Oberleiste (kein Tab).
|
||||
await p.evaluate(() => {
|
||||
const el = [...document.querySelectorAll("button[title]")].find((b) =>
|
||||
(b.getAttribute("title") || "").startsWith("Isometrie"),
|
||||
);
|
||||
el?.click();
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
await p.screenshot({ path: "scripts/probe-wallattr-4-3d.png" });
|
||||
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
tabOk,
|
||||
selected,
|
||||
hasWallSection,
|
||||
hasRefDropdown,
|
||||
refPicked,
|
||||
beforeRef,
|
||||
afterRef,
|
||||
footprintShifted: beforeRef.minY !== afterRef.minY || beforeRef.maxY !== afterRef.maxY,
|
||||
okCustom,
|
||||
setZ,
|
||||
heightBefore,
|
||||
heightAfter,
|
||||
errs,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
await b.close();
|
||||
Reference in New Issue
Block a user