mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 08:28:31 +00:00
fix+docs v1.21.1: audit of v1.16-v1.21
- fix: Split snaps the click to the room's nearest wall instead of the grid, so it works on non-grid-aligned rooms (imported/legacy polygons); the pull is capped (~6 cells) so accidental mid-room clicks stay a miss; splitRoom() still rejects a bad cut. closestPointOnBoundary() + unit test. - docs: README (en+ru) documents Merge/Split/ruler/per-space scale (were undocumented); TESTING.md merge/split rows + fresh self-run; new smokes smoke_merge_split, smoke_split_nonsnap (incl. the capped-pull case). - 73 frontend + 12 backend tests green; module eval + both cards register.
This commit is contained in:
+12
-4
@@ -14,7 +14,7 @@ import {
|
||||
import {
|
||||
lqiColor, snapToGrid, samePoint, pointInPolygon, markerIdForBinding,
|
||||
segmentCm, formatLength, roomEdges, roomPoly, pointStrictlyInside, roomsOverlap,
|
||||
pointOnBoundary, mergeRooms, splitRoom, polygonArea,
|
||||
pointOnBoundary, mergeRooms, splitRoom, polygonArea, closestPointOnBoundary,
|
||||
averageLqi, fitView, declump, safeUrl, resolveTapAction, floorsOf, type FloorInfo,
|
||||
spaceDisplayOf, roomFillColor, DEFAULT_ROOM_COLOR, DEFAULT_ROOM_OPACITY,
|
||||
DEFAULT_TEMP_MIN, DEFAULT_TEMP_MAX, type SpaceDisplay,
|
||||
@@ -28,7 +28,7 @@ import './space-card';
|
||||
import { cardStyles } from './styles';
|
||||
import { langOf, t, type I18nKey } from './i18n';
|
||||
|
||||
const CARD_VERSION = '1.21.0';
|
||||
const CARD_VERSION = '1.21.1';
|
||||
const LS_KEY = 'houseplan_card_layout_v1';
|
||||
const LS_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering
|
||||
const LS_ZOOM = 'houseplan_card_zoom_v1';
|
||||
@@ -1171,9 +1171,17 @@ class HouseplanCard extends LitElement {
|
||||
this._splitSel = null;
|
||||
return;
|
||||
}
|
||||
// A split point lands on the room's nearest wall — the user aims at a wall,
|
||||
// and rooms need not be grid-aligned (imported/legacy polygons), so snapping
|
||||
// to the grid would miss the outline. The pull is capped: a click far from
|
||||
// any wall (e.g. an accidental one in the middle of the room) is a miss and
|
||||
// gets the toast, not a wall the user never meant. splitRoom() still rejects
|
||||
// any cut that is not a clean wall-to-wall chord.
|
||||
const eps = this._gridPitch * 0.02;
|
||||
const pt = this._snap(raw);
|
||||
if (!pointOnBoundary(pt, poly, eps)) {
|
||||
const pull = this._gridPitch * 6; // ≈2.5% of the plan width — generous but intentional
|
||||
const near = closestPointOnBoundary(raw, poly);
|
||||
const pt = near && Math.hypot(near[0] - raw[0], near[1] - raw[1]) <= pull ? near : null;
|
||||
if (!pt || !pointOnBoundary(pt, poly, eps)) {
|
||||
this._showToast(this._t('toast.split_pick_wall'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -105,6 +105,29 @@ function distToSeg(p: number[], a: number[], b: number[]): number {
|
||||
* neighbouring rooms share walls, so their vertices sit on each other's outlines —
|
||||
* including mid-span, since real walls overlap collinearly rather than match exactly.
|
||||
*/
|
||||
/**
|
||||
* Project a point onto the nearest edge of a polygon and return that point,
|
||||
* or null when the polygon has no edges. Used to snap a Split click onto the
|
||||
* actual wall (rooms may not be grid-aligned — imported polygons, older configs),
|
||||
* so cutting no longer requires hitting a grid node exactly on the outline.
|
||||
*/
|
||||
export function closestPointOnBoundary(p: number[], poly: number[][]): number[] | null {
|
||||
if (!poly || poly.length < 2) return null;
|
||||
let best: number[] | null = null;
|
||||
let bestD = Infinity;
|
||||
for (let i = 0; i < poly.length; i++) {
|
||||
const a = poly[i], b = poly[(i + 1) % poly.length];
|
||||
const dx = b[0] - a[0], dy = b[1] - a[1];
|
||||
const len2 = dx * dx + dy * dy;
|
||||
let t = len2 ? ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2 : 0;
|
||||
t = Math.max(0, Math.min(1, t));
|
||||
const q = [a[0] + t * dx, a[1] + t * dy];
|
||||
const d = Math.hypot(p[0] - q[0], p[1] - q[1]);
|
||||
if (d < bestD) { bestD = d; best = q; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
export function pointOnBoundary(p: number[], poly: number[][], eps = 1e-6): boolean {
|
||||
if (!poly || poly.length < 2) return false;
|
||||
for (let i = 0; i < poly.length; i++)
|
||||
|
||||
Reference in New Issue
Block a user