Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions packages/core/src/lib/opening-casing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type OpeningCasingBoard,
openingCasingBoards,
openingCasingFaces,
openingCasingWidth,
resolveOpeningCasingHost,
} from './opening-casing'

Expand Down Expand Up @@ -55,10 +56,12 @@ describe('opening casing schema', () => {
WindowNode.parse({ id: 'window_old', width: 1.2, height: 1 }),
DoorNode.parse({ id: 'door_old', width: 0.9, height: 2.1 }),
]) {
expect(legacy.casingFaceA).toBe(false)
expect(legacy.casingFaceB).toBe(false)
// Loading adds no casing fields: absent faces are uncased.
expect('casingFaceA' in legacy || 'casingFaceB' in legacy || 'casingWidth' in legacy).toBe(
false,
)
expect(openingCasingBoards(legacy, host)).toEqual([])
expect(legacy.casingWidth).toBeCloseTo(2 * INCH, 12)
expect(openingCasingWidth(legacy)).toBeCloseTo(2 * INCH, 12)
}
expect(DEFAULT_OPENING_CASING_WIDTH).toBeCloseTo(2 * INCH, 12)
expect(OPENING_CASING_PROUD).toBeCloseTo(0.75 * INCH, 12)
Expand All @@ -67,7 +70,7 @@ describe('opening casing schema', () => {
test('a casing face turned off stays off', () => {
const parsed = WindowNode.parse({ id: 'window_off', casingFaceA: false, casingWidth: 0.09 })
expect(parsed.casingFaceA).toBe(false)
expect(parsed.casingFaceB).toBe(false)
expect(parsed.casingFaceB).toBeUndefined()
expect(parsed.casingWidth).toBe(0.09)
})
})
Expand Down
4 changes: 0 additions & 4 deletions packages/core/src/schema/host-children.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ test.each(baseline)(
? {
...expected,
floorThresholdVersion: 1,
// Saved before casing: no casing.
casingFaceA: false,
casingFaceB: false,
casingWidth: 0.0508,
}
: saved.type === 'fence'
? {
Expand Down
16 changes: 6 additions & 10 deletions packages/core/src/schema/nodes/door.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ import { z } from 'zod'
import { BaseNode, nodeType, objectId } from '../base'
import { GeometryScriptSource } from '../geometry-source'
import { MaterialSchema } from '../material'
import {
DEFAULT_DOOR_CONTENT_PADDING,
DEFAULT_OPENING_CASING_WIDTH,
DoorType,
defaultDoorSegments,
} from './opening-types'
import { DEFAULT_DOOR_CONTENT_PADDING, DoorType, defaultDoorSegments } from './opening-types'

export { DoorType } from './opening-types'

Expand Down Expand Up @@ -144,10 +139,11 @@ export const DoorNode = BaseNode.extend({
// opening (head and both legs, none at the floor), never inside it, so the
// wall cutout stays width × height. The faces are the host's `a` / `b`
// (WallFace), not the door's front and back, so flipping the swing leaves
// the casing where it is. Absent casing fields keep saved openings uncased.
casingFaceA: z.boolean().default(false),
casingFaceB: z.boolean().default(false),
casingWidth: z.number().positive().default(DEFAULT_OPENING_CASING_WIDTH),
// the casing where it is. Optional, never defaulted, so loading a saved
// opening adds no fields: absent faces are uncased, an absent width is 2 in.
casingFaceA: z.boolean().optional(),
casingFaceB: z.boolean().optional(),
casingWidth: z.number().positive().optional(),

// Swing
hingesSide: z.enum(['left', 'right']).default('left'),
Expand Down
13 changes: 7 additions & 6 deletions packages/core/src/schema/nodes/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { z } from 'zod'
import { BaseNode, nodeType, objectId } from '../base'
import { GeometryScriptSource } from '../geometry-source'
import { MaterialSchema } from '../material'
import { DEFAULT_OPENING_CASING_WIDTH, WindowType } from './opening-types'
import { WindowType } from './opening-types'

export { WindowType } from './opening-types'

Expand Down Expand Up @@ -122,11 +122,12 @@ export const WindowNode = BaseNode.extend({
// Casing: flat boards on the finished faces of the host wall around the
// opening, never inside it, so the wall cutout stays width × height. The
// faces are the host's `a` / `b` (WallFace), not the unit's own front and
// back, so flipping the window leaves its casing where it is. Absent casing fields keep
// saved openings uncased.
casingFaceA: z.boolean().default(false),
casingFaceB: z.boolean().default(false),
casingWidth: z.number().positive().default(DEFAULT_OPENING_CASING_WIDTH),
// back, so flipping the window leaves its casing where it is. Optional, never
// defaulted, so loading a saved opening adds no fields: absent faces are
// uncased, an absent width is 2 in.
casingFaceA: z.boolean().optional(),
casingFaceB: z.boolean().optional(),
casingWidth: z.number().positive().optional(),
}).describe(dedent`Window node - a parametric window placed on a wall
- position: center of the window in wall-local coordinate system
- width/height: overall outer dimensions, which are also the wall cutout
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/store/use-scene-window-migration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ describe('scene window migrations', () => {
expect(window.columnRatios).toEqual([1])
expect(window.rowRatios).toEqual([1])
expect(window.frameThickness).toBe(0.05)
expect(window.casingFaceA).toBe(false)
expect(window.casingFaceB).toBe(false)
// Casing has no default: a window saved before it stays uncased, fieldless.
expect('casingFaceA' in window).toBe(false)
expect(window.sill).toBe(true)
// …and authored fields survive.
expect(window.width).toBe(1.2)
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/systems/wall/wall-merge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ describe('explicit wall merge through the store', () => {
const moved = nodes[absorbedWindow.id] as WindowNode
expect(moved.position[0]).toBeCloseTo(expectedX)
expect(moved.side).toBe('back')
expect([moved.casingFaceA, moved.casingFaceB]).toEqual([false, false])
expect([moved.casingFaceA, moved.casingFaceB]).toEqual([undefined, undefined])
// A door on the reversed wall turns with its frame — depth and yaw — while
// its hinges and handle, hung off that yaw, keep their stored side.
const door = nodes[absorbedDoor.id] as DoorNode
Expand Down
4 changes: 2 additions & 2 deletions packages/nodes/src/door/floorplan-move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ export const doorFloorplanMoveTarget: FloorplanMoveTarget<DoorNode> = ({ node })
roofSegmentId: undefined
roofFace: undefined
visible: true
casingFaceA: boolean
casingFaceB: boolean
casingFaceA?: boolean
casingFaceB?: boolean
} | null = null

// R flips the door's facing (front ↔ back) mid-placement. `apply` re-derives
Expand Down
3 changes: 2 additions & 1 deletion packages/nodes/src/shared/opening-casing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type GeometryContext,
LevelNode,
OPENING_CASING_PROUD,
openingCasingWidth,
sceneRegistry,
slotPaintMaterial,
useScene,
Expand Down Expand Up @@ -224,7 +225,7 @@ test('casing paint targets each host side, inherits the frame, and previews and
part.geometry.computeBoundingBox()
const local = part.geometry.boundingBox!.getCenter(new Vector3())
if (part.geometry.type === 'ExtrudeGeometry') {
local.x = node.width / 2 + node.casingWidth / 2
local.x = node.width / 2 + openingCasingWidth(node) / 2
local.y = 0
}
const center = local.applyMatrix4(mesh.matrixWorld)
Expand Down
4 changes: 2 additions & 2 deletions packages/nodes/src/window/floorplan-move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ export const windowFloorplanMoveTarget: FloorplanMoveTarget<WindowNode> = ({ nod
roofSegmentId: undefined
roofFace: undefined
visible: true
casingFaceA: boolean
casingFaceB: boolean
casingFaceA?: boolean
casingFaceB?: boolean
} | null = null

// R flips the window's facing (front ↔ back) mid-placement — see
Expand Down
Loading