mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-05 14:19:04 +00:00
feat: support openings in independent walls
Issue: #132 User-Visible: yes
This commit is contained in:
+26
-13
@@ -333,8 +333,8 @@ contribute. Three explicitly typed exceptions are stored per space:
|
||||
independent wall segments and `wall_columns` for square/circular columns.
|
||||
They do not create a room or HA area and never split a room implicitly. Their
|
||||
physical bodies are unioned with room walls for rendering and light occlusion,
|
||||
and subtracted from clean room floor area. Openings (doors, windows and gates)
|
||||
still belong only to derived room walls and never cut an independent object.
|
||||
and subtracted from clean room floor area. A finished partition may explicitly
|
||||
host a door, window, gate or passage; unfinished drafts and columns may not.
|
||||
|
||||
Independent linear objects have two deliberate projections. Raw flat-capped
|
||||
quads preserve source identity for hit/selection/drag/properties/delete/history
|
||||
@@ -363,8 +363,11 @@ vertex cannot contribute a child-room mitre to the facade. Per-room rings remain
|
||||
an interior join/nested-room representation, and atomic quads provide a safe
|
||||
physical interval when an acute child ring cannot be subtracted. Paper and
|
||||
masonry paths are emitted by that same geometry pass. Computed independent
|
||||
junction patches enter as extras only after room opening cuts, so an opening
|
||||
cannot cut a coincident partition and room exterior authority remains intact.
|
||||
junction patches enter through the same physical union. A partition-hosted
|
||||
opening is subtracted from its explicit raw body before that union. It also
|
||||
cuts a derived room wall only when the wall is exactly collinear and covers
|
||||
the complete hosted interval; crossing or nearby bodies remain opaque and room
|
||||
exterior authority remains intact.
|
||||
Before the exterior offset is built, each saved atomic endpoint splits its
|
||||
containing collinear union edge. Offset changes are explicit butt steps at that
|
||||
endpoint, including nonzero-to-zero transitions. The topology tolerance starts
|
||||
@@ -489,16 +492,17 @@ navigation, focus restoration and outside-dismiss consumption), but no current
|
||||
tools are grouped without a separate product decision. The change is UI-only:
|
||||
plan/config models and geometry commands are unchanged.
|
||||
|
||||
## Doors, windows & gates (v1.23.0+)
|
||||
## Doors, windows, gates & passages (v1.23.0+)
|
||||
|
||||
`space.openings[]` — plan geometry, **not** markers: an opening needs an angle, a length and a
|
||||
wall, while markers are free points whose positions live in the layout store. Model:
|
||||
`{id, type: door|window|gate, x, y, angle, length, contact?, lock?, invert?, flip_h?, flip_v?}`
|
||||
(normalized coords; `length` normalized by plan width). Placement snaps onto the nearest
|
||||
**derived** wall via `snapToWall` (logic.ts) — the angle is normalized to [-90, 90) because two
|
||||
rooms share a wall with opposite edge directions, and without that a drag across segment
|
||||
boundaries flips the hinge. The opening then keeps **absolute coordinates**, so editing, merging
|
||||
or deleting rooms never breaks it.
|
||||
`space.openings[]` — plan geometry, **not** markers: an opening needs an angle,
|
||||
a length and one wall, while markers are free points whose positions live in
|
||||
the layout store. Model:
|
||||
`{id, type: door|window|gate|passage, x, y, angle, length, host?, contact?, lock?, invert?, flip_h?, flip_v?}`.
|
||||
Room-wall openings omit `host` and retain the absolute-coordinate association.
|
||||
An independent-wall opening stores
|
||||
`host:{kind:'partition',id,t}`; the stable id and normalized position `t` are
|
||||
authoritative, while `x/y/angle` are an atomically refreshed compatibility
|
||||
projection. No explicit host ever falls back to a nearest wall.
|
||||
|
||||
Rendering (after easy-floorplan, MIT): SVG symbol at the origin (jambs + hinged leaf + a
|
||||
quarter-circle arc revealed via `stroke-dashoffset`), translated/rotated onto the wall; windows
|
||||
@@ -536,6 +540,15 @@ room both halves; shared openings use a local-coordinate hard stop at `y=0`.
|
||||
Virtual spans, unfinished drafts and zero-thickness walls are ignored; legacy
|
||||
spans are clipped per atomic body.
|
||||
|
||||
The same resolved host drives placement, symbol face, full-depth partition cut,
|
||||
static/hidden-isometric rendering, Glow and edit operations. Rigid host drag
|
||||
keeps `t` and updates every materialized projection in one history command.
|
||||
Deleting a host with openings requires an explicit cascade dialog; an invalid
|
||||
host fails dark and is visible only as a rebind diagnostic in Plan. Structural
|
||||
room-face topology deliberately keeps every valid wall axis continuous through
|
||||
all opening types (#185); only `open_spans` and absent walls are connectivity
|
||||
gaps.
|
||||
|
||||
## Integration WS API
|
||||
|
||||
| Command | Parameters | Response |
|
||||
|
||||
+6
-5
@@ -549,9 +549,9 @@ When **Walls** is active in the Plan editor, a derived
|
||||
pointer-transparent SVG layer exposes the centre axes of completed room walls,
|
||||
saved inactive outlines and independent partitions. It is painted after their
|
||||
physical wall bodies, but before interactive editor chrome. Columns, decor,
|
||||
devices, the active wall chain and its live preview are not candidates. Door,
|
||||
window, gate and intentionally open-span intervals are cut from room axes; a
|
||||
cut boundary does not become a new endpoint.
|
||||
devices, the active wall chain and its live preview are not candidates.
|
||||
Opening intervals remain part of the structural axes (#185); only intentionally
|
||||
open spans are cut, and a cut boundary does not become a new endpoint.
|
||||
|
||||
The layer and hit resolver share one immutable geometry snapshot. Original
|
||||
segment endpoints are deduplicated and drawn at a physical radius of 5 cm.
|
||||
@@ -573,7 +573,7 @@ single active candidate and never writes config, layout or storage.
|
||||
|
||||
Every completed Walls segment is first persisted in the active `room_drafts`
|
||||
chain. On the click path only, an immutable planar graph is built from solid
|
||||
room edges after opening cuts, independent partitions, inactive drafts and the
|
||||
room edges without presentation opening cuts, independent partitions, inactive drafts and the
|
||||
active chain both before and after the latest segment. Endpoint, T, X and
|
||||
collinear-overlap junctions atomize that computed graph without rewriting any
|
||||
saved wall. A deterministic half-edge walk extracts bounded faces; canonical
|
||||
@@ -582,7 +582,8 @@ identity ignores winding, cyclic start and derived collinear subdivision.
|
||||
Only faces added by the latest segment and containing one of its atoms are
|
||||
offered. They are ordered by area and then canonical key. Existing exact or
|
||||
partially overlapping rooms are excluded, nested rooms remain legal, and any
|
||||
physical gap — including an opening cut — remains a gap. A clean divider across
|
||||
physical gap created by an `open_span` or absent wall remains a gap. A door,
|
||||
window, gate or passage is a property of a wall and preserves connectivity. A clean divider across
|
||||
one room reuses the Split contract: the larger side keeps the room identity,
|
||||
metadata and device binding, and only the smaller side is offered.
|
||||
|
||||
|
||||
@@ -19,6 +19,14 @@
|
||||
Create/Keep/Cancel decisions. Ctrl/Cmd+click no longer adds an extra point
|
||||
while the chain is still too short to close
|
||||
([#173](https://github.com/Matysh/houseplan-card/issues/173)).
|
||||
- Doors, windows, gates and open passages can now be placed in finished
|
||||
independent Walls segments. They cut the full wall depth, move with their
|
||||
host, keep the same Home Assistant state/actions as room-wall openings and
|
||||
are removed only through an explicit cascade confirmation. Openings no
|
||||
longer break the structural wall axis, so drawing a closed contour still
|
||||
offers the room even when one of its walls already contains an opening
|
||||
([#132](https://github.com/Matysh/houseplan-card/issues/132),
|
||||
[#185](https://github.com/Matysh/houseplan-card/issues/185)).
|
||||
- Collinear exterior walls now change thickness exactly at their saved
|
||||
breakpoint. After splitting a room, a 10 cm wall therefore keeps its full
|
||||
depth up to the divider without leaking onto an adjacent zero-thickness
|
||||
|
||||
@@ -27,6 +27,14 @@
|
||||
Create/«Оставить стенами»/Cancel применяются атомарно. Ctrl/Cmd+клик не
|
||||
добавляет лишнюю точку, пока для замыкания ещё недостаточно рёбер
|
||||
([#173](https://github.com/Matysh/houseplan-card/issues/173)).
|
||||
- Дверь, окно, ворота и открытый проём теперь можно поставить в законченный
|
||||
независимый отрезок «Стен». Проём прорезает его на всю глубину, движется
|
||||
вместе со своей стеной, сохраняет обычные состояния и действия Home
|
||||
Assistant, а при удалении стены удаляется только после отдельного
|
||||
подтверждения. Проём больше не разрывает структурную ось стены: замкнутый
|
||||
контур предлагает создать комнату, даже если в одной из стен уже есть проём
|
||||
([#132](https://github.com/Matysh/houseplan-card/issues/132),
|
||||
[#185](https://github.com/Matysh/houseplan-card/issues/185)).
|
||||
- Коллинеарные внешние стены теперь меняют толщину точно в сохранённой точке
|
||||
перехода. После разделения комнаты стена 10 см сохраняет полную толщину до
|
||||
перегородки и не продолжается по соседнему фасаду с нулевой толщиной; План,
|
||||
|
||||
@@ -69,6 +69,23 @@ fallback may show or rewrite it as a door. Before a permanent rollback, convert
|
||||
saved passages deliberately in a current version; automatic conversion is not
|
||||
performed because it would invent a leaf and binding semantics.
|
||||
|
||||
## Independent-wall opening host (#132)
|
||||
|
||||
`space.openings[].host` is an optional discriminated object
|
||||
`{kind:'partition', id:string, t:number}`. Its absence preserves the historical
|
||||
room-wall association. When present, the referenced partition in the same
|
||||
space and normalized `t` are authoritative; the legacy `x/y/angle` siblings
|
||||
remain a materialized compatibility projection for older readers. Full export,
|
||||
plan-only export, merge and optimization preserve the host object.
|
||||
|
||||
Current writes validate the reference, fit and non-overlap. They also reject a
|
||||
stale writer that keeps an existing opening but silently drops its host; this
|
||||
prevents a downgrade from converting it into a nearby room-wall opening. Old
|
||||
frontends may display only the materialized projection, so opening or editing a
|
||||
hosted opening with an old bundle is unsupported. A missing/invalid host is not
|
||||
re-associated automatically: current renderers fail dark and Plan offers an
|
||||
explicit rebind.
|
||||
|
||||
## Four-phase background default and transfer (#146)
|
||||
|
||||
The schema remains `settings.bg_mode: static | daynight` globally and per
|
||||
|
||||
@@ -52,6 +52,13 @@ it never inherits transparency merely by not being a window.
|
||||
So the light's masonry is cut by passages only and differs on purpose from the
|
||||
drawn one.
|
||||
|
||||
The rule is identical for a passage hosted by a finished independent wall.
|
||||
Only its explicit partition body (plus an exactly collinear covering room wall)
|
||||
is cut. Windows remain opaque to indoor Glow, one-sided door/gate/passage cuts
|
||||
remain opaque, and an invalid host fails dark. A window hosted by an independent
|
||||
wall never becomes a sunlight source; sunlight still belongs to exterior room
|
||||
windows.
|
||||
|
||||
Source placement follows the same geometry, fail-dark. If the source centre is
|
||||
inside an opaque wall body, a window tunnel, or an exterior door/gate opening,
|
||||
the source produces no Glow at all. It does not light the indoor half of the
|
||||
|
||||
+2
-1
@@ -179,7 +179,8 @@ Boolean, global + per-space (null = inherit), default OFF.
|
||||
For every opening of type «window» sitting on an EXTERIOR wall — a
|
||||
wall stretch with no other room on its outer side, decided by probing
|
||||
the existing room geometry just off both sides of the window; windows
|
||||
on interior walls do not participate, and open (virtual) boundaries
|
||||
on interior walls do not participate, windows explicitly hosted by independent
|
||||
partitions do not participate, and open (virtual) boundaries
|
||||
never qualify because both sides are rooms — the card draws a wedge
|
||||
when BOTH hold:
|
||||
|
||||
|
||||
@@ -52,6 +52,29 @@
|
||||
- [ ] Пять passage-мутантов из `scripts/mutation-gate.mjs` пойманы своими
|
||||
guards до передачи в review. [auto: mutation-gate]
|
||||
|
||||
## Independent-wall openings and structural axes (#132, #185)
|
||||
|
||||
- [ ] Door/window/gate/passage placement on a finished independent wall stores
|
||||
`host.kind/id/t`; a coincident room wall chooses that explicit host, while
|
||||
crossing or duplicate-host ties are rejected. [auto: opening-placement,
|
||||
partition-openings]
|
||||
- [ ] Every hosted type cuts only its host full-depth in Plan/View/Static/Iso;
|
||||
exact composite room masonry is also cut, nearby bodies remain intact,
|
||||
and malformed hosts fail dark. [auto: physical-geometry,
|
||||
smoke_partition_openings]
|
||||
- [ ] Rigid host drag preserves `t` and updates projections atomically; delete
|
||||
lists hosted openings, Cancel changes nothing, Confirm cascades in one
|
||||
Undo/Redo command. [auto: partition-openings, smoke_partition_openings]
|
||||
- [ ] Contact/lock actions keep existing security rules; passage stays inert;
|
||||
windows and exterior passages stay opaque to Glow, and partition windows
|
||||
produce no sun wedge. [auto: runtime contracts, smoke_glow]
|
||||
- [ ] Door/window/gate/passage presentation gaps do not split the structural
|
||||
axis used by the Walls face graph; real `open_spans` still do. [auto:
|
||||
plan-snap-overlay, smoke_room_autoclose, smoke_partition_openings]
|
||||
- [ ] Backend rejects missing host references, out-of-range `t`, non-fitting or
|
||||
overlapping hosted openings and stale host stripping; exports round-trip
|
||||
the host. [auto: test_validation, test_ha_import_export]
|
||||
|
||||
|
||||
## Device value badge (#90)
|
||||
|
||||
|
||||
+23
-9
@@ -56,7 +56,7 @@ House Plan — локальная интеграция и две Lovelace-кар
|
||||
| Пространство | Этаж, двор, гараж, отдельное строение | Подложку, масштаб сетки, комнаты, стены, проёмы, декор и настройки отображения |
|
||||
| Комната | Только замкнутый контур | Название, необязательную HA-зону, источники температуры/влажности и локальную заливку |
|
||||
| Стена | Сторона контура комнаты | Необязательную толщину и виртуальные участки общей границы |
|
||||
| Проём | Дверь, окно, открытый проём или ворота на стене | Размер и, где применимо, ориентацию, контактный датчик и замок |
|
||||
| Проём | Дверь, окно, открытый проём или ворота на стене комнаты либо законченном независимом отрезке | Размер и, где применимо, ориентацию, контактный датчик и замок |
|
||||
| Маркер | Значок устройства на плане | Привязку к HA, комнату, действие, внешний вид, свет, описание и файлы |
|
||||
| Декор | Элемент подложки | Линию, фигуру, текст, мебель или положение изображения плана |
|
||||
|
||||
@@ -287,7 +287,8 @@ desktop: для точного рисования, Resize, модификато
|
||||
независимыми стенами; отдельной кнопки завершения нет.
|
||||
5. Когда новый сегмент замкнёт область собственными стенами, существующими
|
||||
стенами либо вычисляемой конечной, T- или X-точкой, откроется диалог комнаты.
|
||||
Разрыв проёма стеной не считается. Если возникло несколько областей, диалоги
|
||||
Проём не разрывает структурную ось стены и не мешает замыканию; виртуальный
|
||||
участок остаётся настоящим разрывом. Если возникло несколько областей, диалоги
|
||||
идут от меньшей площади к большей.
|
||||
6. Для каждой области задайте название и свободную HA-зону и нажмите
|
||||
«Сохранить», либо выберите **Оставить стенами**. Cancel/Esc отменяет всю
|
||||
@@ -299,7 +300,7 @@ desktop: для точного рисования, Resize, модификато
|
||||
нарисованных стен видны тонкие осевые линии и точки их концов. Увеличенная
|
||||
точка показывает точное место следующего соединения: конец стены имеет
|
||||
приоритет, а увеличенная точка посреди линии создаёт T-соединение, не разрезая
|
||||
существующую стену. В проёмах соединительной линии нет. Для точного наведения и
|
||||
существующую стену. Осевая линия остаётся непрерывной и в месте проёма. Для точного наведения и
|
||||
предпросмотра рекомендуется редактор на компьютере; tap на сенсорном экране
|
||||
тоже выполняет привязку, но отдельный hover до касания не показывается.
|
||||
|
||||
@@ -327,7 +328,7 @@ T-соединение входит в проходящую стену без в
|
||||
| Стены | Непрерывную цепочку стен; при замыкании предлагает комнаты, при выходе сохраняет независимые стены | Площадь появляется только у подтверждённой комнаты | Физические сегменты блокируют свет; проёмы пропускают его | Частичное перекрытие комнат запрещено; отдельного инструмента «Перегородка» нет |
|
||||
| Колонна | Квадратную или круглую опору | Не меняет площадь комнаты | Блокирует свет внутри своей формы | Имеет одну форму/размер/поворот, но не является стеной или комнатой |
|
||||
| Граница | Виртуальный участок общей стены | Не меняет геометрию и площадь | Свет проходит, физическая стенка не рисуется | Работает только на общей границе соседних комнат |
|
||||
| Проём | Дверь, окно, открытый проём или ворота на стене | Не меняет площадь | Дверь/ворота пропускают свет по состоянию, открытый проём — всегда между комнатами; окно может давать солнечный луч | Должен целиком помещаться на подходящем отрезке стены |
|
||||
| Проём | Дверь, окно, открытый проём или ворота на стене комнаты либо законченном независимом отрезке | Не меняет площадь | Дверь/ворота пропускают свет между полами, открытый проём — всегда; только наружное окно комнаты может давать солнечный луч | Должен целиком помещаться на одном подходящем отрезке стены |
|
||||
|
||||
Остальные операции меняют уже созданную геометрию:
|
||||
|
||||
@@ -376,7 +377,8 @@ Resize показывает живые длины и чистую площадь
|
||||
или круглая форма и внешний размер 1–150 см; у квадрата доступен поворот, у
|
||||
круга размер означает диаметр. Они вычитаются из чистой площади, перекрывают
|
||||
Glow и солнечные лучи, но при Resize комнаты остаются на месте. За пределами
|
||||
комнат эти объекты не создают пол или фон.
|
||||
комнат эти объекты не создают пол или фон. Проём в независимой стене движется
|
||||
вместе с выбранным отрезком и остаётся в том же относительном месте.
|
||||
|
||||
### Толщина стены
|
||||
|
||||
@@ -418,7 +420,8 @@ Glow и солнечные лучи, но при Resize комнаты оста
|
||||
|
||||
## 9. Двери, окна, открытые проёмы, ворота и замки
|
||||
|
||||
Проём всегда привязан к стене. Дверь и открытый проём по умолчанию создаются
|
||||
Проём всегда привязан к одному отрезку стены комнаты или к одному законченному
|
||||
независимому отрезку «Стен». Дверь и открытый проём по умолчанию создаются
|
||||
шириной 90 см, окно — 120 см, ворота — 300 см. Допустимый размер в интерфейсе:
|
||||
20–600 см с шагом 5 см.
|
||||
|
||||
@@ -432,8 +435,18 @@ Glow и солнечные лучи, но при Resize комнаты оста
|
||||
|
||||
Наводить указатель перед кликом необязательно: клик по телу толстой стены сам
|
||||
находит её ось и открывает тот же диалог. Виртуальная стена не принимает проёмы.
|
||||
Если несколько стен сходятся в одной точке, House Plan выбирает одну физическую
|
||||
секцию детерминированно; при необходимости поставьте проём чуть дальше от стыка.
|
||||
Если независимая стена точно совпадает со стеной комнаты, House Plan использует
|
||||
её как явную привязку и прорезает единое составное тело. Не совпадающая
|
||||
геометрически неоднозначность у стыка не угадывается: поставьте проём чуть
|
||||
дальше от узла.
|
||||
|
||||
Проём можно перетаскивать только вдоль того же независимого отрезка. При его
|
||||
удалении House Plan показывает отдельное подтверждение со всеми привязанными
|
||||
проёмами; отмена ничего не меняет, подтверждение удаляет стену и перечисленные
|
||||
проёмы одним шагом Undo. Если привязанный отрезок пропал из старого или
|
||||
повреждённого конфига, в Редакторе плана проём помечается как потерявший стену
|
||||
и предлагает перепривязку; в Просмотре он не рисуется и не создаёт ложный
|
||||
проход для света.
|
||||
|
||||
Ворота по данным и поведению равны двери: могут иметь датчик открытия и замок, вырезают стену и пропускают Glow. Отличается только символ: две половинные створки без большой дуги, приоткрытые наружу на 10°. С датчиком угол меняется от 0° до 10°; без датчика показаны статически открытыми на 10°. Выбор распашных/откатных и других типов пока не предусмотрен.
|
||||
|
||||
@@ -493,7 +506,8 @@ Glow и солнечные лучи, но при Resize комнаты оста
|
||||
продолжает текущую заливку/Glow-base через тоннель. Отображение уже существующих
|
||||
дверей, окон и ворот в Static в рамках этой функции намеренно не менялось.
|
||||
|
||||
Солнце проходит только через наружные окна. Внутренние окна солнечных лучей не создают.
|
||||
Солнце проходит только через наружные окна комнаты. Внутренние окна и окна в
|
||||
независимых стенах солнечных лучей не создают.
|
||||
|
||||
<!-- docs-section: devices -->
|
||||
|
||||
|
||||
@@ -98,6 +98,10 @@ Header in View: space tabs, device count, zoom cluster. Nothing else.
|
||||
partitions. Closing one or more planar faces opens the room queue; its
|
||||
decisions are buffered and applied as one Undo/Redo transaction. Re-selecting
|
||||
Walls, Reset, pan, pinch and pointer cancellation are not finish actions.
|
||||
- Opening places every existing opening type on a room wall or a finished
|
||||
independent Walls segment. A hosted opening moves with that segment; deleting
|
||||
the segment requires an explicit cascade confirmation. Its physical gap does
|
||||
not break the structural wall axis used to recognize closed rooms (#185).
|
||||
- Boundary is contextual: two points on one solid shared wall make the selected
|
||||
stretch virtual; one click on an existing dashed stretch restores it whole.
|
||||
Outer walls and room boundaries hidden under independent masonry are not
|
||||
@@ -175,6 +179,9 @@ layer you cannot see is a layer you cannot edit.
|
||||
places a square column whose side is the current Thickness value. Neither an
|
||||
independent wall nor a column creates a room or HA area by itself. A closed
|
||||
independent-wall ring subtracts only its wall body from room floor.
|
||||
- Door, window, gate and passage may be hosted by one finished independent wall
|
||||
segment. Drafts and columns are never opening targets. Missing hosts fail
|
||||
dark and expose a rebind action only in Plan.
|
||||
- **Select** is the only mode in which these objects intercept input. It offers
|
||||
rigid grid-bound drag, double-click/tap properties, Delete, and a rotate
|
||||
handle for square columns (5° steps; Shift is free). Draft Delete removes the
|
||||
|
||||
@@ -200,6 +200,14 @@ fully occluded instead of leaking around its own masonry. The same fail-dark
|
||||
placement rule applies to window tunnels and exterior door/gate openings;
|
||||
interior passages remain valid source positions (#92).
|
||||
|
||||
An opening with explicit `host:{kind:'partition',id,t}` is resolved from that
|
||||
partition alone and subtracted full-depth from its raw body before the joined
|
||||
presentation union. A precisely collinear room wall covering the same interval
|
||||
is cut as a composite; a crossing/nearby body is not. Host move keeps `t`,
|
||||
delete requires cascade confirmation, and malformed/orphan hosts remain opaque.
|
||||
Opening cuts change physical masonry, not the structural wall axes used for
|
||||
room-face detection (#185).
|
||||
|
||||
`physicalBodySet()` separates raw draft/partition/column bodies from computed
|
||||
junction patches and their joined geometry. Raw bodies remain authoritative for
|
||||
hit testing, selection, drag, properties, deletion, history and furniture
|
||||
|
||||
Reference in New Issue
Block a user