mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-07 06:59:46 +00:00
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Lossless explicit-Optimize reconciliation for an independent partition that
|
||||
* is exactly the same centred physical wall as one solid shared room edge.
|
||||
* is exactly the same centred physical wall as one solid room edge.
|
||||
* Runtime renderers never call this module and it never mutates its inputs.
|
||||
*/
|
||||
import {
|
||||
@@ -180,13 +180,14 @@ export function reconcileCoincidentPartitions(
|
||||
[b[0] / keyScale, b[1] / keyScale],
|
||||
options.pitch,
|
||||
);
|
||||
const sharedByKey = new Map<string, WallInterval[]>();
|
||||
const solidByKey = new Map<string, WallInterval[]>();
|
||||
for (const interval of intervals) {
|
||||
if (interval.kind !== 'shared' || interval.open || !(interval.cm > 0)) continue;
|
||||
if ((interval.kind !== 'shared' && interval.kind !== 'outer')
|
||||
|| interval.open || !(interval.cm > 0)) continue;
|
||||
const key = segmentKey(interval.a, interval.b);
|
||||
const bucket = sharedByKey.get(key);
|
||||
const bucket = solidByKey.get(key);
|
||||
if (bucket) bucket.push(interval);
|
||||
else sharedByKey.set(key, [interval]);
|
||||
else solidByKey.set(key, [interval]);
|
||||
}
|
||||
const modelById = new Map(model.partitions.map((partition) => [partition.id, partition]));
|
||||
let walls = walls0;
|
||||
@@ -204,21 +205,25 @@ export function reconcileCoincidentPartitions(
|
||||
partition.b[0] - partition.a[0], partition.b[1] - partition.a[1],
|
||||
) <= eps) continue;
|
||||
|
||||
const owners = (sharedByKey.get(segmentKey(partition.a, partition.b)) || [])
|
||||
const owners = (solidByKey.get(segmentKey(partition.a, partition.b)) || [])
|
||||
.filter((interval) => sameSegment(
|
||||
interval.a, interval.b, partition.a, partition.b, eps,
|
||||
));
|
||||
if (owners.length !== 2) continue;
|
||||
if (!owners.length) continue;
|
||||
const byRoom = new Map<string, WallInterval>();
|
||||
for (const owner of owners) byRoom.set(owner.roomId, owner);
|
||||
if (byRoom.size !== 2) continue;
|
||||
const shared = [...byRoom.values()];
|
||||
if (shared.some((interval) => !sameSegment(
|
||||
const solid = [...byRoom.values()];
|
||||
const kinds = new Set(solid.map((interval) => interval.kind));
|
||||
if (kinds.size !== 1) continue;
|
||||
const kind = solid[0]?.kind;
|
||||
if ((kind === 'shared' && byRoom.size !== 2)
|
||||
|| (kind === 'outer' && byRoom.size !== 1)) continue;
|
||||
if (solid.some((interval) => !sameSegment(
|
||||
interval.a, interval.b, partition.a, partition.b, eps,
|
||||
))) continue;
|
||||
const sharedCms = new Set(shared.map((interval) => interval.cm));
|
||||
if (sharedCms.size !== 1) continue;
|
||||
const roomCm = shared[0].cm;
|
||||
const solidCms = new Set(solid.map((interval) => interval.cm));
|
||||
if (solidCms.size !== 1) continue;
|
||||
const roomCm = solid[0].cm;
|
||||
|
||||
const anotherPartition = model.partitions.some((other) => (
|
||||
other.id !== partition.id
|
||||
@@ -244,7 +249,7 @@ export function reconcileCoincidentPartitions(
|
||||
}));
|
||||
if (resolvedHosted.some((item) => !item.resolved)) continue;
|
||||
if (resolvedHosted.some((item) => !partitionOpeningHasCompositeRoomWall(
|
||||
item.resolved!, shared, eps,
|
||||
item.resolved!, solid, eps,
|
||||
))) continue;
|
||||
|
||||
const finalCm = Math.max(roomCm, partition.cm);
|
||||
@@ -282,7 +287,7 @@ export function reconcileCoincidentPartitions(
|
||||
model.rooms, nextWalls, openCuts,
|
||||
options.pitch, options.cellCm, options.gridPitch, options.coordScale,
|
||||
);
|
||||
const roomIds = new Set(shared.map((interval) => interval.roomId));
|
||||
const roomIds = new Set(solid.map((interval) => interval.roomId));
|
||||
const associationsOk = nextHosted.every((opening) => {
|
||||
const association = resolveOpeningWallAssociation(index, {
|
||||
x: opening.x * options.coordScale,
|
||||
@@ -290,10 +295,11 @@ export function reconcileCoincidentPartitions(
|
||||
angle: opening.angle,
|
||||
length: opening.length * options.coordScale,
|
||||
}, true);
|
||||
return !!association.negative?.full && !!association.positive?.full
|
||||
&& roomIds.has(association.negative.roomId)
|
||||
&& roomIds.has(association.positive.roomId)
|
||||
&& association.negative.roomId !== association.positive.roomId;
|
||||
const full = [association.negative, association.positive]
|
||||
.filter((side): side is NonNullable<typeof association.negative> => !!side?.full);
|
||||
const associated = new Set(full.map((side) => side.roomId));
|
||||
return full.length === roomIds.size && associated.size === roomIds.size
|
||||
&& [...roomIds].every((roomId) => associated.has(roomId));
|
||||
});
|
||||
if (!associationsOk) continue;
|
||||
|
||||
|
||||
@@ -8403,6 +8403,7 @@ class HouseplanCard extends LitElement {
|
||||
return {
|
||||
minDim: this._cmToUnits(MIN_ROOM_CM),
|
||||
eps: this._gridPitch * 0.05,
|
||||
step: this._gridPitch,
|
||||
movingHalf: cm > 0 ? wallCmToUnits(cm, this._cellCm, this._gridPitch) / 2 : 0,
|
||||
obstacles: this._rszObstacles(),
|
||||
thicknessConflict: exactCms.size > 1,
|
||||
|
||||
@@ -46,6 +46,8 @@ export type SafeResizeObstacle =
|
||||
| { kind: 'circle'; center: number[]; radius: number };
|
||||
|
||||
export interface SafeResizeOptions extends StopOpts {
|
||||
/** One snapped editor step; eligibility must permit a non-zero neighbour. */
|
||||
step?: number;
|
||||
/** Physical half-depth of the moving wall in render units. */
|
||||
movingHalf?: number;
|
||||
/** Independent walls, drafts and columns. They are immutable hard stops. */
|
||||
@@ -706,6 +708,22 @@ export function resolveSafeResize(
|
||||
if (!validateSafeResize(rooms, openings, plan, 0, opts)) {
|
||||
return { enabled: false, reason: 'invalid-geometry' };
|
||||
}
|
||||
const step = Math.abs(Number(opts.step));
|
||||
if (Number.isFinite(step) && step > eps) {
|
||||
const neighbours = [-step, step];
|
||||
if (!neighbours.some((delta) => validateSafeResize(
|
||||
rooms, openings, plan, delta, opts,
|
||||
))) {
|
||||
const withoutObstacles = { ...opts, obstacles: [] };
|
||||
if (neighbours.some((delta) => validateSafeResize(
|
||||
rooms, openings, plan, delta, withoutObstacles,
|
||||
))) return { enabled: false, reason: 'duplicate-physical-wall' };
|
||||
if (openings.length && neighbours.some((delta) => validateSafeResize(
|
||||
rooms, [], plan, delta, opts,
|
||||
))) return { enabled: false, reason: 'opening-conflict' };
|
||||
return { enabled: false, reason: 'invalid-geometry' };
|
||||
}
|
||||
}
|
||||
return { enabled: true, plan };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user