mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
docs: ТЗ #53 — внутренние размеры комнат грань-в-грань и внешние размеры граней
Issue: #53 User-Visible: no
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
# ТЗ #52 — Размеры стен и площади в View
|
||||
|
||||
> **Закрыто 2026-09-07 решением владельца, объединено с #53.** Слой размеров
|
||||
> в режиме просмотра не делается. Разделы «Площади» и «Длины» ниже остаются
|
||||
> действующим контрактом измерений и переиспользуются опцией «Показывать
|
||||
> размеры» экспорта PDF (#53); разделы «Настройка» и «Collision/zoom» к
|
||||
> экрану больше не относятся — раскладку на печатном листе задаёт ТЗ #53.
|
||||
> в режиме просмотра не делается. Раздел «Площади» остаётся действующим
|
||||
> контрактом и переиспользуется опцией «Отображать размеры» экспорта PDF
|
||||
> (#53). Раздел «Длины» (осевые пролёты стен) на бумаге НЕ применяется:
|
||||
> ТЗ #53 §7.3 печатает внутренние размеры каждой комнаты грань-в-грань и
|
||||
> внешние размеры наружных граней. «Настройка» и «Collision/zoom» к экрану
|
||||
> больше не относятся.
|
||||
|
||||
- Issue: https://github.com/Matysh/houseplan-card/issues/52 (закрыт, см. #53)
|
||||
- Приоритет: P3
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
- **Приоритет:** P3 · **Тип:** feature · **Трек:** полный (новый UX, новый ленивый чанк, новый формат вывода)
|
||||
- **Оценка:** пользовательская ценность 7/10 («распечатал план для электрика/страховой»), уникальность против конкурентов 9/10 (у них нет геометрии для печати); сложность 6/10; риск 4/10 (шрифты и растр в PDF, детерминизм)
|
||||
- **Решения владельца:** 2026-08-15 (исключение из `docs/SCOPE.md`, задача остаётся в очереди); 2026-09-07 (#52 объединён сюда, слой размеров во View не делается; кнопка-принтер, маленький диалог, только текущее пространство, без устройств, подложка — если есть у пространства)
|
||||
- **Связанные контракты:** `docs/specs/052-view-dimensions.md` (§ Площади, § Длины — контракт измерений), `docs/WALL-THICKNESS.md`, `docs/BACKDROP.md`, `docs/FURNITURE.md`, `docs/SCOPE.md`, #456 (образец диалога у пространства), #476 (диалог с кнопкой)
|
||||
- **Связанные контракты:** `docs/specs/052-view-dimensions.md` (§ Площади — контракт площадей; § Длины на бумаге не применяется, см. §7.3), `docs/WALL-THICKNESS.md`, `docs/BACKDROP.md`, `docs/FURNITURE.md`, `docs/SCOPE.md`, #456 (образец диалога у пространства), #476 (диалог с кнопкой)
|
||||
|
||||
## 1. Сценарий
|
||||
|
||||
@@ -110,29 +110,50 @@
|
||||
|
||||
### 7.3. Размеры и площади (галочка, по умолчанию включена)
|
||||
|
||||
Правила измерения — **дословно из `052-view-dimensions.md`**, § Площади и
|
||||
§ Длины: площадь чистого пола через `geometryArea`; уникальные физические
|
||||
осевые пролёты стен из нормализованной модели, общая стена один раз,
|
||||
виртуальные границы без длины, проёмы не вычитаются, колонны без линейного
|
||||
размера, коллинеарный мусор через существующую компакцию. Значения —
|
||||
`formatArea`/`formatLength`, единицы из HA (metric/imperial), масштаб сетки
|
||||
пространства (`cell_cm`).
|
||||
Решение владельца 2026-09-07: **внутренние размеры каждой комнаты,
|
||||
достаточные, чтобы начертить её независимо от остальной планировки, и
|
||||
внешние размеры каждой наружной грани планировки.** Осевые пролёты стен из
|
||||
`052-view-dimensions.md` § Длины на бумаге не используются; из #52
|
||||
переиспользуется только § Площади.
|
||||
|
||||
Раскладка на бумаге (в отличие от экрана — без зума и без указателя):
|
||||
**Площадь** — чистый пол через `geometryArea` (тот же резолвер, что у
|
||||
карточки комнаты), `formatArea`, единицы из HA. Печатается под названием
|
||||
комнаты кеглем на 1 pt меньше; без названия — одна строка площади в той же
|
||||
точке.
|
||||
|
||||
- **площадь** — под названием комнаты тем же кеглем минус 1 pt; если
|
||||
названия нет или оно скрыто — в той же точке одна строка площади;
|
||||
- **внешние стены** — размерные линии *снаружи* внешнего контура:
|
||||
выносные линии перпендикулярно грани, размерная линия с засечками
|
||||
(архитектурная нотация), текст над линией, читаемый слева направо или
|
||||
снизу вверх, никогда вверх ногами; пролёты одной прямой грани — цепочкой
|
||||
на одной размерной линии;
|
||||
- **внутренние стены и перегородки** — подпись вдоль пролёта с той стороны,
|
||||
где больше свободного пола; кегль 7 pt;
|
||||
- **коллизии** — детерминированно: приоритет внешний контур → длинный
|
||||
пролёт → короткий; подпись, чей прямоугольник пересекает уже размещённый
|
||||
или выходит за поле листа, скрывается; пролёты короче 40 см на бумаге не
|
||||
подписываются.
|
||||
**Внутренние размеры комнаты.** Контур — внутренние грани стен комнаты
|
||||
(чистый пол, тот же полигон, что у площади), после компакции коллинеарных
|
||||
рёбер. Каждое ребро контура получает длину `formatLength(segmentCm)` —
|
||||
расстояние от грани до грани, а не по оси стены. Подпись — внутри комнаты,
|
||||
параллельно ребру, отступ 2 мм от грани, читается слева направо или снизу
|
||||
вверх. Для комнаты-прямоугольника печатаются все четыре ребра; если при
|
||||
коллизии места не хватает, второе ребро той же длины и того же направления
|
||||
может быть опущено — комната остаётся вычерчиваемой (по одной длине на
|
||||
направление). Для непрямоугольных контуров опускать нельзя: каждое ребро —
|
||||
свой размер. Рёбра короче 30 см на бумаге не подписываются, но их длину
|
||||
можно восстановить из соседних — такие рёбра помечаются короткой засечкой
|
||||
без числа. Проёмы на длину ребра не влияют (ребро — грань стены целиком).
|
||||
|
||||
**Внешние размеры планировки.** Внешний контур — объединение физических
|
||||
стен пространства по наружным граням (тот же силуэт, что у контура
|
||||
«вписать всё»), после компакции. Каждая прямая наружная грань получает
|
||||
размерную линию *снаружи* контура на расстоянии 6 мм: выносные линии
|
||||
перпендикулярно грани, засечки, число над линией. Грани одной прямой
|
||||
(сдвиг по наружной стене из-за выступа) — отдельные размеры, каждая своя;
|
||||
суммарной цепочки нет (её даёт сумма). Несколько несвязных наружных
|
||||
контуров (пристройка, отдельное строение в пространстве) — каждый со
|
||||
своими внешними размерами. Виртуальные (нулевые) границы наружного
|
||||
контура длину не получают.
|
||||
|
||||
**Единицы и точность** — `formatLength`/`formatArea`, metric/imperial из
|
||||
HA, масштаб сетки `cell_cm`; числа совпадают с живой линейкой ресайза на
|
||||
том же ребре и с площадью в карточке комнаты.
|
||||
|
||||
**Коллизии** — детерминированно: приоритет внешние размеры → площади →
|
||||
внутренние рёбра длинные → короткие; подпись, чей прямоугольник
|
||||
пересекает уже размещённый или выходит за поле листа, скрывается (кроме
|
||||
случая «непрямоугольный контур» — там уменьшается кегль до 6 pt прежде
|
||||
чем скрыться). Никакая подпись не печатается вверх ногами.
|
||||
|
||||
### 7.4. Мебель и другой декор (галочка, по умолчанию — Q1)
|
||||
|
||||
@@ -293,8 +314,8 @@ View. `localStorage` — единственное новое состояние,
|
||||
| AC2 | Галочка «подложка» присутствует только у пространства с `bg`; состояние галочек переживает перезагрузку страницы | smoke |
|
||||
| AC3 | «Сохранить» скачивает файл `houseplan-<slug>-<дата>.pdf`; в файле ровно одна страница A4 (MediaBox 595.28×841.89 или наоборот) с ориентацией по пропорциям | smoke (перехват download) + unit на writer |
|
||||
| AC4 | Геометрия §7.1: стены с толщиной залиты, нулевые — штрих, проёмы с разрывами, общая стена один раз, ни одного маркера/состояния/цвета комнаты | golden по растру PDF (§13) + unit по операторам контент-потока |
|
||||
| AC5 | Размеры: значения площадей и длин побайтно совпадают с `formatArea`/`formatLength` на тех же данных, что карточка комнаты и живая линейка (контракт #52) | unit (общие фикстуры с тестами #52-контракта: прямоугольник, L, общая толстая стена, виртуальная граница, партиция) |
|
||||
| AC6 | Внешние размерные линии снаружи контура, текст не вверх ногами, коллизии детерминированы; пролёты < 40 см без подписи | unit на раскладку + golden |
|
||||
| AC5 | Внутренние размеры: у каждой комнаты подписано каждое ребро чистого контура (грань-в-грань), значения совпадают с `formatLength` живой линейки на том же ребре, площадь — с карточкой комнаты; по этим числам комната вычерчивается автономно (тест восстанавливает контур из напечатанных длин и сверяет с моделью) | unit (фикстуры: прямоугольник, L, комната с выступом, общая толстая стена, виртуальная граница, партиция внутри комнаты) |
|
||||
| AC6 | Внешние размеры: у каждой прямой наружной грани планировки своя размерная линия снаружи контура с выносными линиями; несвязные контуры — каждый свои; виртуальные границы без размера; текст не вверх ногами; коллизии детерминированы; рёбра < 30 см — засечка без числа | unit на раскладку + golden |
|
||||
| AC7 | Галочки реально управляют содержимым: без «размеров» в PDF нет ни одной размерной строки; без «названий» — ни одного названия; без «декора» — ни одного декор-объекта; без «подложки» — ни одного XObject Image | unit по извлечённому тексту/операторам |
|
||||
| AC8 | Кириллица и латиница в названиях печатаются встроенным сабсетом; текст извлекается из PDF (`pdfjs-dist` в тесте) как Unicode — работает ToUnicode | unit |
|
||||
| AC9 | Подвал: масштаб из ряда §7.5, линейка соответствует масштабу (1 м на бумаге = 1000/N мм ± 0,1 мм), стрелка севера при компасе, легенда только присутствующих элементов | unit |
|
||||
@@ -312,7 +333,7 @@ View. `localStorage` — единственное новое состояние,
|
||||
- `test/pdf-scene.test.mjs` — таблица §7.1 по операторам, галочки (AC7), подвал (AC9), ориентация и ряд масштабов.
|
||||
- `demo/smoke_pdf_export.mjs` — кнопка, диалог, `localStorage`, перехват `download`, MediaBox, отказ чанка, лимит растра.
|
||||
- golden: сцена `pdf-export-geometry-light` — PDF рендерится `pdfjs-dist` в canvas в браузере golden-harness и сравнивается как PNG (детерминизм §8.4 делает это устойчивым); при отклонении — обычная приёмка эталона.
|
||||
- Свидетели: `pdf-shared-wall-twice` (общая стена дважды), `pdf-dimensions-ignore-toggle` (галочка размеров не влияет), `pdf-virtual-wall-solid` (нулевая стена сплошная), `pdf-font-no-tounicode` (текст без ToUnicode — не извлекается), `pdf-backdrop-over-geometry` (подложка поверх геометрии), `pdf-devices-leak` (маркеры попадают на лист), `pdf-scale-not-standard` (масштаб не из ряда).
|
||||
- Свидетели: `pdf-shared-wall-twice` (общая стена дважды), `pdf-dimensions-ignore-toggle` (галочка размеров не влияет), `pdf-room-edge-dropped` (у непрямоугольной комнаты пропущено ребро — контур невосстановим), `pdf-outer-face-inside` (внешний размер нарисован внутри контура), `pdf-virtual-wall-solid` (нулевая стена сплошная), `pdf-font-no-tounicode` (текст без ToUnicode — не извлекается), `pdf-backdrop-over-geometry` (подложка поверх геометрии), `pdf-devices-leak` (маркеры попадают на лист), `pdf-scale-not-standard` (масштаб не из ряда).
|
||||
|
||||
## 14. Release-артефакты
|
||||
|
||||
@@ -327,7 +348,7 @@ View. `localStorage` — единственное новое состояние,
|
||||
| Риск | Мера |
|
||||
|---|---|
|
||||
| Собственный writer даёт PDF, который открывается не везде | тест парсером `pdfjs-dist` + ручная проверка в Acrobat Reader, Chrome, macOS Preview, Android (AC12); PDF 1.4 без потоков-объектов и без сжатия текста (только FlateDecode контента при наличии `CompressionStream`, иначе без сжатия) |
|
||||
| Размерные подписи налезают друг на друга на плотных планах | детерминированная коллизия §7.3 с приоритетом внешнего контура; проверено на `large-house` фикстуре в golden |
|
||||
| Размерные подписи налезают друг на друга на плотных планах (по 4+ числа на комнату) | детерминированная коллизия §7.3: внешние → площади → длинные рёбра; у прямоугольников дубликаты опускаются первыми; проверено на `large-house` фикстуре в golden |
|
||||
| Ядро на потолке | логика целиком в `src/pdf/**`, ядро — кнопка и вызов; счёт строк в дифе |
|
||||
| Растры делают файл огромным | лимит 25 МБ, JPEG q=0.85, тост вместо молчаливого 100-МБ файла |
|
||||
| Мобильное приложение не скачивает Blob | AC12 вручную до закрытия; при отказе — запасной путь `data:`-URL в новой вкладке, решение по факту |
|
||||
|
||||
Reference in New Issue
Block a user