mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-06 14:39:22 +00:00
BG: drawn-plan paper follows the room contours, not their bounding box
Owner: the white backing must hug the ROOMS — an L-shaped house or detached buildings grew a white square around the plan. Drawn plans now paper one opaque shape per room (paperRoomShapes in logic.ts) in exactly the room's own geometry — polygon points / rounded rect verbatim — so the union of the stack is the paper: islands paint over their parent, open (virtual) boundaries change nothing, and the scene bg_color / daynight sky reaches the exterior walls, shows in the L's pocket and between buildings. Image plans keep the backdrop-image rect (the canvas IS the paper). A live resize preview (_rszPreview) feeds _renderCfg, so the paper moves WITH a dragged wall. Static space-card follows the same contract. Paper is fill-only (stroke:none). smoke_bg_color §11–13 rewritten: L-shaped + detached test rooms, paper-per- room DOM checks, resize-preview wiring, pixel probes (acid in the pocket and between buildings, none inside rooms); §13 injects the snapshot directly — the module-level config-store cache made the old WS mock a no-op. Was 8 red on the previous build, green now. docs/SUN.md + docs/TESTING.md contract updated; unit test for paperRoomShapes.
This commit is contained in:
+28
-15
@@ -13,7 +13,7 @@ import {
|
||||
} from './rules';
|
||||
import {
|
||||
lqiColor, snapToGrid, samePoint, pointInPolygon, markerIdForBinding,
|
||||
segmentCm, formatLength, roomEdges, roomPoly, pointStrictlyInside, roomsOverlap,
|
||||
segmentCm, formatLength, roomEdges, roomPoly, paperRoomShapes, pointStrictlyInside, roomsOverlap,
|
||||
pointOnBoundary, mergeRooms, splitRoomPath, polygonArea, closestPointOnBoundary, pointStrictlyInside as ptInside, islandsOf, sharedBoundary, openZoneOf, distToSegment, outlineWithout, cutSegments, alignGuides, segmentAngle, is45, type AlignGuide, swipeTarget, clampScale, migratePdfUrls, roomFillModeOf, contentUrl,
|
||||
snapToWall, openingAmount, interiorPoint, poleOfInaccessibility, subst,
|
||||
averageLqi, fitView, declump, safeUrl, resolveTapAction, floorsOf, type FloorInfo,
|
||||
@@ -1598,19 +1598,20 @@ class HouseplanCard extends LitElement {
|
||||
|
||||
/**
|
||||
* Opaque plan "paper" (owner 2026-08-03): the scene background — bg_color or
|
||||
* the 'daynight' sky — must never bleed through the plan itself, so an
|
||||
* opaque rect sits under everything the plan draws. It hugs the plan's
|
||||
* extents: the backdrop image when there is one, otherwise the same content
|
||||
* bounds the opening view fits (an empty drawn space papers its whole
|
||||
* canvas — exactly the pre-bg_color look). Its colour is the pre-bg_color
|
||||
* canvas (styles.ts .hp-paper): white for drawn plans, the theme card
|
||||
* background under an image. At night 'daynight' dims it via the zoomwrap
|
||||
* brightness filter ONLY — its alpha stays 1 (docs/SUN.md).
|
||||
* the 'daynight' sky — must never bleed through the plan itself, so opaque
|
||||
* shapes sit under everything the plan draws. With a backdrop image the
|
||||
* paper is the image rect (the canvas IS the paper) — this helper returns
|
||||
* it. For a hand-drawn plan the paper follows the ROOM CONTOURS instead
|
||||
* (paperRoomShapes in logic.ts): one shape per room in exactly the room's
|
||||
* own geometry, so an L-shaped house or detached buildings never grow a
|
||||
* white bounding rectangle — the scene colour is visible right up to the
|
||||
* exterior walls, and an empty drawn space has no paper at all. Its colour
|
||||
* is the pre-bg_color canvas (styles.ts .hp-paper): white for drawn plans,
|
||||
* the theme card background under an image. At night 'daynight' dims it via
|
||||
* the zoomwrap brightness filter ONLY — its alpha stays 1 (docs/SUN.md).
|
||||
*/
|
||||
private _paperRect(m: SpaceModel): { x: number; y: number; w: number; h: number } {
|
||||
if (m.bg) return { x: m.bg.x, y: m.bg.y, w: m.bg.w, h: m.bg.h };
|
||||
const b = this._baseVb();
|
||||
return { x: b[0], y: b[1], w: b[2], h: b[3] };
|
||||
private _paperRect(m: SpaceModel): { x: number; y: number; w: number; h: number } | null {
|
||||
return m.bg ? { x: m.bg.x, y: m.bg.y, w: m.bg.w, h: m.bg.h } : null;
|
||||
}
|
||||
|
||||
/** Aspect ratio of the scene (width/height, px). */
|
||||
@@ -5065,8 +5066,20 @@ class HouseplanCard extends LitElement {
|
||||
style="${dayNight ? `filter:brightness(${(1 - planDim).toFixed(3)})` : ''}">
|
||||
<svg viewBox="${view.x} ${view.y} ${view.w} ${view.h}" preserveAspectRatio="xMidYMid meet">
|
||||
${(() => {
|
||||
const pp = this._paperRect(space);
|
||||
return svg`<rect class="hp-paper" x="${pp.x}" y="${pp.y}" width="${pp.w}" height="${pp.h}" pointer-events="none"></rect>`;
|
||||
// opaque paper: the image rect for image plans, the room
|
||||
// contours for drawn ones (owner: no white square around an
|
||||
// L-shaped house — the scene bg reaches the exterior walls).
|
||||
// `space` comes from _renderCfg, so a live resize preview
|
||||
// (_rszPreview) moves the paper together with the rooms.
|
||||
if (space.bg) {
|
||||
const pp = this._paperRect(space)!;
|
||||
return svg`<rect class="hp-paper" x="${pp.x}" y="${pp.y}" width="${pp.w}" height="${pp.h}" pointer-events="none"></rect>`;
|
||||
}
|
||||
return paperRoomShapes(space.rooms).map((sh) =>
|
||||
'poly' in sh
|
||||
? svg`<polygon class="hp-paper" points="${sh.poly}" pointer-events="none"></polygon>`
|
||||
: svg`<rect class="hp-paper" x="${sh.rect.x}" y="${sh.rect.y}" width="${sh.rect.w}" height="${sh.rect.h}" rx="${sh.rect.rx}" pointer-events="none"></rect>`,
|
||||
);
|
||||
})()}
|
||||
${this._editing ? this._renderMarkupDefs(vb) : nothing}
|
||||
${this._editing && !this._markup
|
||||
|
||||
@@ -62,6 +62,30 @@ export function roomPoly(r: any): number[][] | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paper shapes for a hand-drawn plan (owner 2026-08-03): the opaque paper
|
||||
* follows the ROOM CONTOURS, not their bounding box — an L-shaped house or a
|
||||
* detached building must not grow a white rectangle around itself. One shape
|
||||
* per room, in EXACTLY the geometry the room itself renders (same polygon
|
||||
* points string, same rounded rect), so the paper never peeks past a wall;
|
||||
* the union of the stack is the paper. Islands simply paint over their
|
||||
* parent; open (virtual) boundaries do not affect the paper at all.
|
||||
*/
|
||||
export function paperRoomShapes(rooms: any[]): Array<
|
||||
| { poly: string }
|
||||
| { rect: { x: number; y: number; w: number; h: number; rx: number } }
|
||||
> {
|
||||
const out: Array<{ poly: string } | { rect: { x: number; y: number; w: number; h: number; rx: number } }> = [];
|
||||
for (const r of rooms || []) {
|
||||
if (r?.poly?.length >= 3) {
|
||||
out.push({ poly: r.poly.map((p: number[]) => p.join(',')).join(' ') });
|
||||
} else if (r && r.x != null && r.y != null && r.w != null && r.h != null) {
|
||||
out.push({ rect: { x: r.x, y: r.y, w: r.w, h: r.h, rx: Math.min(r.w, r.h) * 0.03 } });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function roomEdges(rooms: any[]): number[][] {
|
||||
const out: number[][] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
+4
-1
@@ -207,9 +207,12 @@ class HouseplanSpaceCard extends LitElement {
|
||||
}
|
||||
/* Opaque plan paper — the scene bg_color/daynight sky shows only AROUND
|
||||
the plan (owner 2026-08-03). The static card keeps its historical
|
||||
canvas colour: the theme card background. */
|
||||
canvas colour: the theme card background. Drawn plans paper the room
|
||||
contours (per-room shapes, fill only, no stroke) — same contract as
|
||||
the full card. */
|
||||
.hp-paper {
|
||||
fill: var(--ha-card-background, var(--card-background-color, #111));
|
||||
stroke: none;
|
||||
}
|
||||
.hp-static-stage .devlayer {
|
||||
position: absolute;
|
||||
|
||||
+15
-11
@@ -8,13 +8,13 @@
|
||||
*/
|
||||
import { html, svg, nothing, type TemplateResult } from 'lit';
|
||||
import { buildDevices, areaLqi, areaLights, areaTemp } from './devices';
|
||||
import { spaceDisplayOf, roomFillStyle, fillColorsOf, roomFillModeOf, stageBgOf } from './logic';
|
||||
import { spaceDisplayOf, roomFillStyle, fillColorsOf, roomFillModeOf, stageBgOf, paperRoomShapes } from './logic';
|
||||
import { DEFAULT_ICON_RULES, compileIconRules, EXCLUDED_DOMAINS } from './rules';
|
||||
import { t, type Lang } from './i18n';
|
||||
import { bgModeOf, northDegOf, sunStateOf, dayPhase } from './sun';
|
||||
import type { ServerConfig } from './types';
|
||||
import {
|
||||
spaceModels, roomCenter, defaultPositions, markerPos, labelPos, contentBounds, type Layout,
|
||||
spaceModels, roomCenter, defaultPositions, markerPos, labelPos, type Layout,
|
||||
} from './space-geometry';
|
||||
|
||||
export { spaceModels } from './space-geometry';
|
||||
@@ -159,21 +159,25 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
|
||||
const stageBg = sunBg || stageBgOf(o.cfg?.settings, disp);
|
||||
|
||||
// Opaque plan paper (owner 2026-08-03), same contract as the full card: the
|
||||
// scene background never bleeds through the plan. The rect hugs the backdrop
|
||||
// image, or the drawn content's bounds (marker positions included, as on the
|
||||
// full card); an empty space papers its whole canvas.
|
||||
const paperPts = spaceDevs.map((d) => {
|
||||
const p = markerPos(d, o.layout, o.cfg, defPos, space);
|
||||
return [p.x, p.y] as const;
|
||||
});
|
||||
// scene background never bleeds through the plan. With a backdrop image the
|
||||
// paper is the image rect; a hand-drawn plan papers the ROOM CONTOURS
|
||||
// (paperRoomShapes) — never their bounding box, so the scene colour reaches
|
||||
// the exterior walls of an L-shaped house and fills the gaps between
|
||||
// detached buildings. An empty drawn space has no paper at all.
|
||||
const paper = space.bg
|
||||
? { x: space.bg.x, y: space.bg.y, w: space.bg.w, h: space.bg.h }
|
||||
: contentBounds(space, 0.05, paperPts) || { x: vb[0], y: vb[1], w: vb[2], h: vb[3] };
|
||||
: null;
|
||||
|
||||
return html`
|
||||
<div class="hp-static-stage" style="aspect-ratio:${vb[2]}/${vb[3]}${stageBg ? ';background:' + stageBg : ''}">
|
||||
<svg viewBox="${vb[0]} ${vb[1]} ${vb[2]} ${vb[3]}" preserveAspectRatio="xMidYMid meet">
|
||||
${svg`<rect class="hp-paper" x="${paper.x}" y="${paper.y}" width="${paper.w}" height="${paper.h}"></rect>`}
|
||||
${paper
|
||||
? svg`<rect class="hp-paper" x="${paper.x}" y="${paper.y}" width="${paper.w}" height="${paper.h}"></rect>`
|
||||
: paperRoomShapes(space.rooms).map((sh) =>
|
||||
'poly' in sh
|
||||
? svg`<polygon class="hp-paper" points="${sh.poly}"></polygon>`
|
||||
: svg`<rect class="hp-paper" x="${sh.rect.x}" y="${sh.rect.y}" width="${sh.rect.w}" height="${sh.rect.h}" rx="${sh.rect.rx}"></rect>`,
|
||||
)}
|
||||
${bgHref
|
||||
? svg`<image href="${bgHref}" x="${space.bg!.x}" y="${space.bg!.y}" width="${space.bg!.w}" height="${space.bg!.h}" preserveAspectRatio="none" />`
|
||||
: nothing}
|
||||
|
||||
+6
-2
@@ -316,10 +316,14 @@ export const cardStyles = css`
|
||||
/* Opaque plan paper (owner 2026-08-03): the scene bg_color / daynight sky
|
||||
shows ONLY around the plan, never through it. The colour is the
|
||||
pre-bg_color canvas — the theme card background under an image plan,
|
||||
plain white for a hand-drawn one (.stage.noplan). Night dimming comes
|
||||
from the .zoomwrap brightness filter, never from alpha (docs/SUN.md). */
|
||||
plain white for a hand-drawn one (.stage.noplan). On drawn plans the
|
||||
paper is per-room shapes following the room contours — fill only,
|
||||
never a stroke, so the paper cannot poke past a wall. Night dimming
|
||||
comes from the .zoomwrap brightness filter, never from alpha
|
||||
(docs/SUN.md). */
|
||||
.hp-paper {
|
||||
fill: var(--ha-card-background, var(--card-background-color, #111));
|
||||
stroke: none;
|
||||
}
|
||||
.stage.noplan .hp-paper {
|
||||
fill: #ffffff;
|
||||
|
||||
Reference in New Issue
Block a user