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
1,474 changes: 1,474 additions & 0 deletions docs/intermediate-walls-architecture.md

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions src/@types/resources.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1320,6 +1320,12 @@ interface Resources {
"perimeter": "Perimeter",
"removeFloorOpening": "Remove floor opening"
},
"intermediateWall": {
"deleteWall": "Delete Wall",
"fitToView": "Fit to View",
"thickness": "Thickness",
"wallLength": "Length"
},
"opening": {
"confirmDelete": "Are you sure you want to remove this opening?",
"deleteOpening": "Delete opening",
Expand Down Expand Up @@ -1461,6 +1467,10 @@ interface Resources {
"wallToWindowRatio": "Wall-to-window ratio (WWR)",
"windowArea": "Window Area"
},
"wallNode": {
"deleteNode": "Delete Node",
"fitToView": "Fit to View"
},
"wallPost": {
"actsAsPost": "Acts as Post",
"behavior": "Behavior",
Expand Down Expand Up @@ -1699,6 +1709,28 @@ interface Resources {
"description": "Draw an opening within an existing floor area. Use snapping to align with floor edges or other openings.",
"title": "Floor Opening"
},
"intermediateWall": {
"cancelTooltip": "Cancel wall creation (Escape)",
"cancelWall": "✕ Cancel Wall",
"clearLengthOverride": "Clear length override (Escape)",
"completeTooltip": "Complete wall (Enter)",
"completeWall": "✓ Complete Wall",
"controlClickFirst": "Click an existing wall or node to finish",
"controlEnter": "<kbd>Enter</kbd> to complete wall",
"controlEscAbort": "<kbd>Escape</kbd> to abort wall",
"controlEscOverride": "<kbd>Escape</kbd> to clear override",
"controlNumbers": "Type numbers to set exact wall length",
"controlPlace": "Click to place wall points",
"controlSnap": "Points snap to grid and existing geometry",
"controlsHeading": "Controls:",
"infoLeft": "Draw the left edge of your intermediate wall. Click to place points, and finish by clicking an existing wall or node or pressing Enter.",
"infoRight": "Draw the right edge of your intermediate wall. Click to place points, and finish by clicking an existing wall or node or pressing Enter.",
"lengthOverride": "Length Override",
"referenceSide": "Alignment Side",
"referenceSideLeft": "Left",
"referenceSideRight": "Right",
"wallThickness": "Wall Thickness"
},
"keyboard": {
"enter": "Enter",
"esc": "Esc"
Expand Down Expand Up @@ -1884,6 +1916,7 @@ interface Resources {
"basicSelect": "Select",
"floorsAddArea": "Floor Area",
"floorsAddOpening": "Floor Opening",
"intermediateWallAdd": "Draw Intermediate Wall",
"perimeterAdd": "Building Perimeter",
"perimeterAddOpening": "Add Opening",
"perimeterAddPost": "Add Post",
Expand Down
3 changes: 2 additions & 1 deletion src/building/gcs/constraintGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ function makeWall(
outsideLine: { start: newVec2(0, 0), end: newVec2(0, 0) },
direction: opts.direction,
outsideDirection: newVec2(0, 0),
polygon: { points: [] }
polygon: { points: [] },
wallNodeIds: []
}
}

Expand Down
20 changes: 13 additions & 7 deletions src/building/model/ids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const OPENING_ID_PREFIX = 'opening_'
const POST_ID_PREFIX = 'post_'
const RING_BEAM_ID_PREFIX = 'ringbeam_'
const WALL_ASSEMBLY_ID_PREFIX = 'wa_'
const INTERIOR_WALL_ASSEMBLY_ID_PREFIX = 'iwa_'
const FLOOR_ASSEMBLY_ID_PREFIX = 'fa_'
const ROOF_ASSEMBLY_ID_PREFIX = 'ra_'
const OPENING_ASSEMBLY_ID_PREFIX = 'oa_'
Expand Down Expand Up @@ -44,6 +45,7 @@ export type EntityId =
| PerimeterWallId
| PerimeterCornerId
| IntermediateWallId
| WallNodeId
| OpeningId
| WallPostId
| FloorAreaId
Expand All @@ -56,6 +58,7 @@ export type LayerSetId = `${typeof LAYER_SET_ID_PREFIX}${string}`
export type AssemblyId =
| RingBeamAssemblyId
| WallAssemblyId
| InteriorWallAssemblyId
| FloorAssemblyId
| RoofAssemblyId
| OpeningAssemblyId
Expand All @@ -72,17 +75,17 @@ export type SelectableId =
| RoofId
| RoofOverhangId
| ConstraintId
// | RoomId
// | WallNodeId
// | IntermediateWallId
// | RoomId
| WallNodeId
| IntermediateWallId

// Config ids
export type RingBeamAssemblyId = `${typeof RING_BEAM_ID_PREFIX}${string}`
export type WallAssemblyId = `${typeof WALL_ASSEMBLY_ID_PREFIX}${string}`
export type FloorAssemblyId = `${typeof FLOOR_ASSEMBLY_ID_PREFIX}${string}`
export type RoofAssemblyId = `${typeof ROOF_ASSEMBLY_ID_PREFIX}${string}`
export type OpeningAssemblyId = `${typeof OPENING_ASSEMBLY_ID_PREFIX}${string}`

export type InteriorWallAssemblyId = `${typeof INTERIOR_WALL_ASSEMBLY_ID_PREFIX}${string}`
// ID generation helpers
export const createStoreyId = (): StoreyId => createId(STOREY_ID_PREFIX)
export const createPerimeterId = (): PerimeterId => createId(PERIMETER_ID_PREFIX)
Expand All @@ -108,6 +111,7 @@ export const createFloorAssemblyId = (): FloorAssemblyId => createId(FLOOR_ASSEM
export const createRoofAssemblyId = (): RoofAssemblyId => createId(ROOF_ASSEMBLY_ID_PREFIX)
export const createOpeningAssemblyId = (): OpeningAssemblyId => createId(OPENING_ASSEMBLY_ID_PREFIX)
export const createLayerSetId = (): LayerSetId => createId(LAYER_SET_ID_PREFIX)
export const createInteriorWallAssemblyId = (): InteriorWallAssemblyId => createId(INTERIOR_WALL_ASSEMBLY_ID_PREFIX)

// Default floor construction config ID constant
export const DEFAULT_FLOOR_ASSEMBLY_ID = 'fa_clt_default' as FloorAssemblyId
Expand All @@ -126,7 +130,7 @@ export const isOpeningId = (id: string): id is OpeningId => id.startsWith(OPENIN
export const isWallPostId = (id: string): id is WallPostId => id.startsWith(POST_ID_PREFIX)
export const isRoofOverhangId = (id: string): id is RoofOverhangId => id.startsWith(ROOF_OVERHANG_ID_PREFIX)
export const isConstraintId = (id: string): id is ConstraintId => id.startsWith(CONSTRAINT_ID_PREFIX)
export const isRoomId = (id: string): id is RoomId => id.startsWith(ROOF_ID_PREFIX)
export const isRoomId = (id: string): id is RoomId => id.startsWith(ROOM_ID_PREFIX)
export const isWallNodeId = (id: string): id is WallNodeId => id.startsWith(WALL_NODE_ID_PREFIX)
export const isIntermediateWallId = (id: string): id is IntermediateWallId => id.startsWith(INTERMEDIATE_WALL_ID_PREFIX)

Expand All @@ -137,6 +141,8 @@ export const isFloorAssemblyId = (id: string): id is FloorAssemblyId => id.start
export const isRoofAssemblyId = (id: string): id is RoofAssemblyId => id.startsWith(ROOF_ASSEMBLY_ID_PREFIX)
export const isOpeningAssemblyId = (id: string): id is OpeningAssemblyId => id.startsWith(OPENING_ASSEMBLY_ID_PREFIX)
export const isLayerSetId = (id: string): id is LayerSetId => id.startsWith(LAYER_SET_ID_PREFIX)
export const isInteriorWallAssemblyId = (id: string): id is InteriorWallAssemblyId =>
id.startsWith(INTERIOR_WALL_ASSEMBLY_ID_PREFIX)

// Entity type definitions for hit testing
export type EntityType =
Expand All @@ -145,9 +151,9 @@ export type EntityType =
| 'perimeter-corner'
| 'opening'
| 'wall-post'
// | 'intermediate-wall'
| 'intermediate-wall'
// | 'room'
// | 'wall-node'
| 'wall-node'
| 'floor-area'
| 'floor-opening'
| 'roof'
Expand Down
1 change: 1 addition & 0 deletions src/building/model/perimeters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface PerimeterWall {
startCornerId: PerimeterCornerId
endCornerId: PerimeterCornerId
entityIds: WallEntityId[]
wallNodeIds: WallNodeId[]

thickness: Length
wallAssemblyId: WallAssemblyId
Expand Down
68 changes: 44 additions & 24 deletions src/building/model/rooms.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { type Area, type Length, type LineSegment2D, type Polygon2D, type Vec2 } from '@/shared/geometry'

import type { IntermediateWallId, OpeningId, PerimeterId, PerimeterWallId, RoomId, WallId, WallNodeId } from './ids'
import type {
InteriorWallAssemblyId,
IntermediateWallId,
OpeningId,
PerimeterId,
PerimeterWallId,
RoomId,
WallNodeId
} from './ids'

export type RoomType =
| 'living-room'
Expand All @@ -16,76 +24,88 @@ export type RoomType =
| 'service'
| 'generic'

export type WallAxis = 'left' | 'center' | 'right'

export interface BaseWallNode {
id: WallNodeId
perimeterId: PerimeterId
type: 'perimeter' | 'inner'
connectedWallIds: IntermediateWallId[]
}

export interface PerimeterWallNode extends BaseWallNode {
type: 'perimeter'
wallId: PerimeterWallId
offsetFromCornerStart: Length

// Computed
position: Vec2
}

export interface InnerWallNode extends BaseWallNode {
type: 'inner'
position: Vec2
constructedBy: IntermediateWallId
}

// Computed
boundary: Polygon2D
export interface BaseWallNodeGeometry {
boundary?: Polygon2D
center: Vec2
}

export interface PerimeterWallNodeGeometry extends BaseWallNodeGeometry {
position: Vec2
}

export type InnerWallNodeGeometry = BaseWallNodeGeometry

export type WallNodeGeometry = PerimeterWallNodeGeometry | InnerWallNodeGeometry

export type PerimeterWallNodeWithGeometry = PerimeterWallNode & PerimeterWallNodeGeometry
export type InnerWallNodeWithGeometry = InnerWallNode & InnerWallNodeGeometry

export type WallNode = PerimeterWallNode | InnerWallNode
export type WallNodeWithGeometry = PerimeterWallNodeWithGeometry | InnerWallNodeWithGeometry

export interface Room {
id: RoomId
perimeterId: PerimeterId
wallIds: WallId[] // Detected automatically

wallIds: IntermediateWallId[] // Detected automatically
type: RoomType
counter: number // Counts up the rooms per storey and room type (i.e. bedroom 1, bedroom 2, ...)
customLabel?: string
}

// Computed geometry
export interface RoomGeometry {
boundary: Polygon2D
area: Area
}

export type RoomWithGeometry = Room & RoomGeometry

export interface WallAttachment {
nodeId: WallNodeId
axis: 'left' | 'center' | 'right'
axis: WallAxis
}

export interface IntermediateWall {
id: IntermediateWallId
perimeterId: PerimeterId
openingIds: OpeningId[]
leftRoomId: RoomId
rightRoomId: RoomId

openingIds: OpeningId[] // TODO
leftRoomId?: RoomId // TODO
rightRoomId?: RoomId // TODO
start: WallAttachment
end: WallAttachment

thickness: Length
wallAssemblyId?: InteriorWallAssemblyId // TODO
}

// Computed geometry:
export interface IntermediateWallGeometry {
boundary: Polygon2D

centerLine: LineSegment2D
wallLength: Length

leftLength: Length
leftLine: LineSegment2D
rightLength: Length
rightLine: LineSegment2D

direction: Vec2 // Normalized from start -> end of wall
leftDirection: Vec2 // Normalized vector pointing left

// TODO: wallAssemblyId: WallAssemblyId
direction: Vec2
leftDirection: Vec2
}

export type IntermediateWallWithGeometry = IntermediateWall & IntermediateWallGeometry
8 changes: 8 additions & 0 deletions src/building/store/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import {
isConstraintId,
isFloorAreaId,
isFloorOpeningId,
isIntermediateWallId,
isOpeningId,
isPerimeterCornerId,
isPerimeterId,
isPerimeterWallId,
isRoofId,
isRoofOverhangId,
isWallNodeId,
isWallPostId
} from '@/building/model'
import { getModelActions } from '@/building/store/store'
Expand Down Expand Up @@ -45,6 +47,12 @@ export function deleteEntity(selectedId: SelectableId): boolean {
} else if (isRoofOverhangId(selectedId)) {
// Cannot be deleted
return false
} else if (isIntermediateWallId(selectedId)) {
modelStore.removeIntermediateWall(selectedId)
return true
} else if (isWallNodeId(selectedId)) {
modelStore.removeWallNode(selectedId)
return true
} else {
assertUnreachable(selectedId, `Unknown sub-entity type for deletion: ${selectedId}`)
}
Expand Down
Loading
Loading