mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-07 23:19:14 +00:00
feat: a straight wall is one record, not a row of seams (#229)
Рисование прямой стены в несколько кликов оставляло по записи на каждый отрезок. Швы невидимы, пока их не тронешь: выделение хватает кусок, перетаскивание ломает стену пополам, толщина задаётся пофрагментно. У стен комнат этого давно нет — `normalizeWallIntervals` схлопывает каждый сплошной участок одной толщины. Независимые перегородки жили по другому правилу. Новый чистый модуль `src/wall-merge.ts` даёт им то же правило: - `mergeCollinearPartitions` сращивает соседей одинаковой толщины и направления до неподвижной точки, но только там, где узел никому не нужен. Узел остаётся, если в него приходит третья перегородка, стена комнаты (стороной, а не только вершиной), колонна или конец сохранённого черновика. - Направление выжившей записи канонизируется лексикографически: иначе одна и та же физическая стена выходила то a→b, то b→a в зависимости от порядка входа, и каждый host.t вдоль неё переворачивался вместе с ней. - `applyOpeningMoves` переносит проёмы на выжившую запись: и авторитетный `host`, и legacy-проекцию `x/y/angle`, которую рисует старый читатель конфига (docs/CONFIG-COMPATIBILITY.md, #132). Проекция здесь не кэш — канонизация направления разворачивает угол на 180°. Рисование сращивает только свою цепочку и то, чего она коснулась (§8.6 ТЗ): молча править чужие швы в стороне оно не вправе — для этого есть «Оптимизировать планы» с предпросмотром, отчётом и отменой. Оптимизация проходит по всему пространству без seed-ограничения и отдельной строкой сообщает, сколько записей исчезло. Issue: #229 User-Visible: yes
This commit is contained in:
@@ -24,6 +24,7 @@ import {
|
||||
degradeWalls, normalizeWallIntervals, rekeyWallsAfterMove, roomWallProfile,
|
||||
setWallThickness, type WallEntry,
|
||||
} from './wall-thickness';
|
||||
import { applyOpeningMoves, mergeCollinearPartitions } from './wall-merge';
|
||||
|
||||
/** Bump when a new lossless maintenance pass is added. */
|
||||
export const PLAN_MODEL_VERSION = 6;
|
||||
@@ -50,6 +51,8 @@ export interface OptimizeReport extends AlignReport {
|
||||
wallsMerged: number;
|
||||
/** Touching/overlapping virtual pieces merged on the same room pair. */
|
||||
spansMerged: number;
|
||||
/** Collinear independent-wall records collapsed into one (#229). */
|
||||
partitionsMerged: number;
|
||||
}
|
||||
|
||||
export interface OptimizeResult {
|
||||
@@ -401,6 +404,7 @@ export function optimizePlans(configIn: any, layoutIn: Record<string, any>): Opt
|
||||
|
||||
let wallsMerged = 0;
|
||||
let spansMerged = 0;
|
||||
let partitionsMerged = 0;
|
||||
let canonicalized = 0;
|
||||
for (let i = 0; i < config.spaces.length; i++) {
|
||||
const before = beforeSpaces[i];
|
||||
@@ -492,6 +496,33 @@ export function optimizePlans(configIn: any, layoutIn: Record<string, any>): Opt
|
||||
wallsMerged += Math.max(0, wallParts - walls.length);
|
||||
if (walls.length) space.walls = walls;
|
||||
else delete space.walls;
|
||||
|
||||
// Independent walls drawn in several clicks: collapse the seams that have
|
||||
// piled up. Drawing merges only its own chain, so this is where an older
|
||||
// plan finally loses them — explicitly, with a report and an undo (#229).
|
||||
const partitionMerge = mergeCollinearPartitions(space.partitions || [], {
|
||||
pitch: GRID_STEP_N,
|
||||
geometry: {
|
||||
roomPolygons: (space.rooms || [])
|
||||
.map((room: any) => roomPoly(room))
|
||||
.filter((poly: number[][] | null): poly is number[][] => !!poly)
|
||||
.map((poly: number[][]) => poly.map((p) => [p[0] / NORM_W, p[1] / NORM_W])),
|
||||
columns: space.wall_columns || [],
|
||||
draftEnds: (space.room_drafts || []).flatMap((draft: any) => {
|
||||
const points = draft?.points || [];
|
||||
return points.length ? [points[0], points[points.length - 1]] : [];
|
||||
}),
|
||||
},
|
||||
});
|
||||
if (partitionMerge.merged) {
|
||||
partitionsMerged += partitionMerge.merged;
|
||||
space.partitions = partitionMerge.partitions;
|
||||
applyOpeningMoves(space.openings, space.partitions, partitionMerge.openingMoves, {
|
||||
coordScale: NORM_W,
|
||||
cellCm: Number(space.cell_cm) > 0 ? Number(space.cell_cm) : DEFAULT_CELL_CM,
|
||||
gridPitch: GRID_PITCH,
|
||||
});
|
||||
}
|
||||
const canonicalAfter = JSON.stringify({
|
||||
spans: space.open_spans || [],
|
||||
links: (space.rooms || []).map((r: any) => [r.id, r.open_to || []]),
|
||||
@@ -528,6 +559,7 @@ export function optimizePlans(configIn: any, layoutIn: Record<string, any>): Opt
|
||||
canonicalized,
|
||||
wallsMerged,
|
||||
spansMerged,
|
||||
partitionsMerged,
|
||||
},
|
||||
changed,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user