fix: make plan visuals grid-scale invariant

Issue: #239
User-Visible: yes
This commit is contained in:
Sergey Matyunin
2026-08-22 13:45:26 +03:00
parent a4e8dd7ba7
commit 0d91c1e18e
31 changed files with 2017 additions and 963 deletions
+13 -3
View File
@@ -280,8 +280,17 @@ so secure confirmation and Device-editor routing cannot drift.
`size` (icon multiplier via the
`--dev-size` CSS var — value badges scale along) and `angle` rotate/scale a single icon.
Room drawing shows a live **ruler** (`segmentCm` +
`formatLength`, metres or feet+inches by `hass.config.unit_system`); the scale is per-space
`cell_cm` (default 5 cm per grid cell).
`formatLength`, metres or feet+inches by `hass.config.unit_system`); the scale is
per-space canonical `cell_cm`. New spaces default to 1 cm in metric HA or
2.54 cm (shown as 1 inch) in imperial HA. Missing legacy data still reads as
5 cm and is not migrated.
Legacy raw SVG constants are classified as visual units relative to the old
5 cm renderer and pass through `gridVisualScale()` / `gridVisualUnits()`.
Physical cm paths, screen-fixed chrome, plan-relative marker/label sizes and
grid geometry are deliberately excluded from that factor. Full/static roots
expose the same `--hp-cell-visual-scale`; hidden isometric heights and
user-space shadows include the factor in their structural cache inputs.
`config.markers[]`: `{id, binding:'device:<id>'|'entity:<eid>'|'virtual', space?, area?, hidden?, removed?,
@@ -522,7 +531,8 @@ transaction through an accessible `hp-dialog`, never native `confirm()`.
While drawing, the length of the current segment follows the cursor (`_fmtLen` → `segmentCm`/
`formatLength`): metres, or feet+inches when `hass.config.unit_system` is imperial. The scale is
per-space `cell_cm` — cm represented by one grid cell (default 5, so 240 cells ≈ 12 m).
per-space `cell_cm` — canonical centimetres represented by one grid cell; new
spaces use 1 cm or 2.54 cm/1 inch, while missing legacy values fall back to 5 cm.
## Editor chrome and contextual controls
+17 -2
View File
@@ -30,6 +30,21 @@ coordinate system.
4. **What is stored is only where something is drawn.** There is no
stored extent to keep in sync.
### Grid precision and visual units
`cell_cm` is canonical centimetres per grid cell. New metric spaces use 1 cm;
new imperial spaces store 2.54 cm and present it as 1 inch. The historical
5 cm value remains the read fallback for missing or invalid legacy data, not a
creation default, and existing spaces are never migrated merely by opening
settings.
A finer grid changes precision only. Raw SVG constants inherited from the
historical 5 cm renderer are **visual units** and use
`gridVisualScale(cell_cm) = 5 / cell_cm`. Physical sizes already converted from
centimetres, screen-fixed strokes/handles, plan-relative icon sizes and the
grid pitch must not receive that factor again. Full, static and hidden
isometric renderers share this classification.
## Model
| Concept | Before | Now |
@@ -75,8 +90,8 @@ the exact target atomically without exposing a default-fit frame.
| `_NORM` (decor x/y/w/h) | `-1 .. 2` | `-5000 .. 5000` | coordinate |
| opening `length` | `0.001 .. 1` | `0.001 .. 5000` | size — strictly positive |
`+/-5000` is **garbage insurance, not a frame**. At the product's own
scale (`cell_cm` = 5 by default, 240 grid cells across the unit width)
`+/-5000` is **garbage insurance, not a frame**. At the historical compatibility
scale (`cell_cm` = 5, 240 grid cells across the unit width)
one canvas width is ~12 m, so `5000` is ~60 km of plan — unreachable
in a home, while still stopping a stored `1e100` from making the plan
invisible for every client (the failure HP-1500-03 / HP-1501-01
+9
View File
@@ -2,6 +2,15 @@
## Unreleased
- Grid precision no longer changes the appearance of the same physical plan.
Room and wall outlines, openings and their hit areas, Plan hints, the static
card and hidden isometric geometry now retain the `cell_cm: 5` visual size at
every supported cell size, while physical objects and screen-fixed controls
are not scaled twice. New metric spaces start at 1 cm per cell; imperial HA
starts at 1 inch per cell and shows inches in the field. Existing values are
neither migrated nor rounded when settings are opened and saved
([#239](https://github.com/Matysh/houseplan-card/issues/239)).
- While placing a door, window, gate or open passage, Plan now shows the usable
distances from both jambs to the physical inner ends of the wall, with thin
dimension lines and endpoint ticks. A shared wall shows four measurements —
+10
View File
@@ -8,6 +8,16 @@
## Не выпущено
- Точность сетки больше не меняет внешний вид одного и того же физического
плана. Контуры комнат и стен, проёмы и их зоны попадания, подсказки редактора,
статическая карточка и скрытая изометрия сохраняют эталонный вид
`cell_cm: 5` при любом допустимом размере клетки; физические объекты и
экранные элементы повторно не масштабируются. Новое метрическое пространство
начинается с 1 см на клетку, а в имперской системе HA — с 1 дюйма и поле
показывает дюймы. Существующие значения не мигрируют и не округляются при
обычном открытии и сохранении настроек
([#239](https://github.com/Matysh/houseplan-card/issues/239)).
- При размещении двери, окна, ворот или открытого проёма Редактор плана теперь
показывает полезные расстояния от обоих косяков до физических внутренних
концов стены — с тонкими размерными линиями и засечками. На общей стене видны
+15 -2
View File
@@ -612,7 +612,19 @@ separately promised workflows:
- [ ] Average room temperature counts ONLY thermometer/air-monitor devices — fridges, TRV heads,
smart-plug chip temperatures (`*_device_temperature`) and diagnostic-category temps are excluded [manual]
- [ ] Space dialog is 500 px wide; the comfort-bounds inputs are compact (56 px)
- [ ] The scale (cm per cell) input is compact (72 px), not full-width [manual]
- [ ] The scale input is compact (72 px), not full-width; it shows cm in metric
HA and inches in imperial HA [manual; auto: smoke_space_scale_defaults]
- [ ] A new manual space and every floor-import draft start at 1 cm in metric HA
or exactly 1 inch/2.54 canonical cm in imperial HA. Opening and saving an
existing 5 cm, fractional or missing legacy value without editing the
field is lossless; changing language does not rewrite the canonical draft
[auto: smoke_space_scale_defaults]
- [ ] Physically equivalent rich fixtures at 1 cm and 5 cm have equal View,
Plan-with-grid-masked and static-card pixels/critical bounds. The grid has
five times the intervals only; openings retain their edge hit target, and
physical/screen-fixed layers are not double-scaled
[auto: smoke_grid_scale_invariance; unit: grid-scale.test.mjs,
opening-symbol.test.mjs, canvas.test.mjs]
- [ ] General settings (⚙ in the header): fill colors grouped by mode (lights on/off/none,
temp cold/comfy/hot, LQI weak/strong), each with its own opacity slider [manual];
Reset restores defaults; saving defaults stores nothing [manual]
@@ -632,7 +644,8 @@ separately promised workflows:
smoke_unified_wall_tool + unified-wall-tool-source.test]
- [ ] Grid appears; dots snap; the wall chain draws pair-by-pair; shared walls reused
- [ ] Ruler: while drawing, the length of the current segment follows the cursor
(metres, or feet+inches on an imperial HA); scale = space "cm per cell" (default 5)
(metres, or feet+inches on an imperial HA); scale = canonical per-space
`cell_cm` (new-space default 1 cm or 1 inch; missing legacy fallback 5 cm)
- [ ] Every completed segment is crash-safe in `room_drafts`. Changing tool,
editor or floor finishes an open chain as ordinary partitions in one
history/config transaction; the finished chain is not resumed as a draft
+13
View File
@@ -235,6 +235,19 @@ The plan image keeps its proportions initially. Background can later move,
scale or rotate it. Detaching a plan never deletes its server file; deletion
requires an explicit user action.
### Grid scale
The scale field is the real size of one grid cell. A new metric space starts at
**1 cm per cell**; with an imperial Home Assistant unit system it starts at
**1 in per cell** and the field is shown in inches. Floor import uses the same
default for every new space.
Choose a finer cell when you need more precise snap points. It changes only the
number of grid points per metre, not how the finished plan looks: physically
equal rooms, walls, openings, labels and markers retain the same appearance.
Existing spaces keep their stored scale. A legacy space without a scale still
uses the 5 cm compatibility fallback and is not silently migrated.
### Tab order
Space tabs follow the order in which the spaces were created, and that order can
+8 -1
View File
@@ -293,7 +293,7 @@ desktop: для точного рисования, Resize, модификато
| Раздел | Настройка | Результат |
|---|---|---|
| Масштаб | Сантиметров на клетку | Переводит сетку в реальные размеры и площади; начальное значение 5 см |
| Масштаб | См или дюймов на клетку | Переводит сетку в реальные размеры и площади; новое пространство начинается с 1 см или 1 дюйма по системе единиц HA |
| Отображение | Всегда показывать границы | Рисует контуры комнат в просмотре |
| Отображение | Показывать названия | Рисует карточки комнат в Просмотре, киоске и статической карточке; при выключении не оставляет упрощённых подписей |
| Отображение | Показывать LQI | Переопределяет базовую настройку карточки для этого пространства |
@@ -316,6 +316,13 @@ desktop: для точного рисования, Resize, модификато
| Заливка | Свой цвет/LQI/свет/температура | Выбирает постоянный цвет либо данные для заливки комнат пространства; свой цвет является обычным режимом по умолчанию |
| Свечение источников света | Вкл/выкл | Добавляет световые пятна; базовое затемнение используется только без другой видимой заливки |
Более мелкая клетка даёт больше точек привязки на метр и повышает точность, но
не меняет внешний вид готового плана. Физически одинаковые комнаты, стены,
проёмы, подписи и маркеры выглядят одинаково при 1 и 5 см на клетку.
Существующие пространства сохраняют записанный масштаб; для старого
пространства без `cell_cm` остаётся compatibility fallback 5 см, без скрытой
миграции.
Удаление пространства удаляет его комнаты и разметку после подтверждения. Файл подложки при этом автоматически не удаляется: им можно управлять через список уже загруженных планов.
<!-- docs-section: plan-tools -->
Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 44 KiB

+12 -12
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"fixture": "synthetic-only",
"sourceFingerprint": "2b9f91ca744ed0429aae113b6e6f94ceada2c0fc3b2138559cf59b145b3e6b1c",
"sourceFingerprint": "ed33e779c76b7373c152ecf13e82064a09fe62608af1c75cea96c48369cfec11",
"captureScriptSha256": "34f2219790d46efd8250e7a1bd829cb8fc0b0547e1260635fefa52407551b41b",
"command": "npm run build && node demo/docs/capture.mjs",
"scenarios": {
@@ -13,7 +13,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "2b9f91ca744ed0429aae113b6e6f94ceada2c0fc3b2138559cf59b145b3e6b1c",
"sourceSha256": "ed33e779c76b7373c152ecf13e82064a09fe62608af1c75cea96c48369cfec11",
"imageSha256": "2885f96e348b15ab7c696e56e99bddcd9bb2ee94218a883e5a2155f45f5042aa"
},
"view-touch": {
@@ -24,7 +24,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "2b9f91ca744ed0429aae113b6e6f94ceada2c0fc3b2138559cf59b145b3e6b1c",
"sourceSha256": "ed33e779c76b7373c152ecf13e82064a09fe62608af1c75cea96c48369cfec11",
"imageSha256": "f62d8af3617c00a5e99511bd765980d2e27badf25047a1be34046b64195abe6c"
},
"space-create": {
@@ -35,8 +35,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "2b9f91ca744ed0429aae113b6e6f94ceada2c0fc3b2138559cf59b145b3e6b1c",
"imageSha256": "c53db2e5c642a5549c13f3c93a5b359fed69bdb2621bf71a243a877ffcb95e6b"
"sourceSha256": "ed33e779c76b7373c152ecf13e82064a09fe62608af1c75cea96c48369cfec11",
"imageSha256": "c33a7279165a4cec6fa6fadb6fd08cd967e082a17fe101ef442d27d36ae59b6b"
},
"room-contour-close": {
"file": "04-room-contour-close.png",
@@ -46,7 +46,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "2b9f91ca744ed0429aae113b6e6f94ceada2c0fc3b2138559cf59b145b3e6b1c",
"sourceSha256": "ed33e779c76b7373c152ecf13e82064a09fe62608af1c75cea96c48369cfec11",
"imageSha256": "4d63670c7bcca33da21a6cf17275786bed12e8422a50d02d4dabe59645f2cd82"
},
"plan-context-tray": {
@@ -57,7 +57,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "2b9f91ca744ed0429aae113b6e6f94ceada2c0fc3b2138559cf59b145b3e6b1c",
"sourceSha256": "ed33e779c76b7373c152ecf13e82064a09fe62608af1c75cea96c48369cfec11",
"imageSha256": "a6c526fede11bc3503fd2384bd4a3f6afa008f481c5c47578f06cae6c81c6f9b"
},
"device-editor": {
@@ -68,7 +68,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "2b9f91ca744ed0429aae113b6e6f94ceada2c0fc3b2138559cf59b145b3e6b1c",
"sourceSha256": "ed33e779c76b7373c152ecf13e82064a09fe62608af1c75cea96c48369cfec11",
"imageSha256": "9585b59add4d35b5a6f028ce5b720ef1b77a3f8b19436d192cd1a0e6fc637264"
},
"device-display-preview": {
@@ -79,7 +79,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "2b9f91ca744ed0429aae113b6e6f94ceada2c0fc3b2138559cf59b145b3e6b1c",
"sourceSha256": "ed33e779c76b7373c152ecf13e82064a09fe62608af1c75cea96c48369cfec11",
"imageSha256": "cfc317da4628d079a116ff71311fbf06b1eb3b181928a5ea7ba888ab936a5e8b"
},
"background-editor": {
@@ -90,7 +90,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "2b9f91ca744ed0429aae113b6e6f94ceada2c0fc3b2138559cf59b145b3e6b1c",
"sourceSha256": "ed33e779c76b7373c152ecf13e82064a09fe62608af1c75cea96c48369cfec11",
"imageSha256": "d7cfe70551d9260169df8efd832e32ed7df99c7f60b55d1fd4a8322bd4c47175"
},
"room-card": {
@@ -101,7 +101,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "2b9f91ca744ed0429aae113b6e6f94ceada2c0fc3b2138559cf59b145b3e6b1c",
"sourceSha256": "ed33e779c76b7373c152ecf13e82064a09fe62608af1c75cea96c48369cfec11",
"imageSha256": "029a3e69ec647a8a370d99e6bb7f9225833c526739076022f6b52ba54bff30ea"
},
"device-info": {
@@ -112,7 +112,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "2b9f91ca744ed0429aae113b6e6f94ceada2c0fc3b2138559cf59b145b3e6b1c",
"sourceSha256": "ed33e779c76b7373c152ecf13e82064a09fe62608af1c75cea96c48369cfec11",
"imageSha256": "a06cbf83f09e2f67b3566d7c0b10e973db060c3d20786f26f74ada6a21937c3e"
}
}