30 KiB
Свободные цепочки стен и разная толщина при рисовании — ТЗ
Статус: согласовано и отложено (2026-08-05).
Реализацию не начинать до отдельного прямого запроса владельца проекта. Этот документ фиксирует требования и принятые продуктовые решения для будущей реализации.
1. Цель
Добавить возможность:
- Рисовать одну цепочку стен, задавая отдельную толщину каждому следующему сегменту.
- Оставлять на плане незамкнутые стены, не принадлежащие комнате или контуру.
- Переключаться на другие инструменты без удаления уже нарисованной незамкнутой цепочки.
- После возвращения в инструмент или перезагрузки продолжать сохранённую цепочку с одного из её концов.
- Замыкать цепочку с выбором результата:
- создать комнату;
- оставить замкнутую цепочку самостоятельными стенами.
2. Текущее состояние и архитектурная причина изменения
Сейчас комната является единственным первичным источником геометрии стен:
- контур хранится в
rooms[].poly; - стены вычисляются из рёбер комнат;
walls[]хранит толщину рёбер, но не самостоятельную геометрию;- незаконченный
_pathявляется временным состоянием редактора; - смена инструмента очищает
_path; - сервер удаляет старое legacy-поле
segments; - статическая карточка, проёмы, привязки и расчёт видимой области получают геометрию стен преимущественно из комнат.
Поэтому функция должна вводить новую сохраняемую сущность. Простое сохранение
текущего _path не решает рендер, восстановление после перезагрузки,
посегментную толщину и интеграцию с остальными инструментами.
3. Термины
- Свободная цепочка стен — упорядоченная открытая или замкнутая ломаная, которая не принадлежит комнате.
- Сегмент — отрезок между двумя соседними вершинами; у замкнутой цепочки последний сегмент соединяет последнюю вершину с первой.
- Активная цепочка — цепочка, которую пользователь сейчас продолжает.
- Линейная стена — существующий вариант стены без объёмного тела.
- Толстая стена — стена с положительной толщиной в сантиметрах.
- Виртуальный участок — существующий
open_span. Отсутствие толщины само по себе не превращает стену в виртуальную.
4. Модель данных
В конфигурацию пространства добавить необязательное поле wall_paths:
interface WallPathCfg {
id: string;
points: Array<[number, number]>;
closed?: boolean;
segments: Array<{
cm?: number;
}>;
}
Пример открытой цепочки:
{
"id": "wp_01",
"points": [[0.10, 0.20], [0.40, 0.20], [0.40, 0.55]],
"segments": [{ "cm": 15 }, { "cm": 25 }]
}
Пример замкнутой самостоятельной цепочки:
{
"id": "wp_02",
"closed": true,
"points": [[0.10, 0.20], [0.40, 0.20], [0.40, 0.55]],
"segments": [{ "cm": 15 }, { "cm": 20 }, { "cm": 25 }]
}
Инварианты:
- открытая цепочка:
points.length >= 2иsegments.length === points.length - 1; - замкнутая цепочка: минимум три пригодные вершины и
segments.length === points.length; - у замкнутой цепочки последний сегмент идёт от последней вершины к первой;
- первая точка не дублируется в конце массива;
cmотсутствует для линейной стены без объёмного тела;- отсутствие
cmне означаетopen_span; - соседние точки не совпадают;
idуникален в пределах пространства;- координаты нормализованы по тем же правилам, что и
rooms[].poly; - старое корневое поле
segmentsне возвращается и продолжает считаться legacy.
Толщина хранится внутри wall_paths, потому что существующий walls[]
привязан к геометрии комнат и удаляет осиротевшие ключи при нормализации.
5. Рисование и посегментная толщина
5.1 Начало цепочки
- Пользователь выбирает инструмент «Стены».
- Первый клик создаёт временную начальную точку.
- Второй клик создаёт первый сегмент и постоянную запись в
wall_paths. - Одиночная точка без сегмента не сохраняется и исчезает при смене инструмента или перезагрузке.
5.2 Значение поля толщины
Поле толщины означает толщину следующего сегмента:
- значение фиксируется при клике второй точки сегмента;
- изменение поля не меняет уже созданные сегменты;
- live-preview использует текущее значение;
- пустое значение создаёт линейную стену;
- конфиг всегда хранит сантиметры независимо от выбранной системы единиц;
- допустимая толщина — 1–100 см;
- нечисловое значение и значение вне диапазона показывают ошибку и не дают создать сегмент.
Пример: пользователь может последовательно создать сегменты 15 см, 25 см, линейный сегмент и снова 15 см в пределах одной цепочки.
6. Сохранение при смене инструмента
Как только создан первый сегмент:
- цепочка становится частью конфигурации пространства;
- каждый следующий законченный сегмент сразу добавляется в локальную конфигурацию;
- запись на сервер может оставаться debounce-записью;
- перед сменой этажа, режима, пространства или размонтированием карточки ожидающая запись принудительно завершается;
- переключение на другой инструмент прекращает активное рисование, но не удаляет цепочку;
- цепочка остаётся видимой во всех предусмотренных режимах.
Активное состояние редактора не требуется хранить на сервере.
7. Продолжение цепочки
Принятое поведение:
- в той же сессии после возвращения в инструмент «Стены» автоматически продолжается последняя активная цепочка;
- после перезагрузки пользователь выбирает конечную точку кликом;
- конечные точки доступных цепочек подсвечиваются при наведении;
- клик по последней точке продолжает цепочку в текущем направлении;
- клик по первой точке разворачивает порядок точек и сегментных параметров, после чего продолжает цепочку с этого конца;
- при продолжении поле толщины принимает значение крайнего сегмента выбранного конца, после чего пользователь может его изменить;
- из середины сегмента и из внутренней вершины нельзя создавать ответвление.
7.1 Соединение двух цепочек
Разрешается соединение конец с концом:
- при необходимости одна или обе цепочки разворачиваются;
- точки и параметры сегментов объединяются без потери соответствия;
- одна исходная запись сохраняет
id, вторая удаляется; - нулевой соединительный сегмент не создаётся;
- соединение через середину сегмента не поддерживается;
- если соединение приводит к замыканию, открывается диалог завершения, описанный ниже.
8. Замыкание цепочки
При клике по начальной точке активной цепочки с минимум тремя пригодными вершинами:
- Проверяется замыкающий сегмент.
- Проверяются нулевая длина, самопересечения и недопустимые наложения.
- Открывается существующий диалог создания комнаты.
- Внизу диалога добавляется второе действие «Оставить замкнутыми стенами».
До выбора результата исходная открытая цепочка остаётся в конфигурации, а замыкающий сегмент хранится как ожидающее локальное изменение. Это исключает потерю стен при отмене диалога или ошибке сохранения.
8.1 Основное действие: создать комнату
При обычном сохранении комнаты одной транзакцией:
- создаётся
room.poly; - свободная цепочка удаляется из
wall_paths; - толщина каждого сегмента переносится в существующую модель
walls[]; - замыкающий сегмент получает толщину, выбранную перед замыканием;
- если новый контур разделяет физическую стену с существующей комнатой, побеждает толщина уже существующей стены.
Одна физическая стена не должна иметь две конкурирующие толщины.
8.2 Второе действие: оставить замкнутыми стенами
При выборе «Оставить замкнутыми стенами»:
- комната не создаётся;
- заливка, название и площадь не появляются;
- замыкающий сегмент добавляется в
segments; - цепочка сохраняется с
closed: true; - активное рисование этой цепочки завершается.
Для продолжения замкнутой цепочки сначала потребуется удалить один из её сегментов. Перемещение вершин в первой версии не предусмотрено.
8.3 Отмена и ошибки
- отмена диалога возвращает пользователя к открытой сохранённой цепочке;
- ошибка записи не удаляет исходный
wall_path; - удаление
wall_pathи добавление комнаты выполняются атомарно в одном изменении локальной конфигурации.
9. Рендер
Свободные стены отображаются:
- в редакторе плана — всегда;
- в остальных редакторах — по действующим правилам отображения границ;
- в режиме просмотра — при включённом
show_borders; - в основной и статической карточке.
9.1 Толстые стены
Для сегментов с толщиной необходимо:
- строить тело относительно центральной линии;
- объединять тела свободных и комнатных стен перед отрисовкой;
- использовать существующие заливку и штриховку стен;
- корректно формировать L- и T-образные стыки;
- объединять сегменты разной толщины без щелей;
- ограничивать miter на острых углах и переходить к bevel;
- завершать действительно свободный конец ровным поперечным срезом;
- прятать окончание линейного сегмента под телом примыкающей толстой стены.
9.2 Виртуальные участки
Действующее правило сохраняется:
- в режиме просмотра виртуальный пунктир рисуется под телом стены;
- в редакторах пунктир рисуется поверх тела полностью от центральной линии;
- свободная линейная стена не является виртуальным участком.
9.3 Границы содержимого
wall_paths участвуют в расчёте видимой области с учётом половины толщины.
План, состоящий только из свободных стен, должен корректно центрироваться и
масштабироваться.
10. Влияние на комнаты, площадь и свет
До преобразования в комнату свободная цепочка:
- не создаёт заливку пола;
- не создаёт площадь и подпись м²;
- не создаёт название комнаты;
- не связывается с HA area;
- не изменяет внутренний контур существующей комнаты;
- не вычитает площадь из комнаты;
- не разделяет комнату на световые зоны;
- не участвует в распространении света между комнатами;
- не создаёт солнечные лучи или дверной тоннель.
Свободная стена является отображаемой физической геометрией, но не топологической границей помещения.
Автоматическое распознавание комнат из произвольного графа пересекающихся стен не входит в задачу. Комната создаётся только явным замыканием цепочки и выбором основного действия в диалоге.
11. Работа инструментов
11.1 Толщина стены
Инструмент позволяет выбрать сегмент свободной цепочки и изменить только его толщину. Действие «Применить ко всей цепочке» можно добавить позднее как необязательное улучшение.
11.2 Удаление
- единственный сегмент: удалить всю цепочку;
- крайний сегмент: укоротить цепочку;
- средний сегмент: разбить цепочку на две независимые цепочки;
- одна часть сохраняет старый
id, вторая получает новый; - часть без сегментов не сохраняется;
- удаление сегмента замкнутой цепочки превращает её в открытую цепочку с
closed: false.
11.3 Resize и перемещение вершин
Не входят в первую версию. Resize продолжает работать только с комнатами. Перемещение вершин свободных цепочек вынесено в следующий этап.
11.4 Merge и Split
Не считают свободные цепочки комнатами и не изменяют их автоматически.
11.5 Выравнивание
Глобальное выравнивание плана по сетке должно включать точки wall_paths, не
нарушая соответствие сегментов и их толщин.
11.6 Привязка мебели
Свободные физические стены рекомендуется включить в привязку мебели, поскольку для пользователя они являются обычными стенами.
11.7 Двери и окна
Двери и окна устанавливаются только в стены комнат.
- свободные открытые и замкнутые цепочки не являются целью инструмента проёмов;
- при наведении на них инструмент не показывает доступную привязку;
- проёмы становятся доступны только после преобразования цепочки в комнату.
12. Undo, Escape и Reset
Рекомендуемая безопасная модель:
Ctrl+ZилиEscapeудаляет последний сегмент, добавленный в текущей сессии редактирования;- при продолжении старой цепочки Undo не удаляет части, существовавшие до её выбора;
- когда добавленных в сессии сегментов больше нет, следующее действие снимает активность с цепочки;
- для новой цепочки отмена первого сегмента удаляет
wall_path, оставляя временную стартовую точку; - следующая отмена удаляет стартовую точку.
Reset следует трактовать как «Отменить текущие изменения цепочки»:
- новая цепочка возвращается к состоянию до текущего рисования;
- у продолженной цепочки удаляются только добавленные в этой сессии сегменты;
- ранее сохранённая часть не удаляется;
- полное удаление выполняется инструментом удаления.
13. Сохранение и конкурентные изменения
- каждый завершённый сегмент сразу попадает в локальную конфигурацию;
- запись на сервер выполняется существующей сериализованной очередью;
- ожидающий debounce принудительно завершается при уходе из контекста;
activeWallPathIdявляется только состоянием редактора;- после перезагрузки продолжение начинается выбором конечной точки;
- преобразование в комнату не должно сначала удалить цепочку и только потом пытаться сохранить комнату;
- конфликты ревизий обрабатываются действующим механизмом конфигурации.
14. Серверная валидация и ограничения
Предлагаемые пределы:
- не более 500 цепочек на пространство;
- не более 500 точек в одной цепочке;
- не более 2000 свободных сегментов суммарно;
- только конечные числовые координаты в допустимых границах холста;
- толщина 1–100 см;
- уникальные идентификаторы;
- отсутствие совпадающих соседних точек;
- точное соответствие количества сегментов типу цепочки;
- минимум три пригодные вершины у замкнутой цепочки;
- лишние или повреждённые поля отклоняются либо нормализуются единообразно.
Существующие конфигурации миграции не требуют: wall_paths является
необязательным полем.
15. Геометрические и UX edge cases
Обязательно учесть:
- смену инструмента после единственной точки;
- двойной клик в одной координате;
- нулевой и очень короткий сегмент;
- возврат в предыдущую точку;
- попытку замкнуть цепочку с недостаточным количеством вершин;
- самопересечение замыкающего или обычного сегмента;
- прохождение свободной стены через существующую комнату;
- полное или частичное совпадение с комнатной стеной;
- полное или частичное совпадение двух свободных цепочек;
- пересечение двух цепочек без общей вершины;
- соединение двух цепочек с необходимостью развернуть обе;
- попытку соединиться с серединой сегмента;
- T-образный стык стен разной толщины;
- переход толстая → линейная → толстая;
- острый угол и ограничение miter;
- удаление среднего сегмента открытой и замкнутой цепочки;
- отмену диалога комнаты;
- ошибку сервера при завершении комнаты;
- изменение единиц измерения во время рисования;
- координаты вне сетки;
- достижение лимита точек или общего размера конфигурации;
- план без комнат, содержащий только свободные стены;
- скрытые границы в режиме просмотра;
- конкурентное редактирование в другой вкладке.
16. Критерии готовности первой версии
- Незамкнутая цепочка с хотя бы одним сегментом не исчезает при смене инструмента.
- Она сохраняется после перезагрузки карточки и Home Assistant.
- В той же сессии рисование продолжается автоматически.
- После перезагрузки цепочку можно продолжить с любого конца.
- Две цепочки можно соединить конец с концом.
- Ответвление из середины сегмента невозможно.
- Толщина задаётся отдельно каждому следующему сегменту.
- Сегменты разной толщины образуют корректное тело без щелей.
- При замыкании доступны «Создать комнату» и «Оставить замкнутыми стенами».
- Создание комнаты переносит толщины и атомарно удаляет свободную цепочку.
- При общей стене сохраняется ранее существовавшая толщина.
- Отмена диалога не удаляет свободную цепочку.
- Замкнутая самостоятельная цепочка сохраняется без комнаты, заливки и площади.
- Свободные стены видны в основной и статической карточке.
- Они учитываются при масштабировании и центрировании.
- Удаление среднего сегмента корректно разделяет цепочку.
- Двери и окна нельзя устанавливать в свободные стены.
- Старые конфигурации продолжают работать без миграции.
17. Не входит в первую версию
- перемещение вершин свободных стен;
- Resize свободных цепочек;
- ответвление из середины сегмента;
- автоматическое распознавание комнат из сети стен;
- установка дверей и окон в свободные стены;
- участие свободных стен в комнатной топологии, площади и распространении света;
- отдельные материалы, стили и свойства сторон свободной стены.
18. Предлагаемый порядок будущей реализации
- Схема
wall_paths, серверная валидация и чистые операции над цепочками. - Посегментная толщина в инструменте «Стены».
- Сохранение, автоматическое продолжение и выбор конца после перезагрузки.
- Соединение цепочек конец с концом.
- Диалог замыкания и два варианта завершения.
- Геометрия тел и стыков стен разной толщины.
- Основная и статическая карточки, расчёт видимой области.
- Инструменты толщины, удаления, привязки и выравнивания.
- Документация и тестовый чек-лист.
- Прогон тестов только перед пре-релизом согласно принятому процессу.
19. Зафиксированные ответы владельца
| Вопрос | Решение |
|---|---|
| Что делать при замыкании? | Открывать диалог комнаты; внизу сразу добавить второе действие «Оставить замкнутыми стенами». |
| Как продолжать цепочку? | В той же сессии продолжать автоматически; после перезагрузки выбирать конец кликом. |
| Как соединять цепочки? | Соединять конец с концом; ответвление из середины сегмента не поддерживать. |
| Разрешать двери и окна? | Нет, проёмы устанавливаются только в стены комнат. |
| Нужны ли перемещение вершин и Resize? | Вынести в следующий этап; в первой версии оставить продолжение, изменение толщины и удаление сегментов. |
| Какая толщина побеждает у общей стены? | Уже существующая физическая стена определяет толщину. |
| Сохранять ли одиночную точку? | Нет, точка без сегмента остаётся временной. |