Files
houseplan-card/demo/smoke_wall_face_overlap.mjs
2026-09-06 17:38:47 +03:00

98 lines
4.2 KiB
JavaScript

/** Issue #177/#478: overlap guards operate on canonical wall partitions. */
import { launch, checkAll, finish } from './serve.mjs';
const { page, browser } = await launch({ width: 1100, height: 820 }, 1);
const result = await page.evaluate(async () => {
const out = {};
const card = window.__card;
const update = async () => { card.requestUpdate(); await card.updateComplete; };
const ring = (x0, y0, x1, y1) => [[x0, y0], [x1, y0], [x1, y1], [x0, y1]];
const closed = (points) => [...points, [...points[0]]];
const roomAt = (name, points) => ({
id: `r-${name}`, name,
poly: points.map((point) => [point[0] / 1000, point[1] / 1000]),
});
const space = () => card._serverCfg.spaces[0];
const reset = async (rooms = []) => {
card._serverCfg = {
spaces: [{
id: 'overlap', title: 'Overlap', cell_cm: 5, view_box: [0, 0, 1, 0.7],
rooms: structuredClone(rooms), openings: [], partitions: [], wall_columns: [],
}],
markers: [], settings: {},
};
card._space = 'overlap'; card._layout = {}; card._cfgEpoch++;
card._modelCache = null; card._frame = null; card._geometryHistory.clear();
card._path = []; card._activeWallChainId = null;
card._activeWallChainPartitionIds = []; card._wallChainSegmentCms = [];
card._wallFaceBatch = null; card._roomDialog = false;
card._setMode('plan'); card._tool = 'draw'; await update();
};
const click = async (x, y) => {
const stage = (card.shadowRoot || card.renderRoot).querySelector('.stage');
const rect = stage.getBoundingClientRect();
const view = card._viewOr(card._baseVb());
card._markupClick(new MouseEvent('click', {
clientX: rect.left + ((x - view.x) / view.w) * rect.width,
clientY: rect.top + ((y - view.y) / view.h) * rect.height,
bubbles: true,
}));
await update();
};
const draw = async (points) => {
for (const point of points) await click(point[0], point[1]);
};
const base = ring(200, 200, 400, 400);
await reset([roomAt('base', base)]);
await draw(closed(base));
out.duplicateOffersNothing = !card._wallFaceBatch && !card._roomDialog;
out.duplicateKeepsOneRoom = space().rooms.length === 1;
await reset([roomAt('base', base)]);
const roomShape = () => (space().rooms || []).map(({ wall_ids: _wallIds, ...room }) => room);
const configBefore = JSON.stringify(roomShape());
const drawnRing = ring(300, 300, 500, 500);
await draw(closed(drawnRing));
const offered = card._wallFaceBatch?.candidates || [];
out.partialOverlapDecomposesIntoFaces = offered.length === 3;
out.partialOverlapNeverOffersDrawnRing = offered.length > 0
&& offered.every((candidate) => candidate.area < 40000 - 1);
out.partialOverlapFacesSumToUnion = Math.abs(
offered.reduce((sum, candidate) => sum + candidate.area, 0) - 70000,
) < 1;
out.partialOverlapKeepsRoomsUntilDecision = JSON.stringify(roomShape()) === configBefore;
await reset([roomAt('outer', ring(100, 100, 600, 600))]);
await draw(closed(ring(200, 200, 300, 300)));
out.innerNestingIsOffered = card._wallFaceBatch?.candidates.length === 1;
card._nameSel = 'Island'; card._saveRoom(); await update();
out.innerNestingCreatesIslandRoom = space().rooms.length === 2
&& space().rooms.some((room) => room.name === 'Island');
await reset([roomAt('inner', ring(300, 300, 400, 400))]);
await draw(closed(ring(150, 150, 600, 600)));
out.outerNestingIsOffered = card._wallFaceBatch?.candidates.length === 1;
card._nameSel = 'Around'; card._saveRoom(); await update();
out.outerNestingCreatesRoom = space().rooms.length === 2
&& space().rooms.some((room) => room.name === 'Around');
await reset([roomAt('base', base)]);
const clash = ring(300, 300, 500, 500);
card._wallFaceBatch = {
candidates: [{ key: 'manual', area: 1, ring: clash }],
index: 0,
decisions: [{ candidate: { key: 'manual', area: 1, ring: clash }, create: true }],
activePath: closed(clash), activeCms: [15, 15, 15, 15], activePartitionIds: [],
};
const roomsBeforeGuard = space().rooms.length;
card._applyWallFaceBatch(); await update();
out.applyGuardRejectsOverlap = space().rooms.length === roomsBeforeGuard;
out.applyGuardClosesBatch = !card._wallFaceBatch;
return out;
});
checkAll(result);
await finish(browser, result);