@react-arch/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 React Arch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # @react-arch/core
2
+
3
+ The canonical semantic building model and the immutable commands that operate on
4
+ it. **The single source of truth** for building data.
5
+
6
+ Depends only on `@react-arch/shared` and `@react-arch/geometry` — never on React,
7
+ Three.js, PixiJS, or browser APIs (ADR 0001).
8
+
9
+ ## Model
10
+
11
+ `BuildingDocument → Building → Floor → { walls, rooms, openings, objects }`,
12
+ plus `materials`, `assets`, `units`, `version`, and `metadata`. Walls are stored
13
+ by **centerline + thickness**; the visible polygon is derived by
14
+ `@react-arch/geometry`. Openings attach to walls semantically (by `offset` along
15
+ the centerline), so they follow when a wall moves.
16
+
17
+ ## Commands
18
+
19
+ Pure functions returning `{ document, changes, warnings }` — they never mutate
20
+ the input:
21
+
22
+ ```ts
23
+ import { createWall, createOpening, deleteWall } from "@react-arch/core";
24
+
25
+ const a = createWall(doc, { floorId: "ground", start: [0, 0], end: [5, 0] });
26
+ const b = createOpening(a.document, { wallId, type: "door", offset: 2, width: 0.9, height: 2.1 });
27
+ // deleteWall cascades: attached openings are removed too.
28
+ ```
29
+
30
+ Floor commands: `createFloor`, `duplicateFloor`, `reorderFloor`, `updateFloor`,
31
+ `deleteFloor`. Room/opening commands mirror the wall ones.
32
+
33
+ ## History
34
+
35
+ `History` provides snapshot-based undo/redo (`push`, `undo`, `redo`,
36
+ `canUndo`/`canRedo`). Designed to migrate to patches/op-logs later without
37
+ changing the surface.
38
+
39
+ ## Serialization
40
+
41
+ `serialize` / `deserialize` (JSON is the lossless canonical format) with a
42
+ forward-only `migrate` registry and `isCompatibleVersion`.
@@ -0,0 +1,253 @@
1
+ import { Units, Vec2, Vec3 } from "@react-arch/shared";
2
+
3
+ //#region src/model.d.ts
4
+ /** The current on-disk model version. Bump when the schema changes. */
5
+ declare const MODEL_VERSION = "0.1.0";
6
+ type OpeningType = "door" | "window" | "opening";
7
+ type SwingDirection = "in" | "out";
8
+ type HingeSide = "left" | "right";
9
+ interface Wall {
10
+ id: string;
11
+ floorId: string;
12
+ start: Vec2;
13
+ end: Vec2;
14
+ thickness: number;
15
+ height: number;
16
+ materialId?: string;
17
+ layerId?: string;
18
+ locked?: boolean;
19
+ }
20
+ interface Opening {
21
+ id: string;
22
+ floorId: string;
23
+ wallId: string;
24
+ type: OpeningType;
25
+ /** Distance along the wall centerline to the opening center. */
26
+ offset: number;
27
+ width: number;
28
+ height: number;
29
+ sillHeight: number;
30
+ swing?: SwingDirection;
31
+ hinge?: HingeSide;
32
+ materialId?: string;
33
+ }
34
+ interface Room {
35
+ id: string;
36
+ floorId: string;
37
+ name: string;
38
+ /** Explicit polygon (metres). Rooms may also be derived from wall loops. */
39
+ polygon: Vec2[];
40
+ boundaryWallIds?: string[];
41
+ floorMaterialId?: string;
42
+ ceilingMaterialId?: string;
43
+ usageType?: string;
44
+ height?: number;
45
+ }
46
+ interface BuildingObject {
47
+ id: string;
48
+ floorId: string;
49
+ type: string;
50
+ position: Vec3;
51
+ rotation: Vec3;
52
+ scale: Vec3;
53
+ assetId?: string;
54
+ metadata?: Record<string, unknown>;
55
+ }
56
+ interface Floor {
57
+ id: string;
58
+ buildingId: string;
59
+ name: string;
60
+ elevation: number;
61
+ height: number;
62
+ visible: boolean;
63
+ locked: boolean;
64
+ walls: Wall[];
65
+ rooms: Room[];
66
+ openings: Opening[];
67
+ objects: BuildingObject[];
68
+ }
69
+ interface Building {
70
+ id: string;
71
+ name: string;
72
+ floors: Floor[];
73
+ }
74
+ type MaterialCategory = "wall" | "floor" | "ceiling" | "glass" | "wood" | "metal" | "ceramic" | "custom";
75
+ interface Material {
76
+ id: string;
77
+ name: string;
78
+ category: MaterialCategory;
79
+ baseColor: string;
80
+ roughness?: number;
81
+ metalness?: number;
82
+ opacity?: number;
83
+ textureUrl?: string;
84
+ }
85
+ interface Asset {
86
+ id: string;
87
+ name: string;
88
+ url?: string;
89
+ kind: string;
90
+ }
91
+ interface Site {
92
+ id: string;
93
+ name: string;
94
+ polygon?: Vec2[];
95
+ }
96
+ interface BuildingDocument {
97
+ id: string;
98
+ version: string;
99
+ name: string;
100
+ units: Units;
101
+ site?: Site;
102
+ buildings: Building[];
103
+ materials: Material[];
104
+ assets: Asset[];
105
+ metadata: Record<string, unknown>;
106
+ }
107
+ /** A single semantic change emitted by a command. */
108
+ interface ModelChange {
109
+ kind: "create" | "update" | "delete";
110
+ entity: "wall" | "room" | "opening" | "floor" | "object" | "document";
111
+ id: string;
112
+ }
113
+ interface ModelWarning {
114
+ code: string;
115
+ message: string;
116
+ entityId?: string;
117
+ }
118
+ interface CommandResult {
119
+ document: BuildingDocument;
120
+ changes: ModelChange[];
121
+ warnings: ModelWarning[];
122
+ }
123
+ declare function findFloor(doc: BuildingDocument, floorId: string): {
124
+ building: Building;
125
+ floor: Floor;
126
+ } | undefined;
127
+ declare function allFloors(doc: BuildingDocument): Floor[];
128
+ declare function findWall(doc: BuildingDocument, wallId: string): Wall | undefined;
129
+ declare function findRoom(doc: BuildingDocument, roomId: string): Room | undefined;
130
+ declare function findOpening(doc: BuildingDocument, openingId: string): Opening | undefined;
131
+ type EntityKind = "wall" | "room" | "opening" | "floor" | "object";
132
+ interface EntityRef {
133
+ kind: EntityKind;
134
+ id: string;
135
+ }
136
+ //#endregion
137
+ //#region src/commands.d.ts
138
+ interface CreateWallInput {
139
+ floorId: string;
140
+ start: Vec2;
141
+ end: Vec2;
142
+ thickness?: number;
143
+ height?: number;
144
+ materialId?: string;
145
+ id?: string;
146
+ }
147
+ declare function createWall(doc: BuildingDocument, input: CreateWallInput): CommandResult;
148
+ declare function updateWall(doc: BuildingDocument, wallId: string, patch: Partial<Omit<Wall, "id" | "floorId">>): CommandResult;
149
+ /** Move a wall's endpoints, keeping attached openings within bounds. */
150
+ declare function moveWall(doc: BuildingDocument, wallId: string, start: Vec2, end: Vec2): CommandResult;
151
+ declare function deleteWall(doc: BuildingDocument, wallId: string): CommandResult;
152
+ interface CreateRoomInput {
153
+ floorId: string;
154
+ name: string;
155
+ polygon: Vec2[];
156
+ id?: string;
157
+ floorMaterialId?: string;
158
+ ceilingMaterialId?: string;
159
+ usageType?: string;
160
+ }
161
+ /** Convenience: build a rectangular room from origin + size. */
162
+ declare function rectRoomPolygon(x: number, y: number, width: number, depth: number): Vec2[];
163
+ declare function createRoom(doc: BuildingDocument, input: CreateRoomInput): CommandResult;
164
+ declare function updateRoom(doc: BuildingDocument, roomId: string, patch: Partial<Omit<Room, "id" | "floorId">>): CommandResult;
165
+ declare function deleteRoom(doc: BuildingDocument, roomId: string): CommandResult;
166
+ interface CreateOpeningInput {
167
+ wallId: string;
168
+ type: OpeningType;
169
+ offset: number;
170
+ width: number;
171
+ height: number;
172
+ sillHeight?: number;
173
+ id?: string;
174
+ }
175
+ declare function createOpening(doc: BuildingDocument, input: CreateOpeningInput): CommandResult;
176
+ declare function updateOpening(doc: BuildingDocument, openingId: string, patch: Partial<Omit<Opening, "id" | "floorId" | "wallId">>): CommandResult;
177
+ declare function moveOpening(doc: BuildingDocument, openingId: string, offset: number): CommandResult;
178
+ declare function deleteOpening(doc: BuildingDocument, openingId: string): CommandResult;
179
+ interface CreateFloorInput {
180
+ buildingId: string;
181
+ name: string;
182
+ elevation: number;
183
+ height?: number;
184
+ id?: string;
185
+ }
186
+ declare function createFloor(doc: BuildingDocument, input: CreateFloorInput): CommandResult;
187
+ declare function duplicateFloor(doc: BuildingDocument, floorId: string): CommandResult;
188
+ declare function reorderFloor(doc: BuildingDocument, floorId: string, newElevation: number): CommandResult;
189
+ declare function updateFloor(doc: BuildingDocument, floorId: string, patch: Partial<Pick<Floor, "name" | "elevation" | "height" | "visible" | "locked">>): CommandResult;
190
+ declare function deleteFloor(doc: BuildingDocument, floorId: string): CommandResult;
191
+ //#endregion
192
+ //#region src/history.d.ts
193
+ interface HistoryEntry {
194
+ id: string;
195
+ label: string;
196
+ before: BuildingDocument;
197
+ after: BuildingDocument;
198
+ }
199
+ /**
200
+ * Command-based undo/redo. Keeps full document snapshots per entry which is
201
+ * simple and correct for the MVP; this can later move to patches / op logs
202
+ * without changing the public surface.
203
+ */
204
+ declare class History {
205
+ private limit;
206
+ private past;
207
+ private future;
208
+ constructor(limit?: number);
209
+ push(label: string, before: BuildingDocument, after: BuildingDocument): void;
210
+ canUndo(): boolean;
211
+ canRedo(): boolean;
212
+ undo(): BuildingDocument | null;
213
+ redo(): BuildingDocument | null;
214
+ get undoLabel(): string | null;
215
+ get redoLabel(): string | null;
216
+ clear(): void;
217
+ }
218
+ //#endregion
219
+ //#region src/serialize.d.ts
220
+ declare function migrate(input: Record<string, unknown>): Record<string, unknown>;
221
+ declare function serialize(doc: BuildingDocument): string;
222
+ declare function deserialize(json: string): BuildingDocument;
223
+ declare function isCompatibleVersion(version: string): boolean;
224
+ //#endregion
225
+ //#region src/defaults.d.ts
226
+ declare const DEFAULT_MATERIALS: Material[];
227
+ interface EmptyDocumentOptions {
228
+ name?: string;
229
+ units?: "metric" | "imperial";
230
+ buildingName?: string;
231
+ }
232
+ declare function createEmptyDocument(options?: EmptyDocumentOptions): BuildingDocument;
233
+ //#endregion
234
+ //#region src/furniture.d.ts
235
+ /**
236
+ * Placeholder furniture geometry. Each entry is the base footprint (metres) at
237
+ * scale 1: `w` along plan X, `d` along plan Y, `h` vertical. A building object's
238
+ * `scale` multiplies these. Intentionally simple boxes for the MVP — no
239
+ * photorealistic assets.
240
+ */
241
+ interface FurnitureDims {
242
+ width: number;
243
+ depth: number;
244
+ height: number;
245
+ }
246
+ declare const FURNITURE_TYPES: string[];
247
+ /** Resolve the footprint of a furniture type, applying its scale. */
248
+ declare function furnitureDims(type: string, scale?: Vec3): FurnitureDims;
249
+ /** A subtle per-category colour for the placeholder geometry. */
250
+ declare function furnitureColor(type: string): string;
251
+ //#endregion
252
+ export { Asset, Building, BuildingDocument, BuildingObject, CommandResult, CreateFloorInput, CreateOpeningInput, CreateRoomInput, CreateWallInput, DEFAULT_MATERIALS, EmptyDocumentOptions, EntityKind, EntityRef, FURNITURE_TYPES, Floor, FurnitureDims, HingeSide, History, HistoryEntry, MODEL_VERSION, Material, MaterialCategory, ModelChange, ModelWarning, Opening, OpeningType, Room, Site, SwingDirection, Wall, allFloors, createEmptyDocument, createFloor, createOpening, createRoom, createWall, deleteFloor, deleteOpening, deleteRoom, deleteWall, deserialize, duplicateFloor, findFloor, findOpening, findRoom, findWall, furnitureColor, furnitureDims, isCompatibleVersion, migrate, moveOpening, moveWall, rectRoomPolygon, reorderFloor, serialize, updateFloor, updateOpening, updateRoom, updateWall };
253
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/model.ts","../src/commands.ts","../src/history.ts","../src/serialize.ts","../src/defaults.ts","../src/furniture.ts"],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,632 @@
1
+ import { createId, deepClone } from "@react-arch/shared";
2
+ import { openingFits, wallLength } from "@react-arch/geometry";
3
+ //#region src/model.ts
4
+ /** The current on-disk model version. Bump when the schema changes. */
5
+ const MODEL_VERSION = "0.1.0";
6
+ function findFloor(doc, floorId) {
7
+ for (const building of doc.buildings) {
8
+ const floor = building.floors.find((f) => f.id === floorId);
9
+ if (floor) return {
10
+ building,
11
+ floor
12
+ };
13
+ }
14
+ }
15
+ function allFloors(doc) {
16
+ return doc.buildings.flatMap((b) => b.floors);
17
+ }
18
+ function findWall(doc, wallId) {
19
+ for (const floor of allFloors(doc)) {
20
+ const w = floor.walls.find((x) => x.id === wallId);
21
+ if (w) return w;
22
+ }
23
+ }
24
+ function findRoom(doc, roomId) {
25
+ for (const floor of allFloors(doc)) {
26
+ const r = floor.rooms.find((x) => x.id === roomId);
27
+ if (r) return r;
28
+ }
29
+ }
30
+ function findOpening(doc, openingId) {
31
+ for (const floor of allFloors(doc)) {
32
+ const o = floor.openings.find((x) => x.id === openingId);
33
+ if (o) return o;
34
+ }
35
+ }
36
+ //#endregion
37
+ //#region src/commands.ts
38
+ /**
39
+ * Commands are pure: they take a document and return a brand-new document plus
40
+ * a list of changes and warnings. They never mutate the input. The studio /
41
+ * editor layers feed these into the history stack for undo/redo.
42
+ */
43
+ function mutate(doc, fn) {
44
+ const draft = deepClone(doc);
45
+ const warnings = [];
46
+ return {
47
+ document: draft,
48
+ changes: fn(draft, warnings),
49
+ warnings
50
+ };
51
+ }
52
+ function requireFloor(doc, floorId) {
53
+ const found = findFloor(doc, floorId);
54
+ if (!found) throw new Error(`Floor not found: ${floorId}`);
55
+ return found.floor;
56
+ }
57
+ function createWall(doc, input) {
58
+ return mutate(doc, (draft) => {
59
+ const floor = requireFloor(draft, input.floorId);
60
+ const wall = {
61
+ id: input.id ?? createId("wall"),
62
+ floorId: input.floorId,
63
+ start: input.start,
64
+ end: input.end,
65
+ thickness: input.thickness ?? .2,
66
+ height: input.height ?? floor.height,
67
+ materialId: input.materialId
68
+ };
69
+ floor.walls.push(wall);
70
+ return [{
71
+ kind: "create",
72
+ entity: "wall",
73
+ id: wall.id
74
+ }];
75
+ });
76
+ }
77
+ function updateWall(doc, wallId, patch) {
78
+ return mutate(doc, (draft, warnings) => {
79
+ for (const floor of allFloorsOf(draft)) {
80
+ const wall = floor.walls.find((w) => w.id === wallId);
81
+ if (!wall) continue;
82
+ Object.assign(wall, patch);
83
+ reattachOpenings(floor, wall, warnings);
84
+ return [{
85
+ kind: "update",
86
+ entity: "wall",
87
+ id: wallId
88
+ }];
89
+ }
90
+ warnings.push({
91
+ code: "wall-missing",
92
+ message: `Wall ${wallId} not found`
93
+ });
94
+ return [];
95
+ });
96
+ }
97
+ /** Move a wall's endpoints, keeping attached openings within bounds. */
98
+ function moveWall(doc, wallId, start, end) {
99
+ return updateWall(doc, wallId, {
100
+ start,
101
+ end
102
+ });
103
+ }
104
+ function deleteWall(doc, wallId) {
105
+ return mutate(doc, (draft) => {
106
+ const changes = [];
107
+ for (const floor of allFloorsOf(draft)) {
108
+ const idx = floor.walls.findIndex((w) => w.id === wallId);
109
+ if (idx === -1) continue;
110
+ floor.walls.splice(idx, 1);
111
+ changes.push({
112
+ kind: "delete",
113
+ entity: "wall",
114
+ id: wallId
115
+ });
116
+ floor.openings = floor.openings.filter((o) => {
117
+ if (o.wallId === wallId) {
118
+ changes.push({
119
+ kind: "delete",
120
+ entity: "opening",
121
+ id: o.id
122
+ });
123
+ return false;
124
+ }
125
+ return true;
126
+ });
127
+ break;
128
+ }
129
+ return changes;
130
+ });
131
+ }
132
+ /** Convenience: build a rectangular room from origin + size. */
133
+ function rectRoomPolygon(x, y, width, depth) {
134
+ return [
135
+ [x, y],
136
+ [x + width, y],
137
+ [x + width, y + depth],
138
+ [x, y + depth]
139
+ ];
140
+ }
141
+ function createRoom(doc, input) {
142
+ return mutate(doc, (draft) => {
143
+ const floor = requireFloor(draft, input.floorId);
144
+ const room = {
145
+ id: input.id ?? createId("room"),
146
+ floorId: input.floorId,
147
+ name: input.name,
148
+ polygon: input.polygon,
149
+ floorMaterialId: input.floorMaterialId,
150
+ ceilingMaterialId: input.ceilingMaterialId,
151
+ usageType: input.usageType
152
+ };
153
+ floor.rooms.push(room);
154
+ return [{
155
+ kind: "create",
156
+ entity: "room",
157
+ id: room.id
158
+ }];
159
+ });
160
+ }
161
+ function updateRoom(doc, roomId, patch) {
162
+ return mutate(doc, (draft, warnings) => {
163
+ for (const floor of allFloorsOf(draft)) {
164
+ const room = floor.rooms.find((r) => r.id === roomId);
165
+ if (!room) continue;
166
+ Object.assign(room, patch);
167
+ return [{
168
+ kind: "update",
169
+ entity: "room",
170
+ id: roomId
171
+ }];
172
+ }
173
+ warnings.push({
174
+ code: "room-missing",
175
+ message: `Room ${roomId} not found`
176
+ });
177
+ return [];
178
+ });
179
+ }
180
+ function deleteRoom(doc, roomId) {
181
+ return mutate(doc, (draft) => {
182
+ for (const floor of allFloorsOf(draft)) {
183
+ const idx = floor.rooms.findIndex((r) => r.id === roomId);
184
+ if (idx === -1) continue;
185
+ floor.rooms.splice(idx, 1);
186
+ return [{
187
+ kind: "delete",
188
+ entity: "room",
189
+ id: roomId
190
+ }];
191
+ }
192
+ return [];
193
+ });
194
+ }
195
+ function createOpening(doc, input) {
196
+ return mutate(doc, (draft, warnings) => {
197
+ for (const floor of allFloorsOf(draft)) {
198
+ const wall = floor.walls.find((w) => w.id === input.wallId);
199
+ if (!wall) continue;
200
+ const opening = {
201
+ id: input.id ?? createId("opening"),
202
+ floorId: floor.id,
203
+ wallId: input.wallId,
204
+ type: input.type,
205
+ offset: input.offset,
206
+ width: input.width,
207
+ height: input.height,
208
+ sillHeight: input.sillHeight ?? (input.type === "window" ? .9 : 0)
209
+ };
210
+ if (!openingFits(wall, opening)) warnings.push({
211
+ code: "opening-overflow",
212
+ message: `Opening ${opening.id} does not fit within wall ${wall.id}`,
213
+ entityId: opening.id
214
+ });
215
+ floor.openings.push(opening);
216
+ return [{
217
+ kind: "create",
218
+ entity: "opening",
219
+ id: opening.id
220
+ }];
221
+ }
222
+ warnings.push({
223
+ code: "wall-missing",
224
+ message: `Wall ${input.wallId} not found for opening`
225
+ });
226
+ return [];
227
+ });
228
+ }
229
+ function updateOpening(doc, openingId, patch) {
230
+ return mutate(doc, (draft, warnings) => {
231
+ for (const floor of allFloorsOf(draft)) {
232
+ const opening = floor.openings.find((o) => o.id === openingId);
233
+ if (!opening) continue;
234
+ Object.assign(opening, patch);
235
+ const wall = floor.walls.find((w) => w.id === opening.wallId);
236
+ if (wall && !openingFits(wall, opening)) warnings.push({
237
+ code: "opening-overflow",
238
+ message: `Opening ${opening.id} no longer fits its wall`,
239
+ entityId: opening.id
240
+ });
241
+ return [{
242
+ kind: "update",
243
+ entity: "opening",
244
+ id: openingId
245
+ }];
246
+ }
247
+ return [];
248
+ });
249
+ }
250
+ function moveOpening(doc, openingId, offset) {
251
+ return updateOpening(doc, openingId, { offset });
252
+ }
253
+ function deleteOpening(doc, openingId) {
254
+ return mutate(doc, (draft) => {
255
+ for (const floor of allFloorsOf(draft)) {
256
+ const idx = floor.openings.findIndex((o) => o.id === openingId);
257
+ if (idx === -1) continue;
258
+ floor.openings.splice(idx, 1);
259
+ return [{
260
+ kind: "delete",
261
+ entity: "opening",
262
+ id: openingId
263
+ }];
264
+ }
265
+ return [];
266
+ });
267
+ }
268
+ function createFloor(doc, input) {
269
+ return mutate(doc, (draft, warnings) => {
270
+ const building = draft.buildings.find((b) => b.id === input.buildingId);
271
+ if (!building) {
272
+ warnings.push({
273
+ code: "building-missing",
274
+ message: "Building not found"
275
+ });
276
+ return [];
277
+ }
278
+ const floor = {
279
+ id: input.id ?? createId("floor"),
280
+ buildingId: building.id,
281
+ name: input.name,
282
+ elevation: input.elevation,
283
+ height: input.height ?? 2.8,
284
+ visible: true,
285
+ locked: false,
286
+ walls: [],
287
+ rooms: [],
288
+ openings: [],
289
+ objects: []
290
+ };
291
+ building.floors.push(floor);
292
+ building.floors.sort((a, b) => a.elevation - b.elevation);
293
+ return [{
294
+ kind: "create",
295
+ entity: "floor",
296
+ id: floor.id
297
+ }];
298
+ });
299
+ }
300
+ function duplicateFloor(doc, floorId) {
301
+ return mutate(doc, (draft) => {
302
+ for (const building of draft.buildings) {
303
+ const floor = building.floors.find((f) => f.id === floorId);
304
+ if (!floor) continue;
305
+ const idMap = /* @__PURE__ */ new Map();
306
+ const copy = deepClone(floor);
307
+ copy.id = createId("floor");
308
+ copy.name = `${floor.name} (copy)`;
309
+ copy.elevation = floor.elevation + floor.height;
310
+ copy.walls.forEach((w) => {
311
+ const nid = createId("wall");
312
+ idMap.set(w.id, nid);
313
+ w.id = nid;
314
+ w.floorId = copy.id;
315
+ });
316
+ copy.openings.forEach((o) => {
317
+ o.id = createId("opening");
318
+ o.floorId = copy.id;
319
+ o.wallId = idMap.get(o.wallId) ?? o.wallId;
320
+ });
321
+ copy.rooms.forEach((r) => {
322
+ r.id = createId("room");
323
+ r.floorId = copy.id;
324
+ });
325
+ copy.objects.forEach((ob) => {
326
+ ob.id = createId("object");
327
+ ob.floorId = copy.id;
328
+ });
329
+ building.floors.push(copy);
330
+ building.floors.sort((a, b) => a.elevation - b.elevation);
331
+ return [{
332
+ kind: "create",
333
+ entity: "floor",
334
+ id: copy.id
335
+ }];
336
+ }
337
+ return [];
338
+ });
339
+ }
340
+ function reorderFloor(doc, floorId, newElevation) {
341
+ return mutate(doc, (draft) => {
342
+ const floor = requireFloor(draft, floorId);
343
+ floor.elevation = newElevation;
344
+ for (const building of draft.buildings) building.floors.sort((a, b) => a.elevation - b.elevation);
345
+ return [{
346
+ kind: "update",
347
+ entity: "floor",
348
+ id: floorId
349
+ }];
350
+ });
351
+ }
352
+ function updateFloor(doc, floorId, patch) {
353
+ return mutate(doc, (draft) => {
354
+ const floor = requireFloor(draft, floorId);
355
+ Object.assign(floor, patch);
356
+ return [{
357
+ kind: "update",
358
+ entity: "floor",
359
+ id: floorId
360
+ }];
361
+ });
362
+ }
363
+ function deleteFloor(doc, floorId) {
364
+ return mutate(doc, (draft) => {
365
+ for (const building of draft.buildings) {
366
+ const idx = building.floors.findIndex((f) => f.id === floorId);
367
+ if (idx === -1) continue;
368
+ building.floors.splice(idx, 1);
369
+ return [{
370
+ kind: "delete",
371
+ entity: "floor",
372
+ id: floorId
373
+ }];
374
+ }
375
+ return [];
376
+ });
377
+ }
378
+ function allFloorsOf(doc) {
379
+ return doc.buildings.flatMap((b) => b.floors);
380
+ }
381
+ /** Clamp openings so they stay attached after a wall changes length. */
382
+ function reattachOpenings(floor, wall, warnings) {
383
+ const len = wallLength(wall);
384
+ for (const opening of floor.openings) {
385
+ if (opening.wallId !== wall.id) continue;
386
+ const half = opening.width / 2;
387
+ const clamped = Math.min(Math.max(opening.offset, half), Math.max(half, len - half));
388
+ if (clamped !== opening.offset) {
389
+ opening.offset = clamped;
390
+ warnings.push({
391
+ code: "opening-reclamped",
392
+ message: `Opening ${opening.id} re-positioned to stay on wall`,
393
+ entityId: opening.id
394
+ });
395
+ }
396
+ }
397
+ }
398
+ //#endregion
399
+ //#region src/history.ts
400
+ /**
401
+ * Command-based undo/redo. Keeps full document snapshots per entry which is
402
+ * simple and correct for the MVP; this can later move to patches / op logs
403
+ * without changing the public surface.
404
+ */
405
+ var History = class {
406
+ limit;
407
+ past = [];
408
+ future = [];
409
+ constructor(limit = 200) {
410
+ this.limit = limit;
411
+ }
412
+ push(label, before, after) {
413
+ this.past.push({
414
+ id: createId("hist"),
415
+ label,
416
+ before,
417
+ after
418
+ });
419
+ if (this.past.length > this.limit) this.past.shift();
420
+ this.future = [];
421
+ }
422
+ canUndo() {
423
+ return this.past.length > 0;
424
+ }
425
+ canRedo() {
426
+ return this.future.length > 0;
427
+ }
428
+ undo() {
429
+ const entry = this.past.pop();
430
+ if (!entry) return null;
431
+ this.future.push(entry);
432
+ return entry.before;
433
+ }
434
+ redo() {
435
+ const entry = this.future.pop();
436
+ if (!entry) return null;
437
+ this.past.push(entry);
438
+ return entry.after;
439
+ }
440
+ get undoLabel() {
441
+ return this.past.at(-1)?.label ?? null;
442
+ }
443
+ get redoLabel() {
444
+ return this.future.at(-1)?.label ?? null;
445
+ }
446
+ clear() {
447
+ this.past = [];
448
+ this.future = [];
449
+ }
450
+ };
451
+ //#endregion
452
+ //#region src/serialize.ts
453
+ const migrations = {};
454
+ function migrate(input) {
455
+ let doc = { ...input };
456
+ let guard = 0;
457
+ while (doc.version !== "0.1.0" && guard < 50) {
458
+ const m = migrations[doc.version];
459
+ if (!m) break;
460
+ doc = m(doc);
461
+ guard += 1;
462
+ }
463
+ return doc;
464
+ }
465
+ function serialize(doc) {
466
+ return JSON.stringify(doc, null, 2);
467
+ }
468
+ function deserialize(json) {
469
+ return migrate(JSON.parse(json));
470
+ }
471
+ function isCompatibleVersion(version) {
472
+ const [a, b] = version.split(".");
473
+ const [ca, cb] = MODEL_VERSION.split(".");
474
+ return a === ca && b === cb;
475
+ }
476
+ //#endregion
477
+ //#region src/defaults.ts
478
+ const DEFAULT_MATERIALS = [
479
+ {
480
+ id: "mat-plaster",
481
+ name: "White Plaster",
482
+ category: "wall",
483
+ baseColor: "#e8e6e1",
484
+ roughness: .9
485
+ },
486
+ {
487
+ id: "mat-concrete",
488
+ name: "Concrete",
489
+ category: "wall",
490
+ baseColor: "#9b9b97",
491
+ roughness: .8
492
+ },
493
+ {
494
+ id: "mat-oak",
495
+ name: "Oak Floor",
496
+ category: "floor",
497
+ baseColor: "#b88a52",
498
+ roughness: .6
499
+ },
500
+ {
501
+ id: "mat-dark-wood",
502
+ name: "Dark Wood",
503
+ category: "wood",
504
+ baseColor: "#4a3525",
505
+ roughness: .5
506
+ },
507
+ {
508
+ id: "mat-glass",
509
+ name: "Glass",
510
+ category: "glass",
511
+ baseColor: "#bcd6e6",
512
+ opacity: .35,
513
+ roughness: .05,
514
+ metalness: .1
515
+ },
516
+ {
517
+ id: "mat-metal",
518
+ name: "Metal",
519
+ category: "metal",
520
+ baseColor: "#8a8d91",
521
+ metalness: .9,
522
+ roughness: .3
523
+ },
524
+ {
525
+ id: "mat-tile",
526
+ name: "Ceramic Tile",
527
+ category: "ceramic",
528
+ baseColor: "#d8dcdd",
529
+ roughness: .4
530
+ }
531
+ ];
532
+ function createEmptyDocument(options = {}) {
533
+ const buildingId = createId("bld");
534
+ return {
535
+ id: createId("doc"),
536
+ version: MODEL_VERSION,
537
+ name: options.name ?? "Untitled",
538
+ units: options.units ?? "metric",
539
+ buildings: [{
540
+ id: buildingId,
541
+ name: options.buildingName ?? options.name ?? "Building",
542
+ floors: []
543
+ }],
544
+ materials: [...DEFAULT_MATERIALS],
545
+ assets: [],
546
+ metadata: {}
547
+ };
548
+ }
549
+ //#endregion
550
+ //#region src/furniture.ts
551
+ const CATALOG = {
552
+ bed: {
553
+ width: 1,
554
+ depth: 1,
555
+ height: .5
556
+ },
557
+ sofa: {
558
+ width: 2,
559
+ depth: .9,
560
+ height: .75
561
+ },
562
+ table: {
563
+ width: 1.2,
564
+ depth: .8,
565
+ height: .74
566
+ },
567
+ desk: {
568
+ width: 1.2,
569
+ depth: .6,
570
+ height: .74
571
+ },
572
+ chair: {
573
+ width: .5,
574
+ depth: .5,
575
+ height: .9
576
+ },
577
+ sink: {
578
+ width: .5,
579
+ depth: .42,
580
+ height: .85
581
+ },
582
+ toilet: {
583
+ width: .4,
584
+ depth: .6,
585
+ height: .78
586
+ },
587
+ shower: {
588
+ width: .9,
589
+ depth: .9,
590
+ height: .12
591
+ },
592
+ "kitchen counter": {
593
+ width: 1,
594
+ depth: .6,
595
+ height: .9
596
+ },
597
+ wardrobe: {
598
+ width: 1.2,
599
+ depth: .6,
600
+ height: 2
601
+ }
602
+ };
603
+ const DEFAULT_DIMS = {
604
+ width: .6,
605
+ depth: .6,
606
+ height: .6
607
+ };
608
+ const FURNITURE_TYPES = Object.keys(CATALOG);
609
+ /** Resolve the footprint of a furniture type, applying its scale. */
610
+ function furnitureDims(type, scale = [
611
+ 1,
612
+ 1,
613
+ 1
614
+ ]) {
615
+ const base = CATALOG[type] ?? DEFAULT_DIMS;
616
+ return {
617
+ width: base.width * scale[0],
618
+ depth: base.depth * scale[1],
619
+ height: base.height * scale[2]
620
+ };
621
+ }
622
+ /** A subtle per-category colour for the placeholder geometry. */
623
+ function furnitureColor(type) {
624
+ if (type === "shower" || type === "sink" || type === "toilet") return "#9fb6c2";
625
+ if (type.includes("counter") || type === "table" || type === "desk") return "#9c7b54";
626
+ if (type === "bed" || type === "sofa") return "#7c8aa0";
627
+ return "#8a8f99";
628
+ }
629
+ //#endregion
630
+ export { DEFAULT_MATERIALS, FURNITURE_TYPES, History, MODEL_VERSION, allFloors, createEmptyDocument, createFloor, createOpening, createRoom, createWall, deleteFloor, deleteOpening, deleteRoom, deleteWall, deserialize, duplicateFloor, findFloor, findOpening, findRoom, findWall, furnitureColor, furnitureDims, isCompatibleVersion, migrate, moveOpening, moveWall, rectRoomPolygon, reorderFloor, serialize, updateFloor, updateOpening, updateRoom, updateWall };
631
+
632
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["wl"],"sources":["../src/model.ts","../src/commands.ts","../src/history.ts","../src/serialize.ts","../src/defaults.ts","../src/furniture.ts"],"sourcesContent":["import type { Units, Vec2, Vec3 } from \"@react-arch/shared\";\n\n/** The current on-disk model version. Bump when the schema changes. */\nexport const MODEL_VERSION = \"0.1.0\";\n\nexport type OpeningType = \"door\" | \"window\" | \"opening\";\nexport type SwingDirection = \"in\" | \"out\";\nexport type HingeSide = \"left\" | \"right\";\n\nexport interface Wall {\n id: string;\n floorId: string;\n start: Vec2;\n end: Vec2;\n thickness: number;\n height: number;\n materialId?: string;\n layerId?: string;\n locked?: boolean;\n}\n\nexport interface Opening {\n id: string;\n floorId: string;\n wallId: string;\n type: OpeningType;\n /** Distance along the wall centerline to the opening center. */\n offset: number;\n width: number;\n height: number;\n sillHeight: number;\n swing?: SwingDirection;\n hinge?: HingeSide;\n materialId?: string;\n}\n\nexport interface Room {\n id: string;\n floorId: string;\n name: string;\n /** Explicit polygon (metres). Rooms may also be derived from wall loops. */\n polygon: Vec2[];\n boundaryWallIds?: string[];\n floorMaterialId?: string;\n ceilingMaterialId?: string;\n usageType?: string;\n height?: number;\n}\n\nexport interface BuildingObject {\n id: string;\n floorId: string;\n type: string;\n position: Vec3;\n rotation: Vec3;\n scale: Vec3;\n assetId?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface Floor {\n id: string;\n buildingId: string;\n name: string;\n elevation: number;\n height: number;\n visible: boolean;\n locked: boolean;\n walls: Wall[];\n rooms: Room[];\n openings: Opening[];\n objects: BuildingObject[];\n}\n\nexport interface Building {\n id: string;\n name: string;\n floors: Floor[];\n}\n\nexport type MaterialCategory =\n | \"wall\"\n | \"floor\"\n | \"ceiling\"\n | \"glass\"\n | \"wood\"\n | \"metal\"\n | \"ceramic\"\n | \"custom\";\n\nexport interface Material {\n id: string;\n name: string;\n category: MaterialCategory;\n baseColor: string;\n roughness?: number;\n metalness?: number;\n opacity?: number;\n textureUrl?: string;\n}\n\nexport interface Asset {\n id: string;\n name: string;\n url?: string;\n kind: string;\n}\n\nexport interface Site {\n id: string;\n name: string;\n polygon?: Vec2[];\n}\n\nexport interface BuildingDocument {\n id: string;\n version: string;\n name: string;\n units: Units;\n site?: Site;\n buildings: Building[];\n materials: Material[];\n assets: Asset[];\n metadata: Record<string, unknown>;\n}\n\n/** A single semantic change emitted by a command. */\nexport interface ModelChange {\n kind: \"create\" | \"update\" | \"delete\";\n entity: \"wall\" | \"room\" | \"opening\" | \"floor\" | \"object\" | \"document\";\n id: string;\n}\n\nexport interface ModelWarning {\n code: string;\n message: string;\n entityId?: string;\n}\n\nexport interface CommandResult {\n document: BuildingDocument;\n changes: ModelChange[];\n warnings: ModelWarning[];\n}\n\n// ---------------------------------------------------------------------------\n// Read helpers\n// ---------------------------------------------------------------------------\n\nexport function findFloor(\n doc: BuildingDocument,\n floorId: string,\n): { building: Building; floor: Floor } | undefined {\n for (const building of doc.buildings) {\n const floor = building.floors.find((f) => f.id === floorId);\n if (floor) return { building, floor };\n }\n return undefined;\n}\n\nexport function allFloors(doc: BuildingDocument): Floor[] {\n return doc.buildings.flatMap((b) => b.floors);\n}\n\nexport function findWall(doc: BuildingDocument, wallId: string): Wall | undefined {\n for (const floor of allFloors(doc)) {\n const w = floor.walls.find((x) => x.id === wallId);\n if (w) return w;\n }\n return undefined;\n}\n\nexport function findRoom(doc: BuildingDocument, roomId: string): Room | undefined {\n for (const floor of allFloors(doc)) {\n const r = floor.rooms.find((x) => x.id === roomId);\n if (r) return r;\n }\n return undefined;\n}\n\nexport function findOpening(\n doc: BuildingDocument,\n openingId: string,\n): Opening | undefined {\n for (const floor of allFloors(doc)) {\n const o = floor.openings.find((x) => x.id === openingId);\n if (o) return o;\n }\n return undefined;\n}\n\nexport type EntityKind = \"wall\" | \"room\" | \"opening\" | \"floor\" | \"object\";\n\nexport interface EntityRef {\n kind: EntityKind;\n id: string;\n}\n","import { createId, deepClone, type Vec2 } from \"@react-arch/shared\";\nimport { openingFits, wallLength, type WallLike } from \"@react-arch/geometry\";\nimport {\n type BuildingDocument,\n type CommandResult,\n type Floor,\n type ModelChange,\n type ModelWarning,\n type Opening,\n type OpeningType,\n type Room,\n type Wall,\n findFloor,\n} from \"./model.js\";\n\n/**\n * Commands are pure: they take a document and return a brand-new document plus\n * a list of changes and warnings. They never mutate the input. The studio /\n * editor layers feed these into the history stack for undo/redo.\n */\n\nfunction mutate(\n doc: BuildingDocument,\n fn: (draft: BuildingDocument, warnings: ModelWarning[]) => ModelChange[],\n): CommandResult {\n const draft = deepClone(doc);\n const warnings: ModelWarning[] = [];\n const changes = fn(draft, warnings);\n return { document: draft, changes, warnings };\n}\n\nfunction requireFloor(doc: BuildingDocument, floorId: string): Floor {\n const found = findFloor(doc, floorId);\n if (!found) throw new Error(`Floor not found: ${floorId}`);\n return found.floor;\n}\n\n// --- Walls -----------------------------------------------------------------\n\nexport interface CreateWallInput {\n floorId: string;\n start: Vec2;\n end: Vec2;\n thickness?: number;\n height?: number;\n materialId?: string;\n id?: string;\n}\n\nexport function createWall(\n doc: BuildingDocument,\n input: CreateWallInput,\n): CommandResult {\n return mutate(doc, (draft) => {\n const floor = requireFloor(draft, input.floorId);\n const wall: Wall = {\n id: input.id ?? createId(\"wall\"),\n floorId: input.floorId,\n start: input.start,\n end: input.end,\n thickness: input.thickness ?? 0.2,\n height: input.height ?? floor.height,\n materialId: input.materialId,\n };\n floor.walls.push(wall);\n return [{ kind: \"create\", entity: \"wall\", id: wall.id }];\n });\n}\n\nexport function updateWall(\n doc: BuildingDocument,\n wallId: string,\n patch: Partial<Omit<Wall, \"id\" | \"floorId\">>,\n): CommandResult {\n return mutate(doc, (draft, warnings) => {\n for (const floor of allFloorsOf(draft)) {\n const wall = floor.walls.find((w) => w.id === wallId);\n if (!wall) continue;\n Object.assign(wall, patch);\n reattachOpenings(floor, wall, warnings);\n return [{ kind: \"update\", entity: \"wall\", id: wallId }];\n }\n warnings.push({ code: \"wall-missing\", message: `Wall ${wallId} not found` });\n return [];\n });\n}\n\n/** Move a wall's endpoints, keeping attached openings within bounds. */\nexport function moveWall(\n doc: BuildingDocument,\n wallId: string,\n start: Vec2,\n end: Vec2,\n): CommandResult {\n return updateWall(doc, wallId, { start, end });\n}\n\nexport function deleteWall(doc: BuildingDocument, wallId: string): CommandResult {\n return mutate(doc, (draft) => {\n const changes: ModelChange[] = [];\n for (const floor of allFloorsOf(draft)) {\n const idx = floor.walls.findIndex((w) => w.id === wallId);\n if (idx === -1) continue;\n floor.walls.splice(idx, 1);\n changes.push({ kind: \"delete\", entity: \"wall\", id: wallId });\n // Cascade: drop openings attached to the wall.\n floor.openings = floor.openings.filter((o) => {\n if (o.wallId === wallId) {\n changes.push({ kind: \"delete\", entity: \"opening\", id: o.id });\n return false;\n }\n return true;\n });\n break;\n }\n return changes;\n });\n}\n\n// --- Rooms -----------------------------------------------------------------\n\nexport interface CreateRoomInput {\n floorId: string;\n name: string;\n polygon: Vec2[];\n id?: string;\n floorMaterialId?: string;\n ceilingMaterialId?: string;\n usageType?: string;\n}\n\n/** Convenience: build a rectangular room from origin + size. */\nexport function rectRoomPolygon(\n x: number,\n y: number,\n width: number,\n depth: number,\n): Vec2[] {\n return [\n [x, y],\n [x + width, y],\n [x + width, y + depth],\n [x, y + depth],\n ];\n}\n\nexport function createRoom(\n doc: BuildingDocument,\n input: CreateRoomInput,\n): CommandResult {\n return mutate(doc, (draft) => {\n const floor = requireFloor(draft, input.floorId);\n const room: Room = {\n id: input.id ?? createId(\"room\"),\n floorId: input.floorId,\n name: input.name,\n polygon: input.polygon,\n floorMaterialId: input.floorMaterialId,\n ceilingMaterialId: input.ceilingMaterialId,\n usageType: input.usageType,\n };\n floor.rooms.push(room);\n return [{ kind: \"create\", entity: \"room\", id: room.id }];\n });\n}\n\nexport function updateRoom(\n doc: BuildingDocument,\n roomId: string,\n patch: Partial<Omit<Room, \"id\" | \"floorId\">>,\n): CommandResult {\n return mutate(doc, (draft, warnings) => {\n for (const floor of allFloorsOf(draft)) {\n const room = floor.rooms.find((r) => r.id === roomId);\n if (!room) continue;\n Object.assign(room, patch);\n return [{ kind: \"update\", entity: \"room\", id: roomId }];\n }\n warnings.push({ code: \"room-missing\", message: `Room ${roomId} not found` });\n return [];\n });\n}\n\nexport function deleteRoom(doc: BuildingDocument, roomId: string): CommandResult {\n return mutate(doc, (draft) => {\n for (const floor of allFloorsOf(draft)) {\n const idx = floor.rooms.findIndex((r) => r.id === roomId);\n if (idx === -1) continue;\n floor.rooms.splice(idx, 1);\n return [{ kind: \"delete\", entity: \"room\", id: roomId }];\n }\n return [];\n });\n}\n\n// --- Openings --------------------------------------------------------------\n\nexport interface CreateOpeningInput {\n wallId: string;\n type: OpeningType;\n offset: number;\n width: number;\n height: number;\n sillHeight?: number;\n id?: string;\n}\n\nexport function createOpening(\n doc: BuildingDocument,\n input: CreateOpeningInput,\n): CommandResult {\n return mutate(doc, (draft, warnings) => {\n for (const floor of allFloorsOf(draft)) {\n const wall = floor.walls.find((w) => w.id === input.wallId);\n if (!wall) continue;\n const opening: Opening = {\n id: input.id ?? createId(\"opening\"),\n floorId: floor.id,\n wallId: input.wallId,\n type: input.type,\n offset: input.offset,\n width: input.width,\n height: input.height,\n sillHeight: input.sillHeight ?? (input.type === \"window\" ? 0.9 : 0),\n };\n if (!openingFits(wall, opening)) {\n warnings.push({\n code: \"opening-overflow\",\n message: `Opening ${opening.id} does not fit within wall ${wall.id}`,\n entityId: opening.id,\n });\n }\n floor.openings.push(opening);\n return [{ kind: \"create\", entity: \"opening\", id: opening.id }];\n }\n warnings.push({\n code: \"wall-missing\",\n message: `Wall ${input.wallId} not found for opening`,\n });\n return [];\n });\n}\n\nexport function updateOpening(\n doc: BuildingDocument,\n openingId: string,\n patch: Partial<Omit<Opening, \"id\" | \"floorId\" | \"wallId\">>,\n): CommandResult {\n return mutate(doc, (draft, warnings) => {\n for (const floor of allFloorsOf(draft)) {\n const opening = floor.openings.find((o) => o.id === openingId);\n if (!opening) continue;\n Object.assign(opening, patch);\n const wall = floor.walls.find((w) => w.id === opening.wallId);\n if (wall && !openingFits(wall, opening)) {\n warnings.push({\n code: \"opening-overflow\",\n message: `Opening ${opening.id} no longer fits its wall`,\n entityId: opening.id,\n });\n }\n return [{ kind: \"update\", entity: \"opening\", id: openingId }];\n }\n return [];\n });\n}\n\nexport function moveOpening(\n doc: BuildingDocument,\n openingId: string,\n offset: number,\n): CommandResult {\n return updateOpening(doc, openingId, { offset });\n}\n\nexport function deleteOpening(\n doc: BuildingDocument,\n openingId: string,\n): CommandResult {\n return mutate(doc, (draft) => {\n for (const floor of allFloorsOf(draft)) {\n const idx = floor.openings.findIndex((o) => o.id === openingId);\n if (idx === -1) continue;\n floor.openings.splice(idx, 1);\n return [{ kind: \"delete\", entity: \"opening\", id: openingId }];\n }\n return [];\n });\n}\n\n// --- Floors ----------------------------------------------------------------\n\nexport interface CreateFloorInput {\n buildingId: string;\n name: string;\n elevation: number;\n height?: number;\n id?: string;\n}\n\nexport function createFloor(\n doc: BuildingDocument,\n input: CreateFloorInput,\n): CommandResult {\n return mutate(doc, (draft, warnings) => {\n const building = draft.buildings.find((b) => b.id === input.buildingId);\n if (!building) {\n warnings.push({ code: \"building-missing\", message: \"Building not found\" });\n return [];\n }\n const floor: Floor = {\n id: input.id ?? createId(\"floor\"),\n buildingId: building.id,\n name: input.name,\n elevation: input.elevation,\n height: input.height ?? 2.8,\n visible: true,\n locked: false,\n walls: [],\n rooms: [],\n openings: [],\n objects: [],\n };\n building.floors.push(floor);\n building.floors.sort((a, b) => a.elevation - b.elevation);\n return [{ kind: \"create\", entity: \"floor\", id: floor.id }];\n });\n}\n\nexport function duplicateFloor(\n doc: BuildingDocument,\n floorId: string,\n): CommandResult {\n return mutate(doc, (draft) => {\n for (const building of draft.buildings) {\n const floor = building.floors.find((f) => f.id === floorId);\n if (!floor) continue;\n const idMap = new Map<string, string>();\n const copy: Floor = deepClone(floor);\n copy.id = createId(\"floor\");\n copy.name = `${floor.name} (copy)`;\n copy.elevation = floor.elevation + floor.height;\n // Re-id child entities and keep opening→wall references consistent.\n copy.walls.forEach((w) => {\n const nid = createId(\"wall\");\n idMap.set(w.id, nid);\n w.id = nid;\n w.floorId = copy.id;\n });\n copy.openings.forEach((o) => {\n o.id = createId(\"opening\");\n o.floorId = copy.id;\n o.wallId = idMap.get(o.wallId) ?? o.wallId;\n });\n copy.rooms.forEach((r) => {\n r.id = createId(\"room\");\n r.floorId = copy.id;\n });\n copy.objects.forEach((ob) => {\n ob.id = createId(\"object\");\n ob.floorId = copy.id;\n });\n building.floors.push(copy);\n building.floors.sort((a, b) => a.elevation - b.elevation);\n return [{ kind: \"create\", entity: \"floor\", id: copy.id }];\n }\n return [];\n });\n}\n\nexport function reorderFloor(\n doc: BuildingDocument,\n floorId: string,\n newElevation: number,\n): CommandResult {\n return mutate(doc, (draft) => {\n const floor = requireFloor(draft, floorId);\n floor.elevation = newElevation;\n for (const building of draft.buildings) {\n building.floors.sort((a, b) => a.elevation - b.elevation);\n }\n return [{ kind: \"update\", entity: \"floor\", id: floorId }];\n });\n}\n\nexport function updateFloor(\n doc: BuildingDocument,\n floorId: string,\n patch: Partial<Pick<Floor, \"name\" | \"elevation\" | \"height\" | \"visible\" | \"locked\">>,\n): CommandResult {\n return mutate(doc, (draft) => {\n const floor = requireFloor(draft, floorId);\n Object.assign(floor, patch);\n return [{ kind: \"update\", entity: \"floor\", id: floorId }];\n });\n}\n\nexport function deleteFloor(doc: BuildingDocument, floorId: string): CommandResult {\n return mutate(doc, (draft) => {\n for (const building of draft.buildings) {\n const idx = building.floors.findIndex((f) => f.id === floorId);\n if (idx === -1) continue;\n building.floors.splice(idx, 1);\n return [{ kind: \"delete\", entity: \"floor\", id: floorId }];\n }\n return [];\n });\n}\n\n// --- internals -------------------------------------------------------------\n\nfunction allFloorsOf(doc: BuildingDocument): Floor[] {\n return doc.buildings.flatMap((b) => b.floors);\n}\n\n/** Clamp openings so they stay attached after a wall changes length. */\nfunction reattachOpenings(floor: Floor, wall: Wall, warnings: ModelWarning[]): void {\n const wl: WallLike = wall;\n const len = wallLength(wl);\n for (const opening of floor.openings) {\n if (opening.wallId !== wall.id) continue;\n const half = opening.width / 2;\n const clamped = Math.min(Math.max(opening.offset, half), Math.max(half, len - half));\n if (clamped !== opening.offset) {\n opening.offset = clamped;\n warnings.push({\n code: \"opening-reclamped\",\n message: `Opening ${opening.id} re-positioned to stay on wall`,\n entityId: opening.id,\n });\n }\n }\n}\n","import { createId } from \"@react-arch/shared\";\nimport type { BuildingDocument } from \"./model.js\";\n\nexport interface HistoryEntry {\n id: string;\n label: string;\n before: BuildingDocument;\n after: BuildingDocument;\n}\n\n/**\n * Command-based undo/redo. Keeps full document snapshots per entry which is\n * simple and correct for the MVP; this can later move to patches / op logs\n * without changing the public surface.\n */\nexport class History {\n private past: HistoryEntry[] = [];\n private future: HistoryEntry[] = [];\n\n constructor(private limit = 200) {}\n\n push(label: string, before: BuildingDocument, after: BuildingDocument): void {\n this.past.push({ id: createId(\"hist\"), label, before, after });\n if (this.past.length > this.limit) this.past.shift();\n this.future = [];\n }\n\n canUndo(): boolean {\n return this.past.length > 0;\n }\n\n canRedo(): boolean {\n return this.future.length > 0;\n }\n\n undo(): BuildingDocument | null {\n const entry = this.past.pop();\n if (!entry) return null;\n this.future.push(entry);\n return entry.before;\n }\n\n redo(): BuildingDocument | null {\n const entry = this.future.pop();\n if (!entry) return null;\n this.past.push(entry);\n return entry.after;\n }\n\n get undoLabel(): string | null {\n return this.past.at(-1)?.label ?? null;\n }\n\n get redoLabel(): string | null {\n return this.future.at(-1)?.label ?? null;\n }\n\n clear(): void {\n this.past = [];\n this.future = [];\n }\n}\n","import { MODEL_VERSION, type BuildingDocument } from \"./model.js\";\n\n/**\n * Migration registry. Each migration upgrades a document one version forward.\n * Add new entries as the schema evolves; `migrate` chains them in order.\n */\ntype Migration = (doc: Record<string, unknown>) => Record<string, unknown>;\n\nconst migrations: Record<string, Migration> = {\n // Example placeholder for the first real migration:\n // \"0.0.1\": (doc) => ({ ...doc, version: \"0.1.0\", assets: doc.assets ?? [] }),\n};\n\nexport function migrate(input: Record<string, unknown>): Record<string, unknown> {\n let doc = { ...input };\n let guard = 0;\n while (doc.version !== MODEL_VERSION && guard < 50) {\n const m = migrations[doc.version as string];\n if (!m) break; // No path forward; validation will report the mismatch.\n doc = m(doc);\n guard += 1;\n }\n return doc;\n}\n\nexport function serialize(doc: BuildingDocument): string {\n return JSON.stringify(doc, null, 2);\n}\n\nexport function deserialize(json: string): BuildingDocument {\n const raw = JSON.parse(json) as Record<string, unknown>;\n return migrate(raw) as unknown as BuildingDocument;\n}\n\nexport function isCompatibleVersion(version: string): boolean {\n // Same major.minor is considered compatible for the MVP.\n const [a, b] = version.split(\".\");\n const [ca, cb] = MODEL_VERSION.split(\".\");\n return a === ca && b === cb;\n}\n","import { createId } from \"@react-arch/shared\";\nimport type { BuildingDocument, Material } from \"./model.js\";\nimport { MODEL_VERSION } from \"./model.js\";\n\nexport const DEFAULT_MATERIALS: Material[] = [\n { id: \"mat-plaster\", name: \"White Plaster\", category: \"wall\", baseColor: \"#e8e6e1\", roughness: 0.9 },\n { id: \"mat-concrete\", name: \"Concrete\", category: \"wall\", baseColor: \"#9b9b97\", roughness: 0.8 },\n { id: \"mat-oak\", name: \"Oak Floor\", category: \"floor\", baseColor: \"#b88a52\", roughness: 0.6 },\n { id: \"mat-dark-wood\", name: \"Dark Wood\", category: \"wood\", baseColor: \"#4a3525\", roughness: 0.5 },\n { id: \"mat-glass\", name: \"Glass\", category: \"glass\", baseColor: \"#bcd6e6\", opacity: 0.35, roughness: 0.05, metalness: 0.1 },\n { id: \"mat-metal\", name: \"Metal\", category: \"metal\", baseColor: \"#8a8d91\", metalness: 0.9, roughness: 0.3 },\n { id: \"mat-tile\", name: \"Ceramic Tile\", category: \"ceramic\", baseColor: \"#d8dcdd\", roughness: 0.4 },\n];\n\nexport interface EmptyDocumentOptions {\n name?: string;\n units?: \"metric\" | \"imperial\";\n buildingName?: string;\n}\n\nexport function createEmptyDocument(\n options: EmptyDocumentOptions = {},\n): BuildingDocument {\n const buildingId = createId(\"bld\");\n return {\n id: createId(\"doc\"),\n version: MODEL_VERSION,\n name: options.name ?? \"Untitled\",\n units: options.units ?? \"metric\",\n buildings: [\n {\n id: buildingId,\n name: options.buildingName ?? options.name ?? \"Building\",\n floors: [],\n },\n ],\n materials: [...DEFAULT_MATERIALS],\n assets: [],\n metadata: {},\n };\n}\n","import type { Vec3 } from \"@react-arch/shared\";\n\n/**\n * Placeholder furniture geometry. Each entry is the base footprint (metres) at\n * scale 1: `w` along plan X, `d` along plan Y, `h` vertical. A building object's\n * `scale` multiplies these. Intentionally simple boxes for the MVP — no\n * photorealistic assets.\n */\nexport interface FurnitureDims {\n width: number;\n depth: number;\n height: number;\n}\n\nconst CATALOG: Record<string, FurnitureDims> = {\n bed: { width: 1.0, depth: 1.0, height: 0.5 },\n sofa: { width: 2.0, depth: 0.9, height: 0.75 },\n table: { width: 1.2, depth: 0.8, height: 0.74 },\n desk: { width: 1.2, depth: 0.6, height: 0.74 },\n chair: { width: 0.5, depth: 0.5, height: 0.9 },\n sink: { width: 0.5, depth: 0.42, height: 0.85 },\n toilet: { width: 0.4, depth: 0.6, height: 0.78 },\n shower: { width: 0.9, depth: 0.9, height: 0.12 },\n \"kitchen counter\": { width: 1.0, depth: 0.6, height: 0.9 },\n wardrobe: { width: 1.2, depth: 0.6, height: 2.0 },\n};\n\nconst DEFAULT_DIMS: FurnitureDims = { width: 0.6, depth: 0.6, height: 0.6 };\n\nexport const FURNITURE_TYPES = Object.keys(CATALOG);\n\n/** Resolve the footprint of a furniture type, applying its scale. */\nexport function furnitureDims(type: string, scale: Vec3 = [1, 1, 1]): FurnitureDims {\n const base = CATALOG[type] ?? DEFAULT_DIMS;\n return {\n width: base.width * scale[0],\n depth: base.depth * scale[1],\n height: base.height * scale[2],\n };\n}\n\n/** A subtle per-category colour for the placeholder geometry. */\nexport function furnitureColor(type: string): string {\n if (type === \"shower\" || type === \"sink\" || type === \"toilet\") return \"#9fb6c2\";\n if (type.includes(\"counter\") || type === \"table\" || type === \"desk\") return \"#9c7b54\";\n if (type === \"bed\" || type === \"sofa\") return \"#7c8aa0\";\n return \"#8a8f99\";\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@react-arch/core",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "@react-arch/shared": "0.1.0",
16
+ "@react-arch/geometry": "0.1.0"
17
+ },
18
+ "devDependencies": {
19
+ "typescript": "^5.7.2",
20
+ "vitest": "^2.1.8"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/react-arch/react-arch.git",
31
+ "directory": "packages/core"
32
+ },
33
+ "license": "MIT",
34
+ "scripts": {
35
+ "typecheck": "tsc --noEmit",
36
+ "test": "vitest run --passWithNoTests",
37
+ "lint": "oxlint src",
38
+ "build": "tsdown"
39
+ }
40
+ }