mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-28 19:01:34 +00:00
- settings.volumetric_view (General settings › Display, third switch) replaces the alpha entry, the header projection toggle and the phone-menu item; one rule for card, sidebar page and kiosk; editors stay Flat; backend accepts only a boolean, support package copies it. - Raised tiles in 2.5D View: no ring, rounded body min(0.275 D, 0.3 h), edge 0.1 D with the lab filters, ×1.12 size also in layout/collision, lift 0.075 D, one floor-shadow layer under all markers (theme × floor table), frames hugging tile and edge (Alert > Focus > Selected > Hover), forced colours without edge and shadow (src/iso-tiles.ts, styles/iso-tiles). - Soft sun wash instead of projected Flat wedges (src/iso-sun.ts): same gates and windows as sun_rays, length from elevation, parallelogram along the sun, tone and streaks by floor lightness, sill line. - Walls take the user's wall colour (top opaque, sides × .77/.68/.60); theme rules for walls/openings/labels removed; furniture uses the Flat stroke rule. - Smokes smoke_iso_tiles/iso_sun/iso_theme_walls/volumetric_setting, 16 new mutants, acceptance scenes STAGE6_ACCEPTANCE_SCENARIOS (golden with their Linux CI baselines), ACCEPTANCE.md side by side. Issue: #649 User-Visible: yes
152 lines
6.4 KiB
JavaScript
152 lines
6.4 KiB
JavaScript
/** Issue #141: joined independent walls share one production geometry surface. */
|
|
import { launch, checkAll, finish } from './serve.mjs';
|
|
|
|
const { page, browser } = await launch({ width: 1000, height: 860 }, 1);
|
|
|
|
const out = await page.evaluate(async () => {
|
|
const result = {};
|
|
const card = window.__card;
|
|
const root = () => card.shadowRoot || card.renderRoot;
|
|
const update = async () => {
|
|
card._cfgEpoch++;
|
|
card._modelCache = null;
|
|
card._frame = null;
|
|
card._wallUnionCache = null;
|
|
card._lightBarrierCache = null;
|
|
card.requestUpdate();
|
|
await card.updateComplete;
|
|
};
|
|
const entry = (a, b, cm) => ({
|
|
// The smoke needs an exact persisted span; the renderer resolves the key
|
|
// from these endpoints before it falls back to the legacy bucket.
|
|
key: `smoke-${a.join('-')}-${b.join('-')}`, a: [...a], b: [...b], cm,
|
|
});
|
|
const a = [0.05, 0.05], tr = [0.95, 0.05], br = [0.95, 0.95], bl = [0.05, 0.95];
|
|
const cfg = {
|
|
model_version: 10,
|
|
spaces: [{
|
|
id: 'junctions', title: 'Junctions', cell_cm: 5, view_box: [0, 0, 1, 1],
|
|
rooms: [{ id: 'room', name: 'Room', area: null, poly: [a, tr, br, bl] }],
|
|
walls: [entry(a, tr, 0), entry(tr, br, 0), entry(br, bl, 0), entry(bl, a, 0)],
|
|
partitions: [
|
|
{ id: 'l-horizontal', a: [0.10, 0.30], b: [0.40, 0.30], cm: 10 },
|
|
{ id: 'l-vertical', a: [0.40, 0.30], b: [0.40, 0.60], cm: 20 },
|
|
{ id: 'oblique-a', a: [0.58, 0.22], b: [0.76, 0.38], cm: 22 },
|
|
{ id: 'oblique-b', a: [0.76, 0.38], b: [0.62, 0.53], cm: 12 },
|
|
{ id: 't-through', a: [0.18, 0.70], b: [0.78, 0.70], cm: 24 },
|
|
{ id: 't-branch', a: [0.50, 0.54], b: [0.50, 0.70], cm: 16 },
|
|
{ id: 'room-branch', a: [0.30, 0.82], b: [0.30, 0.95], cm: 18 },
|
|
{ id: 'saved-chain-0', a: [0.12, 0.56], b: [0.28, 0.56], cm: 12 },
|
|
{ id: 'saved-chain-1', a: [0.28, 0.56], b: [0.28, 0.64], cm: 18 },
|
|
],
|
|
wall_columns: [],
|
|
}],
|
|
markers: [], settings: {},
|
|
};
|
|
card._serverCfg = structuredClone(cfg);
|
|
card._layout = {};
|
|
card._space = 'junctions';
|
|
card._setMode('plan');
|
|
card._tool = 'select';
|
|
await update();
|
|
|
|
const sourceBefore = JSON.stringify(card._serverCfg.spaces[0]);
|
|
const space = card._spaceModel();
|
|
const bodies = card._physicalBodiesR(space);
|
|
const raw = card._rawPhysicalBodiesR(space);
|
|
const frame = card._physicalBodiesCache;
|
|
result.computedPatchesExist = frame?.patches.length >= 4 && bodies.length > raw.length;
|
|
result.rawIdentityCountStaysPerSegment = raw.length === 9
|
|
&& card._curSpaceCfg.partitions.length === 9;
|
|
|
|
const wallBody = root().querySelector('.wallbody');
|
|
const missingCorner = new DOMPoint(404, 298);
|
|
result.savedRightAngleToothIsFilled = !!wallBody?.isPointInFill(missingCorner);
|
|
const planD = wallBody?.getAttribute('d') || '';
|
|
|
|
const insideRing = (point, ring) => {
|
|
let inside = false;
|
|
for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
|
|
const a0 = ring[i], b0 = ring[j];
|
|
const crosses = ((a0[1] > point[1]) !== (b0[1] > point[1]))
|
|
&& point[0] < ((b0[0] - a0[0]) * (point[1] - a0[1]))
|
|
/ ((b0[1] - a0[1]) || 1e-12) + a0[0];
|
|
if (crosses) inside = !inside;
|
|
}
|
|
return inside;
|
|
};
|
|
const insideGeometry = (point, geometry) => (geometry || []).some((polygon) =>
|
|
polygon?.length && insideRing(point, polygon[0])
|
|
&& !polygon.slice(1).some((hole) => insideRing(point, hole)));
|
|
const room = space.rooms[0];
|
|
const clean = card._cleanFloor(room, room.poly, space);
|
|
result.cleanFloorUsesJoinedCorner = !insideGeometry([404, 298], clean.geom);
|
|
const light = card._lightBarriers(
|
|
space, [{ r: room, poly: room.poly }], card._physicalBodiesR(space),
|
|
);
|
|
result.lightUsesJoinedCorner = insideGeometry([404, 298], light.masonryGeometry);
|
|
|
|
card._tool = 'draw';
|
|
card._activeWallChainId = null;
|
|
card._activeWallChainPartitionIds = [];
|
|
card._path = [[120, 780], [380, 780]];
|
|
card._wallChainSegmentCms = [12];
|
|
card._drawWallField = '24';
|
|
card._cursorPt = [380, 600];
|
|
await update();
|
|
const liveD = root().querySelector('.drawwall-preview')?.getAttribute('d') || '';
|
|
card._path = [[120, 780], [380, 780], [380, 600]];
|
|
card._wallChainSegmentCms = [12, 24];
|
|
card._cursorPt = null;
|
|
await update();
|
|
const committedPreviewD = root().querySelector('.drawwall-preview')?.getAttribute('d') || '';
|
|
result.rubberBandAndCommittedPreviewMatch = !!liveD && committedPreviewD === liveD;
|
|
|
|
card._path = [[500, 540]];
|
|
card._wallChainSegmentCms = [];
|
|
card._drawWallField = '20';
|
|
card._cursorPt = [500, 700];
|
|
await update();
|
|
result.lineTargetGetsLocalJoinPatch = root().querySelectorAll('.drawwall-preview').length === 2;
|
|
result.previewDoesNotWriteOrSplitTarget = JSON.stringify(card._serverCfg.spaces[0]) === sourceBefore;
|
|
|
|
card._cursorPt = null;
|
|
card._path = [];
|
|
card._tool = 'select';
|
|
card._setMode('view');
|
|
await update();
|
|
result.planViewParity = !!planD && root().querySelector('.wallbody')?.getAttribute('d') === planD;
|
|
|
|
history.replaceState(null, '', '#space=junctions');
|
|
await window.__hpHarnessProjection(card, 'iso');
|
|
await window.__hpEnsureHarnessIsoRuntime(card);
|
|
await update();
|
|
result.isoUsesJoinedFootprint = !!root().querySelector('.iso-walls .iso-wall-top')
|
|
&& card._isoSource().build().walls.flat(2).length > 0;
|
|
await window.__hpHarnessProjection(card, 'flat');
|
|
await update();
|
|
|
|
await customElements.whenDefined('houseplan-space-card');
|
|
const staticCard = document.createElement('houseplan-space-card');
|
|
const baseCall = card.hass.callWS.bind(card.hass);
|
|
staticCard.setConfig({ type: 'custom:houseplan-space-card', space: 'junctions', show_button: false });
|
|
staticCard.hass = { ...card.hass, callWS: async (message) => {
|
|
if (message.type === 'houseplan/config/get') return { config: structuredClone(cfg), rev: 1 };
|
|
if (message.type === 'houseplan/layout/get') return { layout: {}, rev: 1 };
|
|
return baseCall(message);
|
|
} };
|
|
document.body.appendChild(staticCard);
|
|
const started = Date.now();
|
|
while (!staticCard.renderRoot?.querySelector('.wallbody') && Date.now() - started < 6000)
|
|
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
await staticCard.updateComplete;
|
|
result.staticUsesSameJoinedPath = staticCard.renderRoot
|
|
?.querySelector('.wallbody')?.getAttribute('d') === planD;
|
|
staticCard.remove();
|
|
|
|
result.renderNeverRewritesConfig = JSON.stringify(card._serverCfg.spaces[0]) === sourceBefore;
|
|
return result;
|
|
});
|
|
|
|
await finish(browser, checkAll(out));
|