fix: address isometric stage 3 review

Issue: #160
User-Visible: no
This commit is contained in:
Sergey Matyunin
2026-09-06 07:48:03 +03:00
parent 77c28076a2
commit bd60edd238
37 changed files with 656 additions and 536 deletions
+11 -13
View File
@@ -378,6 +378,7 @@ import {
openingVisibleMetrics, renderOpeningVisibleGeometry,
type OpeningFaceOffset, type OpeningVisibleSpec,
} from './render/opening-symbol';
import { openingLockFloorPlacement } from './opening-symbol-placement';
import {
openingDefaultLengthCm, openingPlacementPreset, passagePlacementPreviewGeometry,
resolveOpeningPlacementResult, sameOpeningPlacementInput,
@@ -6166,18 +6167,6 @@ export class HouseplanCard extends LitElement {
const start = unprojectFloorPoint([view.x, view.y]), end = unprojectFloorPoint([view.x + view.w, view.y + view.h]);
return { x: start[0], y: start[1], w: end[0] - start[0], h: end[1] - start[1] };
}
/** Shared Flat/Iso host-face semantics for raised opening locks. */
private _openingLockAnchor(opening: RenderOpening, openingWallIndex: OpeningWallIndex): PlanPoint {
const rad = ((opening.angle + 90) * Math.PI) / 180;
const gateFace = opening.type === 'gate'
? this._openingFace(opening, openingWallIndex, !opening.flip_v)
: null;
const lockOffset = gridVisualUnits(16, this._cellCm);
const offset = gateFace
? -lockOffset * gateFace.side
: lockOffset * (opening.flip_v ? -1 : 1);
return [opening.rx + Math.cos(rad) * offset, opening.ry + Math.sin(rad) * offset];
}
/** Stage 3 keeps one immutable floor point and one runtime visual point per
* raised item. This snapshot is presentation-only and never enters config. */
private _isoOverlayScene(
@@ -13094,7 +13083,16 @@ export class HouseplanCard extends LitElement {
const st = this._renderPlanHass.states[o.lock!]?.state;
const locked = st === 'locked';
const known = locked || ['unlocked', 'open', 'opening', 'unlocking', 'locking'].includes(String(st));
const floorAnchor = this._openingLockAnchor(o, openingWallIndex);
const gateFace = o.type === 'gate'
? this._openingFace(o, openingWallIndex, !o.flip_v)
: null;
const floorAnchor = openingLockFloorPlacement({
x: o.rx,
y: o.ry,
angle: o.angle,
flipV: !!o.flip_v,
gateFace,
}, this._cellCm)[0];
const isoPlacement = isoPlacements?.get(String(o.id));
const point = isoPlacement?.visualScene ?? this._scenePoint(floorAnchor);
const left = ((point[0] - view.x) / view.w) * 100;
+9 -8
View File
@@ -35,6 +35,7 @@ import {
type OpeningWallIndex, type WallEntry,
} from './wall-thickness';
import { partitionOpeningFace } from './partition-openings';
import { openingLockFloorPlacement } from './opening-symbol-placement';
import { physicalBodyParts, type PartitionOpeningCut } from './physical-geometry';
import { gridVisualScale, gridVisualUnits } from './grid-scale';
import { iconUnit, type Rect } from './space-geometry';
@@ -855,7 +856,6 @@ export function isoOpeningLockPlacement(
openingWallIndex: OpeningWallIndex,
cellCm: number,
): { floorAnchor: PlanPoint; preferredRoomId: string | null } {
const rad = ((opening.angle + 90) * Math.PI) / 180;
const faceFlipV = !opening.flip_v;
const gateFace = opening.type === 'gate'
? opening.partitionHost
@@ -868,20 +868,21 @@ export function isoOpeningLockPlacement(
flip_v: faceFlipV,
})
: null;
const lockOffset = gridVisualUnits(16, cellCm);
const offset = gateFace ? -lockOffset * gateFace.side : lockOffset * (opening.flip_v ? -1 : 1);
const floorAnchor: PlanPoint = [
opening.rx + Math.cos(rad) * offset,
opening.ry + Math.sin(rad) * offset,
];
const [floorAnchor, negativeSide] = openingLockFloorPlacement({
x: opening.rx,
y: opening.ry,
angle: opening.angle,
flipV: !!opening.flip_v,
gateFace,
}, cellCm);
if (opening.partitionHost) return { floorAnchor, preferredRoomId: null };
const side = offset < 0 ? 'negative' : 'positive';
const association = resolveOpeningWallAssociation(openingWallIndex, {
x: opening.rx,
y: opening.ry,
angle: opening.angle,
length: opening.rlen,
}, true);
const side = negativeSide ? 'negative' : 'positive';
return { floorAnchor, preferredRoomId: association[side]?.roomId || null };
}
+32
View File
@@ -1,4 +1,5 @@
import type { OpeningCfg } from './types';
import { gridVisualUnits } from './grid-scale';
export interface OpeningFaceOffset {
ox: number;
@@ -12,6 +13,19 @@ export interface OpeningSymbolOffset {
oy: number;
}
export interface OpeningLockAnchorInput {
x: number;
y: number;
angle: number;
flipV: boolean;
gateFace?: Pick<OpeningFaceOffset, 'side'> | null;
}
export type OpeningLockFloorPlacement = [
floorAnchor: [number, number],
negativeSide: boolean,
];
/**
* Resolve only the visible symbol translation. Every opening symbol stays on
* the saved wall centreline: wall-face direction/depth and flip direction are
@@ -25,3 +39,21 @@ export function openingSymbolOffset(
): OpeningSymbolOffset {
return { ox: 0, oy: 0 };
}
/** One Flat/Iso authority for an opening lock's immutable floor placement. */
export function openingLockFloorPlacement(
opening: OpeningLockAnchorInput, cellCm: number,
): OpeningLockFloorPlacement {
const rad = ((opening.angle + 90) * Math.PI) / 180;
const lockOffset = gridVisualUnits(16, cellCm);
const offset = opening.gateFace
? -lockOffset * opening.gateFace.side
: lockOffset * (opening.flipV ? -1 : 1);
return [
[
opening.x + Math.cos(rad) * offset,
opening.y + Math.sin(rad) * offset,
],
offset < 0,
];
}