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
9 changes: 9 additions & 0 deletions apps/editor/public/items/office-chair/floor-plan.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions apps/editor/public/items/sofa/floor-plan.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 13 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@
"idb-keyval": "^6.2.2",
"mitt": "^3.0.1",
"nanoid": "^5.1.6",
"three-bvh-csg": "^0.0.18",
"three-mesh-bvh": "^0.9.8",
"zod": "^4.3.5",
"zundo": "^2.3.0",
"zustand": "^5"
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/events/bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
CeilingNode,
DoorNode,
FenceNode,
GuideNode,
ItemNode,
LevelNode,
RoofNode,
Expand Down Expand Up @@ -130,6 +131,12 @@ type ToolEvents = {
'tool:cancel': undefined
}

type GuideEvents = {
'guide:set-reference-scale': { guideId: GuideNode['id'] }
'guide:cancel-reference-scale': undefined
'guide:deleted': { guideId: GuideNode['id'] }
}

type PresetEvents = {
'preset:generate-thumbnail': { presetId: string; nodeId: string }
'preset:thumbnail-updated': { presetId: string; thumbnailUrl: string }
Expand Down Expand Up @@ -170,6 +177,7 @@ type EditorEvents = GridEvents &
NodeEvents<'door', DoorEvent> &
CameraControlEvents &
ToolEvents &
GuideEvents &
PresetEvents &
ThumbnailEvents &
SnapshotEvents &
Expand Down
25 changes: 9 additions & 16 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export {
} from './hooks/spatial-grid/spatial-grid-sync'
export { useSpatialQuery } from './hooks/spatial-grid/use-spatial-query'
export { loadAssetUrl, saveAsset } from './lib/asset-storage'
export { getRenderableSlabPolygon } from './lib/slab-polygon'
export {
detectSpacesForLevel,
initSpaceDetectionSync,
Expand All @@ -47,32 +48,25 @@ export {
LIBRARY_MATERIAL_REF_PREFIX,
MATERIAL_CATALOG,
MATERIAL_CATEGORIES,
type MaterialCategory,
type MaterialCatalogItem,
type MaterialCategory,
toLibraryMaterialRef,
} from './material-library'
export { baseMaterial, glassMaterial } from './materials'
export * from './schema'
export {
type ControlValue,
type ItemInteractiveState,
useInteractive,
} from './store/use-interactive'
export {
getSceneHistoryPauseDepth,
pauseSceneHistory,
resetSceneHistoryPauseDepth,
resumeSceneHistory,
} from './store/history-control'
export {
type ControlValue,
type ItemInteractiveState,
useInteractive,
} from './store/use-interactive'
export { default as useLiveTransforms, type LiveTransform } from './store/use-live-transforms'
export { clearSceneHistory, default as useScene } from './store/use-scene'
export { CeilingSystem } from './systems/ceiling/ceiling-system'
export { DoorSystem } from './systems/door/door-system'
export { FenceSystem } from './systems/fence/fence-system'
export { ItemSystem } from './systems/item/item-system'
export { RoofSystem } from './systems/roof/roof-system'
export { SlabSystem } from './systems/slab/slab-system'
export { StairSystem } from './systems/stair/stair-system'
export { syncAutoStairOpenings } from './systems/stair/stair-opening-sync'
export {
getClampedWallCurveOffset,
getMaxWallCurveOffset,
Expand All @@ -94,14 +88,13 @@ export {
} from './systems/wall/wall-footprint'
export {
calculateLevelMiters,
getAdjacentWallIds,
getWallMiterBoundaryPoints,
type Point2D,
pointToKey,
type WallMiterBoundaryPoints,
type WallMiterData,
} from './systems/wall/wall-mitering'
export { WallSystem } from './systems/wall/wall-system'
export { WindowSystem } from './systems/window/window-system'
export type { SceneGraph } from './utils/clone-scene-graph'
export { cloneLevelSubtree, cloneSceneGraph, forkSceneGraph } from './utils/clone-scene-graph'
export { isObject } from './utils/types'
67 changes: 67 additions & 0 deletions packages/core/src/lib/slab-polygon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { SlabNode } from '../schema'
import { insetPolygonFromCentroid, simplifyClosedPolygon } from './polygon-geometry'

/** Half of default wall thickness — used to extend slab geometry under walls */
const SLAB_OUTSET = 0.05
const AUTO_SLAB_INSET = 0.02
const AUTO_SLAB_SIMPLIFY_TOLERANCE = 0.08

export function getRenderableSlabPolygon(slabNode: SlabNode): Array<[number, number]> {
return slabNode.autoFromWalls
? simplifyClosedPolygon(
insetPolygonFromCentroid(slabNode.polygon, AUTO_SLAB_INSET),
AUTO_SLAB_SIMPLIFY_TOLERANCE,
)
: outsetPolygon(slabNode.polygon, SLAB_OUTSET)
}

/**
* Expand a polygon outward by a uniform distance.
* Offsets each edge outward then intersects consecutive offset edges.
*/
function outsetPolygon(polygon: Array<[number, number]>, amount: number): Array<[number, number]> {
const n = polygon.length
if (n < 3) return polygon

// Determine winding via signed area
let area2 = 0
for (let i = 0; i < n; i++) {
const j = (i + 1) % n
area2 += polygon[i]![0] * polygon[j]![1] - polygon[j]![0] * polygon[i]![1]
}
const s = area2 >= 0 ? 1 : -1

// Offset each edge outward by amount
const offEdges: Array<[number, number, number, number]> = []
for (let i = 0; i < n; i++) {
const j = (i + 1) % n
const dx = polygon[j]![0] - polygon[i]![0]
const dz = polygon[j]![1] - polygon[i]![1]
const len = Math.sqrt(dx * dx + dz * dz)
if (len < 1e-9) {
offEdges.push([polygon[i]![0], polygon[i]![1], dx, dz])
continue
}
const nx = ((s * dz) / len) * amount
const nz = ((s * -dx) / len) * amount
offEdges.push([polygon[i]![0] + nx, polygon[i]![1] + nz, dx, dz])
}

// Intersect consecutive offset edges to get new vertices
const result: Array<[number, number]> = []
for (let i = 0; i < n; i++) {
const j = (i + 1) % n
const [ax, az, adx, adz] = offEdges[i]!
const [bx, bz, bdx, bdz] = offEdges[j]!
const denom = adx * bdz - adz * bdx
if (Math.abs(denom) < 1e-9) {
// Parallel edges — use offset endpoint
result.push([ax + adx, az + adz])
} else {
const t = ((bx - ax) * bdz - (bz - az) * bdx) / denom
result.push([ax + t * adx, az + t * adz])
}
}

return result
}
24 changes: 0 additions & 24 deletions packages/core/src/materials.ts

This file was deleted.

6 changes: 3 additions & 3 deletions packages/core/src/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export { BuildingNode } from './nodes/building'
export { CeilingNode } from './nodes/ceiling'
export { DoorNode, DoorSegment } from './nodes/door'
export { FenceBaseStyle, FenceNode, FenceStyle } from './nodes/fence'
export { GuideNode } from './nodes/guide'
export { GuideNode, GuideScaleReference } from './nodes/guide'
export type {
AnimationEffect,
Asset,
Expand All @@ -43,13 +43,14 @@ export type {
} from './nodes/item'
export { getScaledDimensions, ItemNode } from './nodes/item'
export { LevelNode } from './nodes/level'
export { getEffectiveRoofSurfaceMaterial, RoofNode } from './nodes/roof'
export type { RoofSurfaceMaterialRole, RoofSurfaceMaterialSpec } from './nodes/roof'
export { getEffectiveRoofSurfaceMaterial, RoofNode } from './nodes/roof'
export { RoofSegmentNode, RoofType } from './nodes/roof-segment'
export { ScanNode } from './nodes/scan'
// Nodes
export { SiteNode } from './nodes/site'
export { SlabNode } from './nodes/slab'
export type { StairSurfaceMaterialRole, StairSurfaceMaterialSpec } from './nodes/stair'
export { SpawnNode } from './nodes/spawn'
export {
getEffectiveStairSurfaceMaterial,
Expand All @@ -59,7 +60,6 @@ export {
StairTopLandingMode,
StairType,
} from './nodes/stair'
export type { StairSurfaceMaterialRole, StairSurfaceMaterialSpec } from './nodes/stair'
export { AttachmentSide, StairSegmentNode, StairSegmentType } from './nodes/stair-segment'
export { SurfaceHoleMetadata } from './nodes/surface-hole-metadata'
export type { WallSurfaceMaterialSpec, WallSurfaceSide } from './nodes/wall'
Expand Down
17 changes: 16 additions & 1 deletion packages/core/src/schema/nodes/door.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ export const DoorNode = BaseNode.extend({
width: z.number().default(0.9),
height: z.number().default(2.1),

// Opening mode
openingKind: z.enum(['door', 'opening']).default('door'),
openingShape: z.enum(['rectangle', 'rounded', 'arch']).default('rectangle'),
openingRadiusMode: z.enum(['all', 'individual']).default('all'),
openingTopRadii: z.tuple([z.number(), z.number()]).default([0.15, 0.15]),
cornerRadius: z.number().min(0).default(0.15),
archHeight: z.number().min(0).default(0.45),
openingRevealRadius: z.number().min(0).default(0.025),

// Frame
frameThickness: z.number().default(0.05),
frameDepth: z.number().default(0.07),
Expand All @@ -41,6 +50,11 @@ export const DoorNode = BaseNode.extend({
// Swing
hingesSide: z.enum(['left', 'right']).default('left'),
swingDirection: z.enum(['inward', 'outward']).default('inward'),
swingAngle: z
.number()
.min(0)
.max(Math.PI / 2)
.default(0),

// Leaf segments — stacked top to bottom, each with its own column split
segments: z.array(DoorSegment).default([
Expand Down Expand Up @@ -76,9 +90,10 @@ export const DoorNode = BaseNode.extend({
panicBarHeight: z.number().default(1.0),
}).describe(dedent`Door node - a parametric door placed on a wall
- position: center of the door in wall-local coordinate system (Y = height/2, always at floor)
- openingKind/openingShape: hinged door or frameless wall opening shape
- segments: rows stacked top to bottom, each defining its own columnRatios
- type 'empty' = no leaf fill for that segment, 'panel' = raised/recessed panel, 'glass' = glazed
- hingesSide/swingDirection: which way the door opens
- hingesSide/swingDirection/swingAngle: which way the door opens and how far it is currently open
- doorCloser/panicBar: commercial and emergency hardware options
`)

Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/schema/nodes/guide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import { z } from 'zod'
import { AssetUrl } from '../asset-url'
import { BaseNode, nodeType, objectId } from '../base'

export const GuideScaleReference = z.object({
start: z.tuple([z.number(), z.number()]),
end: z.tuple([z.number(), z.number()]),
realLengthMeters: z.number().positive(),
measuredLengthUnits: z.number().positive(),
metersPerUnit: z.number().positive(),
label: z.string(),
})

export const GuideNode = BaseNode.extend({
id: objectId('guide'),
type: nodeType('guide'),
Expand All @@ -10,6 +19,8 @@ export const GuideNode = BaseNode.extend({
rotation: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
scale: z.number().default(1),
opacity: z.number().min(0).max(100).default(50),
scaleReference: GuideScaleReference.nullable().default(null),
})

export type GuideScaleReference = z.infer<typeof GuideScaleReference>
export type GuideNode = z.infer<typeof GuideNode>
11 changes: 11 additions & 0 deletions packages/core/src/schema/nodes/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ export const WindowNode = BaseNode.extend({
width: z.number().default(1.5),
height: z.number().default(1.5),

// Opening mode - when set to "opening", the window is only a shaped cutout
openingKind: z.enum(['window', 'opening']).default('window'),
openingShape: z.enum(['rectangle', 'rounded', 'arch']).default('rectangle'),
openingRadiusMode: z.enum(['all', 'individual']).default('all'),
openingCornerRadii: z
.tuple([z.number(), z.number(), z.number(), z.number()])
.default([0.15, 0.15, 0.15, 0.15]),
cornerRadius: z.number().default(0.15),
archHeight: z.number().default(0.35),
openingRevealRadius: z.number().default(0.025),

// Frame
frameThickness: z.number().default(0.05),
frameDepth: z.number().default(0.07),
Expand Down
3 changes: 3 additions & 0 deletions packages/editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
"three": "^0.184"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@iconify/react": "^6.0.2",
"@number-flow/react": "^0.5.14",
"@radix-ui/react-alert-dialog": "^1.1.15",
Expand Down
Loading
Loading