@pascal-app/core 0.1.11 → 0.1.13

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.
Files changed (48) hide show
  1. package/dist/events/bus.d.ts +10 -2
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/hooks/scene-registry/scene-registry.d.ts +1 -0
  4. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  5. package/dist/hooks/scene-registry/scene-registry.js +1 -0
  6. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +9 -4
  7. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  8. package/dist/hooks/spatial-grid/spatial-grid-manager.js +58 -13
  9. package/dist/hooks/spatial-grid/use-spatial-query.d.ts +5 -0
  10. package/dist/hooks/spatial-grid/use-spatial-query.d.ts.map +1 -1
  11. package/dist/hooks/spatial-grid/wall-spatial-grid.d.ts +4 -1
  12. package/dist/hooks/spatial-grid/wall-spatial-grid.d.ts.map +1 -1
  13. package/dist/hooks/spatial-grid/wall-spatial-grid.js +33 -8
  14. package/dist/index.d.ts +5 -4
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +6 -5
  17. package/dist/schema/index.d.ts +1 -0
  18. package/dist/schema/index.d.ts.map +1 -1
  19. package/dist/schema/index.js +1 -0
  20. package/dist/schema/nodes/ceiling.d.ts +1 -0
  21. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  22. package/dist/schema/nodes/ceiling.js +2 -0
  23. package/dist/schema/nodes/item.d.ts +9 -0
  24. package/dist/schema/nodes/item.d.ts.map +1 -1
  25. package/dist/schema/nodes/item.js +8 -0
  26. package/dist/schema/nodes/site.d.ts +5 -0
  27. package/dist/schema/nodes/site.d.ts.map +1 -1
  28. package/dist/schema/nodes/slab.d.ts +1 -0
  29. package/dist/schema/nodes/slab.d.ts.map +1 -1
  30. package/dist/schema/nodes/slab.js +1 -0
  31. package/dist/schema/nodes/window.d.ts +40 -0
  32. package/dist/schema/nodes/window.d.ts.map +1 -0
  33. package/dist/schema/nodes/window.js +38 -0
  34. package/dist/schema/types.d.ts +48 -0
  35. package/dist/schema/types.d.ts.map +1 -1
  36. package/dist/schema/types.js +2 -0
  37. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  38. package/dist/systems/ceiling/ceiling-system.js +15 -0
  39. package/dist/systems/item/item-system.d.ts.map +1 -1
  40. package/dist/systems/item/item-system.js +8 -4
  41. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  42. package/dist/systems/slab/slab-system.js +15 -0
  43. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  44. package/dist/systems/wall/wall-system.js +15 -9
  45. package/dist/systems/window/window-system.d.ts +2 -0
  46. package/dist/systems/window/window-system.d.ts.map +1 -0
  47. package/dist/systems/window/window-system.js +147 -0
  48. package/package.json +6 -3
@@ -1,5 +1,5 @@
1
1
  import type { ThreeEvent } from '@react-three/fiber';
2
- import type { BuildingNode, CeilingNode, ItemNode, LevelNode, RoofNode, SiteNode, SlabNode, WallNode, ZoneNode } from '../schema';
2
+ import type { BuildingNode, CeilingNode, ItemNode, LevelNode, RoofNode, SiteNode, SlabNode, WallNode, WindowNode, ZoneNode } from '../schema';
3
3
  import type { AnyNode } from '../schema/types';
4
4
  export interface GridEvent {
5
5
  position: [number, number, number];
@@ -22,6 +22,7 @@ export type ZoneEvent = NodeEvent<ZoneNode>;
22
22
  export type SlabEvent = NodeEvent<SlabNode>;
23
23
  export type CeilingEvent = NodeEvent<CeilingNode>;
24
24
  export type RoofEvent = NodeEvent<RoofNode>;
25
+ export type WindowEvent = NodeEvent<WindowNode>;
25
26
  export declare const eventSuffixes: readonly ["click", "move", "enter", "leave", "pointerdown", "pointerup", "context-menu", "double-click"];
26
27
  export type EventSuffix = (typeof eventSuffixes)[number];
27
28
  type NodeEvents<T extends string, E> = {
@@ -33,14 +34,21 @@ type GridEvents = {
33
34
  export interface CameraControlEvent {
34
35
  nodeId: AnyNode['id'];
35
36
  }
37
+ export interface ThumbnailGenerateEvent {
38
+ projectId: string;
39
+ }
36
40
  type CameraControlEvents = {
37
41
  'camera-controls:view': CameraControlEvent;
38
42
  'camera-controls:capture': CameraControlEvent;
39
43
  'camera-controls:top-view': undefined;
40
44
  'camera-controls:orbit-cw': undefined;
41
45
  'camera-controls:orbit-ccw': undefined;
46
+ 'camera-controls:generate-thumbnail': ThumbnailGenerateEvent;
47
+ };
48
+ type ToolEvents = {
49
+ 'tool:cancel': undefined;
42
50
  };
43
- type EditorEvents = GridEvents & NodeEvents<'wall', WallEvent> & NodeEvents<'item', ItemEvent> & NodeEvents<'site', SiteEvent> & NodeEvents<'building', BuildingEvent> & NodeEvents<'level', LevelEvent> & NodeEvents<'zone', ZoneEvent> & NodeEvents<'slab', SlabEvent> & NodeEvents<'ceiling', CeilingEvent> & NodeEvents<'roof', RoofEvent> & CameraControlEvents;
51
+ type EditorEvents = GridEvents & NodeEvents<'wall', WallEvent> & NodeEvents<'item', ItemEvent> & NodeEvents<'site', SiteEvent> & NodeEvents<'building', BuildingEvent> & NodeEvents<'level', LevelEvent> & NodeEvents<'zone', ZoneEvent> & NodeEvents<'slab', SlabEvent> & NodeEvents<'ceiling', CeilingEvent> & NodeEvents<'roof', RoofEvent> & NodeEvents<'window', WindowEvent> & CameraControlEvents & ToolEvents;
44
52
  export declare const emitter: import("mitt").Emitter<EditorEvents>;
45
53
  export {};
46
54
  //# sourceMappingURL=bus.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bus.d.ts","sourceRoot":"","sources":["../../src/events/bus.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bus.d.ts","sourceRoot":"","sources":["../../src/events/bus.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,7 @@ export declare const sceneRegistry: {
13
13
  roof: Set<string>;
14
14
  scan: Set<string>;
15
15
  guide: Set<string>;
16
+ window: Set<string>;
16
17
  };
17
18
  };
18
19
  export declare function useRegistry(id: string, type: keyof typeof sceneRegistry.byType, ref: React.RefObject<THREE.Object3D>): void;
@@ -1 +1 @@
1
- {"version":3,"file":"scene-registry.d.ts","sourceRoot":"","sources":["../../../src/hooks/scene-registry/scene-registry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAEpC,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;CAmBzB,CAAC;AAEF,wBAAgB,WAAW,CACzB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,OAAO,aAAa,CAAC,MAAM,EACvC,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,QAkBrC"}
1
+ {"version":3,"file":"scene-registry.d.ts","sourceRoot":"","sources":["../../../src/hooks/scene-registry/scene-registry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAEpC,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;CAoBzB,CAAC;AAEF,wBAAgB,WAAW,CACzB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,OAAO,aAAa,CAAC,MAAM,EACvC,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,QAkBrC"}
@@ -16,6 +16,7 @@ export const sceneRegistry = {
16
16
  roof: new Set(),
17
17
  scan: new Set(),
18
18
  guide: new Set(),
19
+ window: new Set(),
19
20
  },
20
21
  };
21
22
  export function useRegistry(id, type, ref) {
@@ -56,28 +56,33 @@ export declare class SpatialGridManager {
56
56
  canPlaceOnWall(levelId: string, wallId: string, localX: number, localY: number, dimensions: [number, number, number], attachType?: 'wall' | 'wall-side', side?: 'front' | 'back', ignoreIds?: string[]): {
57
57
  valid: boolean;
58
58
  conflictIds: string[];
59
+ adjustedY: number;
60
+ wasAdjusted: boolean;
61
+ } | {
62
+ valid: boolean;
63
+ conflictIds: never[];
59
64
  };
60
65
  getWallForItem(levelId: string, itemId: string): string | undefined;
61
66
  /**
62
67
  * Get the total slab elevation at a given (x, z) position on a level.
63
- * Returns the highest slab elevation if the point is inside any slab polygon, otherwise 0.
68
+ * Returns the highest slab elevation if the point is inside any slab polygon (but not in any holes), otherwise 0.
64
69
  */
65
70
  getSlabElevationAt(levelId: string, x: number, z: number): number;
66
71
  /**
67
72
  * Get the slab elevation for an item using its full footprint (bounding box).
68
- * Checks if any part of the item's rotated footprint overlaps with any slab polygon.
73
+ * Checks if any part of the item's rotated footprint overlaps with any slab polygon (excluding holes).
69
74
  * Returns the highest overlapping slab elevation, or 0 if none.
70
75
  */
71
76
  getSlabElevationForItem(levelId: string, position: [number, number, number], dimensions: [number, number, number], rotation: [number, number, number]): number;
72
77
  /**
73
- * Get the slab elevation for a wall by checking if it overlaps with any slab polygon.
78
+ * Get the slab elevation for a wall by checking if it overlaps with any slab polygon (excluding holes).
74
79
  * Uses wallOverlapsPolygon which handles edge cases (points on boundary, collinear segments).
75
80
  * Returns the highest slab elevation found, or 0 if none.
76
81
  */
77
82
  getSlabElevationForWall(levelId: string, start: [number, number], end: [number, number]): number;
78
83
  /**
79
84
  * Check if an item can be placed on a ceiling.
80
- * Validates that the footprint is within the ceiling polygon and doesn't overlap other ceiling items.
85
+ * Validates that the footprint is within the ceiling polygon (but not in any holes) and doesn't overlap other ceiling items.
81
86
  */
82
87
  canPlaceOnCeiling(ceilingId: string, position: [number, number, number], dimensions: [number, number, number], rotation: [number, number, number], ignoreIds?: string[]): {
83
88
  valid: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"spatial-grid-manager.d.ts","sourceRoot":"","sources":["../../../src/hooks/spatial-grid/spatial-grid-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"spatial-grid-manager.d.ts","sourceRoot":"","sources":["../../../src/hooks/spatial-grid/spatial-grid-manager.ts"],"names":[],"mappings":"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"}
@@ -369,7 +369,7 @@ export class SpatialGridManager {
369
369
  }
370
370
  /**
371
371
  * Get the total slab elevation at a given (x, z) position on a level.
372
- * Returns the highest slab elevation if the point is inside any slab polygon, otherwise 0.
372
+ * Returns the highest slab elevation if the point is inside any slab polygon (but not in any holes), otherwise 0.
373
373
  */
374
374
  getSlabElevationAt(levelId, x, z) {
375
375
  const slabMap = this.slabsByLevel.get(levelId);
@@ -378,9 +378,20 @@ export class SpatialGridManager {
378
378
  let maxElevation = 0;
379
379
  for (const slab of slabMap.values()) {
380
380
  if (slab.polygon.length >= 3 && pointInPolygon(x, z, slab.polygon)) {
381
- const elevation = slab.elevation ?? 0.05;
382
- if (elevation > maxElevation) {
383
- maxElevation = elevation;
381
+ // Check if point is in any hole
382
+ let inHole = false;
383
+ const holes = slab.holes || [];
384
+ for (const hole of holes) {
385
+ if (hole.length >= 3 && pointInPolygon(x, z, hole)) {
386
+ inHole = true;
387
+ break;
388
+ }
389
+ }
390
+ if (!inHole) {
391
+ const elevation = slab.elevation ?? 0.05;
392
+ if (elevation > maxElevation) {
393
+ maxElevation = elevation;
394
+ }
384
395
  }
385
396
  }
386
397
  }
@@ -388,7 +399,7 @@ export class SpatialGridManager {
388
399
  }
389
400
  /**
390
401
  * Get the slab elevation for an item using its full footprint (bounding box).
391
- * Checks if any part of the item's rotated footprint overlaps with any slab polygon.
402
+ * Checks if any part of the item's rotated footprint overlaps with any slab polygon (excluding holes).
392
403
  * Returns the highest overlapping slab elevation, or 0 if none.
393
404
  */
394
405
  getSlabElevationForItem(levelId, position, dimensions, rotation) {
@@ -398,16 +409,29 @@ export class SpatialGridManager {
398
409
  let maxElevation = -Infinity;
399
410
  for (const slab of slabMap.values()) {
400
411
  if (slab.polygon.length >= 3 && itemOverlapsPolygon(position, dimensions, rotation, slab.polygon, 0.01)) {
401
- const elevation = slab.elevation ?? 0.05;
402
- if (elevation > maxElevation) {
403
- maxElevation = elevation;
412
+ // Check if item is entirely within a hole (if so, ignore this slab)
413
+ // We consider it entirely in a hole if the item center is in the hole
414
+ let inHole = false;
415
+ const [cx, , cz] = position;
416
+ const holes = slab.holes || [];
417
+ for (const hole of holes) {
418
+ if (hole.length >= 3 && pointInPolygon(cx, cz, hole)) {
419
+ inHole = true;
420
+ break;
421
+ }
422
+ }
423
+ if (!inHole) {
424
+ const elevation = slab.elevation ?? 0.05;
425
+ if (elevation > maxElevation) {
426
+ maxElevation = elevation;
427
+ }
404
428
  }
405
429
  }
406
430
  }
407
431
  return maxElevation === -Infinity ? 0 : maxElevation;
408
432
  }
409
433
  /**
410
- * Get the slab elevation for a wall by checking if it overlaps with any slab polygon.
434
+ * Get the slab elevation for a wall by checking if it overlaps with any slab polygon (excluding holes).
411
435
  * Uses wallOverlapsPolygon which handles edge cases (points on boundary, collinear segments).
412
436
  * Returns the highest slab elevation found, or 0 if none.
413
437
  */
@@ -420,9 +444,22 @@ export class SpatialGridManager {
420
444
  if (slab.polygon.length < 3)
421
445
  continue;
422
446
  if (wallOverlapsPolygon(start, end, slab.polygon)) {
423
- const elevation = slab.elevation ?? 0.05;
424
- if (elevation > maxElevation) {
425
- maxElevation = elevation;
447
+ // Check if wall midpoint is in a hole (if so, ignore this slab)
448
+ let inHole = false;
449
+ const midX = (start[0] + end[0]) / 2;
450
+ const midZ = (start[1] + end[1]) / 2;
451
+ const holes = slab.holes || [];
452
+ for (const hole of holes) {
453
+ if (hole.length >= 3 && pointInPolygon(midX, midZ, hole)) {
454
+ inHole = true;
455
+ break;
456
+ }
457
+ }
458
+ if (!inHole) {
459
+ const elevation = slab.elevation ?? 0.05;
460
+ if (elevation > maxElevation) {
461
+ maxElevation = elevation;
462
+ }
426
463
  }
427
464
  }
428
465
  }
@@ -430,7 +467,7 @@ export class SpatialGridManager {
430
467
  }
431
468
  /**
432
469
  * Check if an item can be placed on a ceiling.
433
- * Validates that the footprint is within the ceiling polygon and doesn't overlap other ceiling items.
470
+ * Validates that the footprint is within the ceiling polygon (but not in any holes) and doesn't overlap other ceiling items.
434
471
  */
435
472
  canPlaceOnCeiling(ceilingId, position, dimensions, rotation, ignoreIds) {
436
473
  const ceiling = this.ceilings.get(ceilingId);
@@ -444,6 +481,14 @@ export class SpatialGridManager {
444
481
  return { valid: false, conflictIds: [] };
445
482
  }
446
483
  }
484
+ // Check if item center is in any hole (if so, it cannot be placed)
485
+ const [centerX, , centerZ] = position;
486
+ const holes = ceiling.holes || [];
487
+ for (const hole of holes) {
488
+ if (hole.length >= 3 && pointInPolygon(centerX, centerZ, hole)) {
489
+ return { valid: false, conflictIds: [] };
490
+ }
491
+ }
447
492
  // Check for overlaps with other ceiling items
448
493
  return this.getCeilingGrid(ceilingId).canPlace(position, dimensions, rotation, ignoreIds);
449
494
  }
@@ -7,6 +7,11 @@ export declare function useSpatialQuery(): {
7
7
  canPlaceOnWall: (levelId: LevelNode["id"], wallId: WallNode["id"], localX: number, localY: number, dimensions: [number, number, number], attachType?: "wall" | "wall-side", side?: "front" | "back", ignoreIds?: string[]) => {
8
8
  valid: boolean;
9
9
  conflictIds: string[];
10
+ adjustedY: number;
11
+ wasAdjusted: boolean;
12
+ } | {
13
+ valid: boolean;
14
+ conflictIds: never[];
10
15
  };
11
16
  canPlaceOnCeiling: (ceilingId: CeilingNode["id"], position: [number, number, number], dimensions: [number, number, number], rotation: [number, number, number], ignoreIds?: string[]) => {
12
17
  valid: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"use-spatial-query.d.ts","sourceRoot":"","sources":["../../../src/hooks/spatial-grid/use-spatial-query.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAGpE,wBAAgB,eAAe;+BAGhB,SAAS,CAAC,IAAI,CAAC,YACd,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,cACtB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,YAC1B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,cACtB,MAAM,EAAE;;;;8BASX,SAAS,CAAC,IAAI,CAAC,UAChB,QAAQ,CAAC,IAAI,CAAC,UACd,MAAM,UACN,MAAM,cACF,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,eACxB,MAAM,GAAG,WAAW,SACzB,OAAO,GAAG,MAAM,cACX,MAAM,EAAE;;;;mCAkBT,WAAW,CAAC,IAAI,CAAC,YAClB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,cACtB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,YAC1B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,cACtB,MAAM,EAAE;;;;EAQzB"}
1
+ {"version":3,"file":"use-spatial-query.d.ts","sourceRoot":"","sources":["../../../src/hooks/spatial-grid/use-spatial-query.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAGpE,wBAAgB,eAAe;+BAGhB,SAAS,CAAC,IAAI,CAAC,YACd,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,cACtB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,YAC1B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,cACtB,MAAM,EAAE;;;;8BASX,SAAS,CAAC,IAAI,CAAC,UAChB,QAAQ,CAAC,IAAI,CAAC,UACd,MAAM,UACN,MAAM,cACF,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,eACxB,MAAM,GAAG,WAAW,SACzB,OAAO,GAAG,MAAM,cACX,MAAM,EAAE;;;;;;;;;mCAkBT,WAAW,CAAC,IAAI,CAAC,YAClB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,cACtB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,YAC1B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,cACtB,MAAM,EAAE;;;;EAQzB"}
@@ -14,7 +14,7 @@ export declare class WallSpatialGrid {
14
14
  private wallItems;
15
15
  private itemToWall;
16
16
  /**
17
- * Check if an item can be placed on a wall
17
+ * Check if an item can be placed on a wall with auto-adjustment for vertical position
18
18
  * @param wallId - The wall to place on
19
19
  * @param wallLength - Length of the wall
20
20
  * @param wallHeight - Height of the wall
@@ -25,10 +25,13 @@ export declare class WallSpatialGrid {
25
25
  * @param attachType - 'wall' (blocks both sides) or 'wall-side' (blocks one side)
26
26
  * @param side - Which side for 'wall-side' items
27
27
  * @param ignoreIds - Item IDs to ignore in conflict check
28
+ * @returns Validation result with auto-adjusted Y position if needed
28
29
  */
29
30
  canPlaceOnWall(wallId: string, wallLength: number, wallHeight: number, tCenter: number, itemWidth: number, yBottom: number, itemHeight: number, attachType?: AttachType, side?: WallSide, ignoreIds?: string[]): {
30
31
  valid: boolean;
31
32
  conflictIds: string[];
33
+ adjustedY: number;
34
+ wasAdjusted: boolean;
32
35
  };
33
36
  /**
34
37
  * Check if two items conflict based on their attach types and sides
@@ -1 +1 @@
1
- {"version":3,"file":"wall-spatial-grid.d.ts","sourceRoot":"","sources":["../../../src/hooks/spatial-grid/wall-spatial-grid.ts"],"names":[],"mappings":"AAAA,KAAK,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAA;AAChC,KAAK,UAAU,GAAG,MAAM,GAAG,WAAW,CAAA;AAKtC,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,IAAI,CAAC,EAAE,QAAQ,CAAA;CAChB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,UAAU,CAA4B;IAE9C;;;;;;;;;;;;OAYG;IACH,cAAc,CACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,GAAE,UAAmB,EAC/B,IAAI,CAAC,EAAE,QAAQ,EACf,SAAS,GAAE,MAAM,EAAO,GACvB;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,EAAE,CAAA;KAAE;IAoC5C;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IA0BzB,MAAM,CAAC,SAAS,EAAE,iBAAiB;IAUnC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IASrC,cAAc,CAAC,MAAM,EAAE,MAAM;IAQ7B,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;IAapC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIlD,KAAK;CAIN"}
1
+ {"version":3,"file":"wall-spatial-grid.d.ts","sourceRoot":"","sources":["../../../src/hooks/spatial-grid/wall-spatial-grid.ts"],"names":[],"mappings":"AAAA,KAAK,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAA;AAChC,KAAK,UAAU,GAAG,MAAM,GAAG,WAAW,CAAA;AAQtC,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,IAAI,CAAC,EAAE,QAAQ,CAAA;CAChB;AAgCD,qBAAa,eAAe;IAC1B,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,UAAU,CAA4B;IAE9C;;;;;;;;;;;;;OAaG;IACH,cAAc,CACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,GAAE,UAAmB,EAC/B,IAAI,CAAC,EAAE,QAAQ,EACf,SAAS,GAAE,MAAM,EAAO,GACvB;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE;IAsCrF;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IA0BzB,MAAM,CAAC,SAAS,EAAE,iBAAiB;IAUnC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IASrC,cAAc,CAAC,MAAM,EAAE,MAAM;IAQ7B,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;IAapC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIlD,KAAK;CAIN"}
@@ -1,10 +1,33 @@
1
1
  // Small tolerance for floating point comparison to allow adjacent items
2
2
  const EPSILON = 0.001;
3
+ // Margin from ceiling/floor when auto-snapping items
4
+ const AUTO_SNAP_MARGIN = 0.05;
5
+ /**
6
+ * Auto-adjust Y position to fit item within wall bounds
7
+ * Returns the adjusted Y position (bottom of item)
8
+ */
9
+ function autoAdjustYPosition(yBottom, itemHeight, wallHeight) {
10
+ const yTop = yBottom + itemHeight;
11
+ // If fits perfectly, no adjustment needed
12
+ if (yBottom >= 0 && yTop <= wallHeight) {
13
+ return { adjustedY: yBottom, wasAdjusted: false };
14
+ }
15
+ // If too high (top exceeds wall height), snap down from ceiling
16
+ if (yTop > wallHeight) {
17
+ const adjustedY = wallHeight - itemHeight - AUTO_SNAP_MARGIN;
18
+ return { adjustedY: Math.max(0, adjustedY), wasAdjusted: true };
19
+ }
20
+ // If too low (bottom below floor), snap up from floor
21
+ if (yBottom < 0) {
22
+ return { adjustedY: AUTO_SNAP_MARGIN, wasAdjusted: true };
23
+ }
24
+ return { adjustedY: yBottom, wasAdjusted: false };
25
+ }
3
26
  export class WallSpatialGrid {
4
27
  wallItems = new Map(); // wallId -> placements
5
28
  itemToWall = new Map(); // itemId -> wallId (reverse lookup)
6
29
  /**
7
- * Check if an item can be placed on a wall
30
+ * Check if an item can be placed on a wall with auto-adjustment for vertical position
8
31
  * @param wallId - The wall to place on
9
32
  * @param wallLength - Length of the wall
10
33
  * @param wallHeight - Height of the wall
@@ -15,18 +38,20 @@ export class WallSpatialGrid {
15
38
  * @param attachType - 'wall' (blocks both sides) or 'wall-side' (blocks one side)
16
39
  * @param side - Which side for 'wall-side' items
17
40
  * @param ignoreIds - Item IDs to ignore in conflict check
41
+ * @returns Validation result with auto-adjusted Y position if needed
18
42
  */
19
43
  canPlaceOnWall(wallId, wallLength, wallHeight, tCenter, itemWidth, yBottom, itemHeight, attachType = 'wall', side, ignoreIds = []) {
20
44
  const halfW = itemWidth / wallLength / 2;
21
45
  const tStart = tCenter - halfW;
22
46
  const tEnd = tCenter + halfW;
23
- // yBottom is the bottom of the item, so yEnd = yBottom + itemHeight
24
- const yStart = yBottom;
25
- const yEnd = yBottom + itemHeight;
26
- // Check wall boundaries
27
- if (tStart < 0 || tEnd > 1 || yStart < 0 || yEnd > wallHeight) {
28
- return { valid: false, conflictIds: [] };
47
+ // Check horizontal boundaries (still reject if item exceeds wall width)
48
+ if (tStart < 0 || tEnd > 1) {
49
+ return { valid: false, conflictIds: [], adjustedY: yBottom, wasAdjusted: false };
29
50
  }
51
+ // Auto-adjust vertical position to fit within wall bounds
52
+ const { adjustedY, wasAdjusted } = autoAdjustYPosition(yBottom, itemHeight, wallHeight);
53
+ const yStart = adjustedY;
54
+ const yEnd = adjustedY + itemHeight;
30
55
  const existing = this.wallItems.get(wallId) ?? [];
31
56
  const ignoreSet = new Set(ignoreIds);
32
57
  const conflicts = [];
@@ -44,7 +69,7 @@ export class WallSpatialGrid {
44
69
  }
45
70
  }
46
71
  }
47
- return { valid: conflicts.length === 0, conflictIds: conflicts };
72
+ return { valid: conflicts.length === 0, conflictIds: conflicts, adjustedY, wasAdjusted };
48
73
  }
49
74
  /**
50
75
  * Check if two items conflict based on their attach types and sides
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
- export type { BuildingEvent, CameraControlEvent, EventSuffix, GridEvent, ItemEvent, LevelEvent, NodeEvent, SiteEvent, SlabEvent, WallEvent, ZoneEvent, CeilingEvent, RoofEvent, } from './events/bus';
1
+ export type { BuildingEvent, CameraControlEvent, CeilingEvent, EventSuffix, GridEvent, ItemEvent, LevelEvent, NodeEvent, RoofEvent, SiteEvent, SlabEvent, WallEvent, WindowEvent, ZoneEvent, } from './events/bus';
2
2
  export { emitter, eventSuffixes } from './events/bus';
3
3
  export { sceneRegistry, useRegistry, } from './hooks/scene-registry/scene-registry';
4
+ export { pointInPolygon, spatialGridManager } from './hooks/spatial-grid/spatial-grid-manager';
4
5
  export { initSpatialGridSync, resolveLevelId, } from './hooks/spatial-grid/spatial-grid-sync';
5
6
  export { useSpatialQuery } from './hooks/spatial-grid/use-spatial-query';
6
- export { pointInPolygon } from './hooks/spatial-grid/spatial-grid-manager';
7
+ export { loadAssetUrl, saveAsset } from './lib/asset-storage';
8
+ export { detectSpacesForLevel, initSpaceDetectionSync, type Space, wallTouchesOthers, } from './lib/space-detection';
7
9
  export * from './schema';
8
10
  export { default as useScene } from './store/use-scene';
9
11
  export { CeilingSystem } from './systems/ceiling/ceiling-system';
@@ -11,7 +13,6 @@ export { ItemSystem } from './systems/item/item-system';
11
13
  export { RoofSystem } from './systems/roof/roof-system';
12
14
  export { SlabSystem } from './systems/slab/slab-system';
13
15
  export { WallSystem } from './systems/wall/wall-system';
16
+ export { WindowSystem } from './systems/window/window-system';
14
17
  export { isObject } from './utils/types';
15
- export { saveAsset, loadAssetUrl } from './lib/asset-storage';
16
- export { detectSpacesForLevel, wallTouchesOthers, initSpaceDetectionSync, type Space } from './lib/space-detection';
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,YAAY,EACZ,SAAS,GACV,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAErD,OAAO,EACL,aAAa,EACb,WAAW,GACZ,MAAM,uCAAuC,CAAA;AAC9C,OAAO,EACL,mBAAmB,EACnB,cAAc,GACf,MAAM,wCAAwC,CAAA;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAA;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAA;AAE1E,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAEvD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAE7D,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,KAAK,KAAK,EAAE,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,WAAW,EACX,SAAS,GACV,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAErD,OAAO,EACL,aAAa,EACb,WAAW,GACZ,MAAM,uCAAuC,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAA;AAC9F,OAAO,EACL,mBAAmB,EACnB,cAAc,GACf,MAAM,wCAAwC,CAAA;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAA;AAExE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAE7D,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,KAAK,KAAK,EACV,iBAAiB,GAClB,MAAM,uBAAuB,CAAA;AAE9B,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA"}
package/dist/index.js CHANGED
@@ -3,9 +3,13 @@
3
3
  export { emitter, eventSuffixes } from './events/bus';
4
4
  // Hooks
5
5
  export { sceneRegistry, useRegistry, } from './hooks/scene-registry/scene-registry';
6
+ export { pointInPolygon, spatialGridManager } from './hooks/spatial-grid/spatial-grid-manager';
6
7
  export { initSpatialGridSync, resolveLevelId, } from './hooks/spatial-grid/spatial-grid-sync';
7
8
  export { useSpatialQuery } from './hooks/spatial-grid/use-spatial-query';
8
- export { pointInPolygon } from './hooks/spatial-grid/spatial-grid-manager';
9
+ // Asset storage
10
+ export { loadAssetUrl, saveAsset } from './lib/asset-storage';
11
+ // Space detection
12
+ export { detectSpacesForLevel, initSpaceDetectionSync, wallTouchesOthers, } from './lib/space-detection';
9
13
  // Schema
10
14
  export * from './schema';
11
15
  export { default as useScene } from './store/use-scene';
@@ -15,8 +19,5 @@ export { ItemSystem } from './systems/item/item-system';
15
19
  export { RoofSystem } from './systems/roof/roof-system';
16
20
  export { SlabSystem } from './systems/slab/slab-system';
17
21
  export { WallSystem } from './systems/wall/wall-system';
22
+ export { WindowSystem } from './systems/window/window-system';
18
23
  export { isObject } from './utils/types';
19
- // Asset storage
20
- export { saveAsset, loadAssetUrl } from './lib/asset-storage';
21
- // Space detection
22
- export { detectSpacesForLevel, wallTouchesOthers, initSpaceDetectionSync } from './lib/space-detection';
@@ -13,5 +13,6 @@ export { RoofNode } from './nodes/roof';
13
13
  export { ScanNode } from './nodes/scan';
14
14
  export { GuideNode } from './nodes/guide';
15
15
  export type { AnyNodeId, AnyNodeType } from './types';
16
+ export { WindowNode } from './nodes/window';
16
17
  export { AnyNode } from './types';
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAE3E,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAEzC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAE7C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAErD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAE3E,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAEzC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAE7C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE3C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA"}
@@ -14,5 +14,6 @@ export { ZoneNode } from './nodes/zone';
14
14
  export { RoofNode } from './nodes/roof';
15
15
  export { ScanNode } from './nodes/scan';
16
16
  export { GuideNode } from './nodes/guide';
17
+ export { WindowNode } from './nodes/window';
17
18
  // Union types
18
19
  export { AnyNode } from './types';
@@ -19,6 +19,7 @@ export declare const CeilingNode: z.ZodObject<{
19
19
  type: z.ZodDefault<z.ZodLiteral<"ceiling">>;
20
20
  children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`item_${string}`>>>>;
21
21
  polygon: z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
22
+ holes: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>>;
22
23
  height: z.ZodDefault<z.ZodNumber>;
23
24
  }, z.core.$strip>;
24
25
  export type CeilingNode = z.infer<typeof CeilingNode>;
@@ -1 +1 @@
1
- {"version":3,"file":"ceiling.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/ceiling.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;iBAavB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAA"}
1
+ {"version":3,"file":"ceiling.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/ceiling.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;iBAevB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAA"}
@@ -9,8 +9,10 @@ export const CeilingNode = BaseNode.extend({
9
9
  // Specific props
10
10
  // Polygon boundary - array of [x, z] coordinates defining the ceiling
11
11
  polygon: z.array(z.tuple([z.number(), z.number()])),
12
+ holes: z.array(z.array(z.tuple([z.number(), z.number()]))).default([]),
12
13
  height: z.number().default(2.5), // Height in meters
13
14
  }).describe(dedent `
14
15
  Ceiling node - used to represent a ceiling in the building
15
16
  - polygon: array of [x, z] points defining the ceiling boundary
17
+ - holes: array of polygons representing holes in the ceiling
16
18
  `);
@@ -11,9 +11,13 @@ declare const assetSchema: z.ZodObject<{
11
11
  "wall-side": "wall-side";
12
12
  ceiling: "ceiling";
13
13
  }>>;
14
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
14
15
  offset: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
15
16
  rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
16
17
  scale: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
18
+ surface: z.ZodOptional<z.ZodObject<{
19
+ height: z.ZodNumber;
20
+ }, z.core.$strip>>;
17
21
  }, z.core.$strip>;
18
22
  export type AssetInput = z.input<typeof assetSchema>;
19
23
  export type Asset = z.infer<typeof assetSchema>;
@@ -41,6 +45,7 @@ export declare const ItemNode: z.ZodObject<{
41
45
  front: "front";
42
46
  back: "back";
43
47
  }>>;
48
+ children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`item_${string}`>>>>;
44
49
  wallId: z.ZodOptional<z.ZodString>;
45
50
  wallT: z.ZodOptional<z.ZodNumber>;
46
51
  asset: z.ZodObject<{
@@ -55,9 +60,13 @@ export declare const ItemNode: z.ZodObject<{
55
60
  "wall-side": "wall-side";
56
61
  ceiling: "ceiling";
57
62
  }>>;
63
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
58
64
  offset: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
59
65
  rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
60
66
  scale: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
67
+ surface: z.ZodOptional<z.ZodObject<{
68
+ height: z.ZodNumber;
69
+ }, z.core.$strip>>;
61
70
  }, z.core.$strip>;
62
71
  }, z.core.$strip>;
63
72
  export type ItemNode = z.infer<typeof ItemNode>;
@@ -1 +1 @@
1
- {"version":3,"file":"item.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/item.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,QAAA,MAAM,WAAW;;;;;;;;;;;;;;;iBAYf,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAA;AACpD,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAA;AAE/C,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuBnB,CAAA;AAEF,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA"}
1
+ {"version":3,"file":"item.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/item.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,QAAA,MAAM,WAAW;;;;;;;;;;;;;;;;;;;iBAkBf,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAA;AACpD,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAA;AAE/C,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyBnB,CAAA;AAEF,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA"}
@@ -9,10 +9,16 @@ const assetSchema = z.object({
9
9
  src: z.string(),
10
10
  dimensions: z.tuple([z.number(), z.number(), z.number()]).default([1, 1, 1]), // [w, h, d]
11
11
  attachTo: z.enum(['wall', 'wall-side', 'ceiling']).optional(),
12
+ tags: z.array(z.string()).optional(),
12
13
  // These are "Corrective" transforms to normalize the GLB
13
14
  offset: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
14
15
  rotation: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
15
16
  scale: z.tuple([z.number(), z.number(), z.number()]).default([1, 1, 1]),
17
+ surface: z
18
+ .object({
19
+ height: z.number(), // where things rest
20
+ })
21
+ .optional(), // undefined = can't place things on it
16
22
  });
17
23
  export const ItemNode = BaseNode.extend({
18
24
  id: objectId('item'),
@@ -20,6 +26,7 @@ export const ItemNode = BaseNode.extend({
20
26
  position: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
21
27
  rotation: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
22
28
  side: z.enum(['front', 'back']).optional(),
29
+ children: z.array(objectId('item')).default([]),
23
30
  // Wall attachment properties (only used when asset.attachTo is "wall" or "wall-side")
24
31
  wallId: z.string().optional(),
25
32
  wallT: z.number().optional(), // 0-1 parametric position along wall
@@ -35,4 +42,5 @@ export const ItemNode = BaseNode.extend({
35
42
  - offset: corrective position offset for the model
36
43
  - rotation: corrective rotation for the model
37
44
  - scale: corrective scale for the model
45
+ - tags: tags associated with the item
38
46
  `);
@@ -66,6 +66,7 @@ export declare const SiteNode: z.ZodObject<{
66
66
  front: "front";
67
67
  back: "back";
68
68
  }>>;
69
+ children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`item_${string}`>>>>;
69
70
  wallId: z.ZodOptional<z.ZodString>;
70
71
  wallT: z.ZodOptional<z.ZodNumber>;
71
72
  asset: z.ZodObject<{
@@ -80,9 +81,13 @@ export declare const SiteNode: z.ZodObject<{
80
81
  "wall-side": "wall-side";
81
82
  ceiling: "ceiling";
82
83
  }>>;
84
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
83
85
  offset: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
84
86
  rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
85
87
  scale: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
88
+ surface: z.ZodOptional<z.ZodObject<{
89
+ height: z.ZodNumber;
90
+ }, z.core.$strip>>;
86
91
  }, z.core.$strip>;
87
92
  }, z.core.$strip>], "type">>>;
88
93
  }, z.core.$strip>;
@@ -1 +1 @@
1
- {"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/site.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiBvB,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwBpB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA"}
1
+ {"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/site.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiBvB,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwBpB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA"}
@@ -18,6 +18,7 @@ export declare const SlabNode: z.ZodObject<{
18
18
  id: z.ZodDefault<z.ZodTemplateLiteral<`slab_${string}`>>;
19
19
  type: z.ZodDefault<z.ZodLiteral<"slab">>;
20
20
  polygon: z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
21
+ holes: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>>;
21
22
  elevation: z.ZodDefault<z.ZodNumber>;
22
23
  }, z.core.$strip>;
23
24
  export type SlabNode = z.infer<typeof SlabNode>;
@@ -1 +1 @@
1
- {"version":3,"file":"slab.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/slab.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;iBAapB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA"}
1
+ {"version":3,"file":"slab.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/slab.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;iBAcpB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA"}
@@ -7,6 +7,7 @@ export const SlabNode = BaseNode.extend({
7
7
  // Specific props
8
8
  // Polygon boundary - array of [x, z] coordinates defining the slab
9
9
  polygon: z.array(z.tuple([z.number(), z.number()])),
10
+ holes: z.array(z.array(z.tuple([z.number(), z.number()]))).default([]),
10
11
  elevation: z.number().default(0.05), // Elevation in meters
11
12
  }).describe(dedent `
12
13
  Slab node - used to represent a slab/floor in the building
@@ -0,0 +1,40 @@
1
+ import { z } from 'zod';
2
+ export declare const WindowNode: z.ZodObject<{
3
+ object: z.ZodDefault<z.ZodLiteral<"node">>;
4
+ name: z.ZodOptional<z.ZodString>;
5
+ parentId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
6
+ visible: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
7
+ camera: z.ZodOptional<z.ZodObject<{
8
+ position: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
9
+ target: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
10
+ mode: z.ZodDefault<z.ZodEnum<{
11
+ perspective: "perspective";
12
+ orthographic: "orthographic";
13
+ }>>;
14
+ fov: z.ZodOptional<z.ZodNumber>;
15
+ zoom: z.ZodOptional<z.ZodNumber>;
16
+ }, z.core.$strip>>;
17
+ metadata: z.ZodDefault<z.ZodOptional<z.ZodJSONSchema>>;
18
+ id: z.ZodDefault<z.ZodTemplateLiteral<`window_${string}`>>;
19
+ type: z.ZodDefault<z.ZodLiteral<"window">>;
20
+ position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
21
+ rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
22
+ side: z.ZodOptional<z.ZodEnum<{
23
+ front: "front";
24
+ back: "back";
25
+ }>>;
26
+ wallId: z.ZodOptional<z.ZodString>;
27
+ width: z.ZodDefault<z.ZodNumber>;
28
+ height: z.ZodDefault<z.ZodNumber>;
29
+ frameThickness: z.ZodDefault<z.ZodNumber>;
30
+ frameDepth: z.ZodDefault<z.ZodNumber>;
31
+ columnRatios: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
32
+ rowRatios: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
33
+ columnDividerThickness: z.ZodDefault<z.ZodNumber>;
34
+ rowDividerThickness: z.ZodDefault<z.ZodNumber>;
35
+ sill: z.ZodDefault<z.ZodBoolean>;
36
+ sillDepth: z.ZodDefault<z.ZodNumber>;
37
+ sillThickness: z.ZodDefault<z.ZodNumber>;
38
+ }, z.core.$strip>;
39
+ export type WindowNode = z.infer<typeof WindowNode>;
40
+ //# sourceMappingURL=window.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"window.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/window.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwCrB,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAA"}
@@ -0,0 +1,38 @@
1
+ import dedent from 'dedent';
2
+ import { z } from 'zod';
3
+ import { BaseNode, nodeType, objectId } from '../base';
4
+ export const WindowNode = BaseNode.extend({
5
+ id: objectId('window'),
6
+ type: nodeType('window'),
7
+ // Position in wall-local coordinate system (center of window)
8
+ position: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
9
+ rotation: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
10
+ side: z.enum(['front', 'back']).optional(),
11
+ // Wall reference
12
+ wallId: z.string().optional(),
13
+ // Overall dimensions
14
+ width: z.number().default(1.5),
15
+ height: z.number().default(1.5),
16
+ // Frame
17
+ frameThickness: z.number().default(0.05),
18
+ frameDepth: z.number().default(0.07),
19
+ // Divisions — ratios allow non-uniform panes
20
+ // [0.5, 0.5] = two equal panes
21
+ // [0.6, 0.4] = one larger, one smaller
22
+ // [1] = single pane (no division)
23
+ columnRatios: z.array(z.number()).default([1]),
24
+ rowRatios: z.array(z.number()).default([1]),
25
+ columnDividerThickness: z.number().default(0.03),
26
+ rowDividerThickness: z.number().default(0.03),
27
+ // Sill
28
+ sill: z.boolean().default(true),
29
+ sillDepth: z.number().default(0.08),
30
+ sillThickness: z.number().default(0.03),
31
+ }).describe(dedent `Window node - a parametric window placed on a wall
32
+ - position: center of the window in wall-local coordinate system
33
+ - width/height: overall outer dimensions
34
+ - frameThickness: width of the frame members
35
+ - frameDepth: how deep the frame sits within the wall
36
+ - columnRatios/rowRatios: pane division ratios
37
+ - sill: whether to show a window sill
38
+ `);
@@ -66,6 +66,7 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
66
66
  front: "front";
67
67
  back: "back";
68
68
  }>>;
69
+ children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`item_${string}`>>>>;
69
70
  wallId: z.ZodOptional<z.ZodString>;
70
71
  wallT: z.ZodOptional<z.ZodNumber>;
71
72
  asset: z.ZodObject<{
@@ -80,9 +81,13 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
80
81
  "wall-side": "wall-side";
81
82
  ceiling: "ceiling";
82
83
  }>>;
84
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
83
85
  offset: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
84
86
  rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
85
87
  scale: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
88
+ surface: z.ZodOptional<z.ZodObject<{
89
+ height: z.ZodNumber;
90
+ }, z.core.$strip>>;
86
91
  }, z.core.$strip>;
87
92
  }, z.core.$strip>], "type">>>;
88
93
  }, z.core.$strip>, z.ZodObject<{
@@ -183,6 +188,7 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
183
188
  front: "front";
184
189
  back: "back";
185
190
  }>>;
191
+ children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`item_${string}`>>>>;
186
192
  wallId: z.ZodOptional<z.ZodString>;
187
193
  wallT: z.ZodOptional<z.ZodNumber>;
188
194
  asset: z.ZodObject<{
@@ -197,9 +203,13 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
197
203
  "wall-side": "wall-side";
198
204
  ceiling: "ceiling";
199
205
  }>>;
206
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
200
207
  offset: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
201
208
  rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
202
209
  scale: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
210
+ surface: z.ZodOptional<z.ZodObject<{
211
+ height: z.ZodNumber;
212
+ }, z.core.$strip>>;
203
213
  }, z.core.$strip>;
204
214
  }, z.core.$strip>, z.ZodObject<{
205
215
  object: z.ZodDefault<z.ZodLiteral<"node">>;
@@ -240,6 +250,7 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
240
250
  id: z.ZodDefault<z.ZodTemplateLiteral<`slab_${string}`>>;
241
251
  type: z.ZodDefault<z.ZodLiteral<"slab">>;
242
252
  polygon: z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
253
+ holes: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>>;
243
254
  elevation: z.ZodDefault<z.ZodNumber>;
244
255
  }, z.core.$strip>, z.ZodObject<{
245
256
  object: z.ZodDefault<z.ZodLiteral<"node">>;
@@ -261,6 +272,7 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
261
272
  type: z.ZodDefault<z.ZodLiteral<"ceiling">>;
262
273
  children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`item_${string}`>>>>;
263
274
  polygon: z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
275
+ holes: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>>;
264
276
  height: z.ZodDefault<z.ZodNumber>;
265
277
  }, z.core.$strip>, z.ZodObject<{
266
278
  object: z.ZodDefault<z.ZodLiteral<"node">>;
@@ -332,6 +344,42 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
332
344
  rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
333
345
  scale: z.ZodDefault<z.ZodNumber>;
334
346
  opacity: z.ZodDefault<z.ZodNumber>;
347
+ }, z.core.$strip>, z.ZodObject<{
348
+ object: z.ZodDefault<z.ZodLiteral<"node">>;
349
+ name: z.ZodOptional<z.ZodString>;
350
+ parentId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
351
+ visible: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
352
+ camera: z.ZodOptional<z.ZodObject<{
353
+ position: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
354
+ target: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
355
+ mode: z.ZodDefault<z.ZodEnum<{
356
+ perspective: "perspective";
357
+ orthographic: "orthographic";
358
+ }>>;
359
+ fov: z.ZodOptional<z.ZodNumber>;
360
+ zoom: z.ZodOptional<z.ZodNumber>;
361
+ }, z.core.$strip>>;
362
+ metadata: z.ZodDefault<z.ZodOptional<z.ZodJSONSchema>>;
363
+ id: z.ZodDefault<z.ZodTemplateLiteral<`window_${string}`>>;
364
+ type: z.ZodDefault<z.ZodLiteral<"window">>;
365
+ position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
366
+ rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
367
+ side: z.ZodOptional<z.ZodEnum<{
368
+ front: "front";
369
+ back: "back";
370
+ }>>;
371
+ wallId: z.ZodOptional<z.ZodString>;
372
+ width: z.ZodDefault<z.ZodNumber>;
373
+ height: z.ZodDefault<z.ZodNumber>;
374
+ frameThickness: z.ZodDefault<z.ZodNumber>;
375
+ frameDepth: z.ZodDefault<z.ZodNumber>;
376
+ columnRatios: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
377
+ rowRatios: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
378
+ columnDividerThickness: z.ZodDefault<z.ZodNumber>;
379
+ rowDividerThickness: z.ZodDefault<z.ZodNumber>;
380
+ sill: z.ZodDefault<z.ZodBoolean>;
381
+ sillDepth: z.ZodDefault<z.ZodNumber>;
382
+ sillThickness: z.ZodDefault<z.ZodNumber>;
335
383
  }, z.core.$strip>], "type">;
336
384
  export type AnyNode = z.infer<typeof AnyNode>;
337
385
  export type AnyNodeType = AnyNode['type'];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/schema/types.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,KAAK,CAAA;AAanB,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAYlB,CAAA;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,CAAA;AAC7C,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;AACzC,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/schema/types.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,KAAK,CAAA;AAcnB,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAalB,CAAA;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,CAAA;AAC7C,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;AACzC,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA"}
@@ -9,6 +9,7 @@ import { ScanNode } from './nodes/scan';
9
9
  import { SiteNode } from './nodes/site';
10
10
  import { SlabNode } from './nodes/slab';
11
11
  import { WallNode } from './nodes/wall';
12
+ import { WindowNode } from './nodes/window';
12
13
  import { ZoneNode } from './nodes/zone';
13
14
  export const AnyNode = z.discriminatedUnion('type', [
14
15
  SiteNode,
@@ -22,4 +23,5 @@ export const AnyNode = z.discriminatedUnion('type', [
22
23
  RoofNode,
23
24
  ScanNode,
24
25
  GuideNode,
26
+ WindowNode,
25
27
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAa,WAAW,EAAE,MAAM,cAAc,CAAA;AAO1D,eAAO,MAAM,aAAa,YAuBzB,CAAA;AAeD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,KAAK,CAAC,cAAc,CA6BtF"}
1
+ {"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAa,WAAW,EAAE,MAAM,cAAc,CAAA;AAO1D,eAAO,MAAM,aAAa,YAuBzB,CAAA;AAeD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,KAAK,CAAC,cAAc,CA+CtF"}
@@ -56,6 +56,21 @@ export function generateCeilingGeometry(ceilingNode) {
56
56
  shape.lineTo(pt[0], -pt[1]);
57
57
  }
58
58
  shape.closePath();
59
+ // Add holes to the shape
60
+ const holes = ceilingNode.holes || [];
61
+ for (const holePolygon of holes) {
62
+ if (holePolygon.length < 3)
63
+ continue;
64
+ const holePath = new THREE.Path();
65
+ const holeFirstPt = holePolygon[0];
66
+ holePath.moveTo(holeFirstPt[0], -holeFirstPt[1]);
67
+ for (let i = 1; i < holePolygon.length; i++) {
68
+ const pt = holePolygon[i];
69
+ holePath.lineTo(pt[0], -pt[1]);
70
+ }
71
+ holePath.closePath();
72
+ shape.holes.push(holePath);
73
+ }
59
74
  // Create flat shape geometry (no extrusion)
60
75
  const geometry = new THREE.ShapeGeometry(shape);
61
76
  // Rotate so the shape lies flat in X-Z plane
@@ -1 +1 @@
1
- {"version":3,"file":"item-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item/item-system.tsx"],"names":[],"mappings":"AAYA,eAAO,MAAM,UAAU,YAyCtB,CAAA"}
1
+ {"version":3,"file":"item-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item/item-system.tsx"],"names":[],"mappings":"AAYA,eAAO,MAAM,UAAU,YA6CtB,CAAA"}
@@ -31,10 +31,14 @@ export const ItemSystem = () => {
31
31
  }
32
32
  }
33
33
  else if (!item.asset.attachTo) {
34
- // Floor item: elevate by slab height (using full footprint overlap)
35
- const levelId = resolveLevelId(item, nodes);
36
- const slabElevation = spatialGridManager.getSlabElevationForItem(levelId, item.position, item.asset.dimensions, item.rotation);
37
- mesh.position.y = slabElevation;
34
+ // If parented to another item (surface placement), R3F handles positioning via the hierarchy
35
+ const parentNode = item.parentId ? nodes[item.parentId] : undefined;
36
+ if (parentNode?.type !== 'item') {
37
+ // Floor item: elevate by slab height (using full footprint overlap)
38
+ const levelId = resolveLevelId(item, nodes);
39
+ const slabElevation = spatialGridManager.getSlabElevationForItem(levelId, item.position, item.asset.dimensions, item.rotation);
40
+ mesh.position.y = slabElevation + item.position[1];
41
+ }
38
42
  }
39
43
  clearDirty(id);
40
44
  });
@@ -1 +1 @@
1
- {"version":3,"file":"slab-system.d.ts","sourceRoot":"","sources":["../../../src/systems/slab/slab-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,cAAc,CAAA;AAOvD,eAAO,MAAM,UAAU,YAwBtB,CAAA;AAkED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,KAAK,CAAC,cAAc,CAiC7E"}
1
+ {"version":3,"file":"slab-system.d.ts","sourceRoot":"","sources":["../../../src/systems/slab/slab-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,cAAc,CAAA;AAOvD,eAAO,MAAM,UAAU,YAwBtB,CAAA;AAkED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,KAAK,CAAC,cAAc,CAmD7E"}
@@ -105,6 +105,21 @@ export function generateSlabGeometry(slabNode) {
105
105
  shape.lineTo(pt[0], -pt[1]);
106
106
  }
107
107
  shape.closePath();
108
+ // Add holes to the shape
109
+ const holes = slabNode.holes || [];
110
+ for (const holePolygon of holes) {
111
+ if (holePolygon.length < 3)
112
+ continue;
113
+ const holePath = new THREE.Path();
114
+ const holeFirstPt = holePolygon[0];
115
+ holePath.moveTo(holeFirstPt[0], -holeFirstPt[1]);
116
+ for (let i = 1; i < holePolygon.length; i++) {
117
+ const pt = holePolygon[i];
118
+ holePath.lineTo(pt[0], -pt[1]);
119
+ }
120
+ holePath.closePath();
121
+ shape.holes.push(holePath);
122
+ }
108
123
  // Extrude the shape by elevation
109
124
  const geometry = new THREE.ExtrudeGeometry(shape, {
110
125
  depth: elevation,
@@ -1 +1 @@
1
- {"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,KAAK,EAAE,OAAO,EAAa,QAAQ,EAAE,MAAM,cAAc,CAAA;AAEhE,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAA;AASxB,eAAO,MAAM,UAAU,YAwDtB,CAAA;AA0DD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,OAAO,EAAE,EACxB,SAAS,EAAE,aAAa,EACxB,aAAa,SAAI,oFA0IlB"}
1
+ {"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,OAAO,KAAK,EAAE,OAAO,EAAa,QAAQ,EAAE,MAAM,cAAc,CAAA;AAEhE,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAA;AAUxB,eAAO,MAAM,UAAU,YAuDtB,CAAA;AA2DD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,OAAO,EAAE,EACxB,SAAS,EAAE,aAAa,EACxB,aAAa,SAAI,oFA4IlB"}
@@ -1,5 +1,6 @@
1
1
  import { useFrame } from '@react-three/fiber';
2
2
  import * as THREE from 'three';
3
+ import { computeBoundsTree } from 'three-mesh-bvh';
3
4
  import { Brush, Evaluator, SUBTRACTION } from 'three-bvh-csg';
4
5
  import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
5
6
  import { spatialGridManager } from '../../hooks/spatial-grid/spatial-grid-manager';
@@ -11,6 +12,7 @@ const csgEvaluator = new Evaluator();
11
12
  // ============================================================================
12
13
  // WALL SYSTEM
13
14
  // ============================================================================
15
+ let useFrameNb = 0;
14
16
  export const WallSystem = () => {
15
17
  const dirtyNodes = useScene((state) => state.dirtyNodes);
16
18
  const clearDirty = useScene((state) => state.clearDirty);
@@ -20,11 +22,11 @@ export const WallSystem = () => {
20
22
  const nodes = useScene.getState().nodes;
21
23
  // Collect dirty walls and their levels
22
24
  const dirtyWallsByLevel = new Map();
25
+ useFrameNb += 1;
23
26
  dirtyNodes.forEach((id) => {
24
27
  const node = nodes[id];
25
28
  if (!node || node.type !== 'wall')
26
29
  return;
27
- console.log('wall front/back', node.frontSide, node.backSide);
28
30
  const levelId = node.parentId;
29
31
  if (!levelId)
30
32
  return;
@@ -104,7 +106,7 @@ function updateWallGeometry(wallId, miterData) {
104
106
  collisionMesh.geometry.dispose();
105
107
  collisionMesh.geometry = collisionGeo;
106
108
  }
107
- mesh.position.set(node.start[0], 0, node.start[1]);
109
+ mesh.position.set(node.start[0], slabElevation, node.start[1]);
108
110
  const angle = Math.atan2(node.end[1] - node.start[1], node.end[0] - node.start[0]);
109
111
  mesh.rotation.y = -angle;
110
112
  }
@@ -118,8 +120,10 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
118
120
  const { junctionData } = miterData;
119
121
  const wallStart = { x: wallNode.start[0], y: wallNode.start[1] };
120
122
  const wallEnd = { x: wallNode.end[0], y: wallNode.end[1] };
121
- // Wall height is adjusted by slab elevation (positive reduces, negative increases)
122
- const height = (wallNode.height ?? 2.5) - slabElevation;
123
+ // Positive slab: shift the whole wall up (full height preserved)
124
+ // Negative slab: extend wall downward so top stays fixed at wallNode.height
125
+ const wallHeight = wallNode.height ?? 2.5;
126
+ const height = slabElevation > 0 ? wallHeight : wallHeight - slabElevation;
123
127
  const thickness = wallNode.thickness ?? 0.1;
124
128
  const halfT = thickness / 2;
125
129
  // Wall direction and normal (exactly like demo)
@@ -199,10 +203,6 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
199
203
  });
200
204
  // Rotate so extrusion direction (Z) becomes height direction (Y)
201
205
  geometry.rotateX(-Math.PI / 2);
202
- // Translate by slab elevation (works for both positive and negative values)
203
- if (slabElevation !== 0) {
204
- geometry.translate(0, slabElevation, 0);
205
- }
206
206
  geometry.computeVertexNormals();
207
207
  // Apply CSG subtraction for cutouts (doors/windows)
208
208
  const cutoutBrushes = collectCutoutBrushes(wallNode, childrenNodes, thickness);
@@ -210,6 +210,9 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
210
210
  return geometry;
211
211
  }
212
212
  // Create wall brush from geometry
213
+ // Pre-compute BVH with new API to avoid deprecation warning
214
+ geometry.computeBoundsTree = computeBoundsTree;
215
+ geometry.computeBoundsTree({ maxLeafSize: 10 });
213
216
  const wallBrush = new Brush(geometry);
214
217
  wallBrush.updateMatrixWorld();
215
218
  // Subtract each cutout from the wall
@@ -244,7 +247,7 @@ function collectCutoutBrushes(wallNode, childrenNodes, wallThickness) {
244
247
  wallMesh.updateMatrixWorld();
245
248
  const wallMatrixInverse = wallMesh.matrixWorld.clone().invert();
246
249
  for (const child of childrenNodes) {
247
- if (child.type !== 'item')
250
+ if (child.type !== 'item' && child.type !== 'window')
248
251
  continue;
249
252
  const childMesh = sceneRegistry.nodes.get(child.id);
250
253
  if (!childMesh)
@@ -279,6 +282,9 @@ function collectCutoutBrushes(wallNode, childrenNodes, wallThickness) {
279
282
  const boxGeo = new THREE.BoxGeometry(width, height, depth);
280
283
  // Position box at the center of the cutout
281
284
  boxGeo.translate(minX + width / 2, minY + height / 2, 0);
285
+ // Pre-compute BVH with new API to avoid deprecation warning
286
+ boxGeo.computeBoundsTree = computeBoundsTree;
287
+ boxGeo.computeBoundsTree({ maxLeafSize: 10 });
282
288
  const brush = new Brush(boxGeo);
283
289
  brushes.push(brush);
284
290
  }
@@ -0,0 +1,2 @@
1
+ export declare const WindowSystem: () => null;
2
+ //# sourceMappingURL=window-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"window-system.d.ts","sourceRoot":"","sources":["../../../src/systems/window/window-system.tsx"],"names":[],"mappings":"AA4BA,eAAO,MAAM,YAAY,YA2BxB,CAAA"}
@@ -0,0 +1,147 @@
1
+ import { useFrame } from '@react-three/fiber';
2
+ import * as THREE from 'three';
3
+ import { DoubleSide, MeshStandardNodeMaterial } from 'three/webgpu';
4
+ import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
5
+ import useScene from '../../store/use-scene';
6
+ const glassMaterial = new MeshStandardNodeMaterial({
7
+ name: 'glass',
8
+ color: 'lightblue',
9
+ roughness: 0.05,
10
+ metalness: 0.1,
11
+ transparent: true,
12
+ opacity: 0.3,
13
+ side: DoubleSide,
14
+ depthWrite: false,
15
+ });
16
+ const frameMaterial = new MeshStandardNodeMaterial({
17
+ name: 'window-frame',
18
+ color: '#e8e8e8',
19
+ roughness: 0.6,
20
+ metalness: 0,
21
+ });
22
+ // Invisible material for root mesh — used as selection hitbox only
23
+ const hitboxMaterial = new THREE.MeshBasicMaterial({ visible: false });
24
+ export const WindowSystem = () => {
25
+ const dirtyNodes = useScene((state) => state.dirtyNodes);
26
+ const clearDirty = useScene((state) => state.clearDirty);
27
+ useFrame(() => {
28
+ if (dirtyNodes.size === 0)
29
+ return;
30
+ const nodes = useScene.getState().nodes;
31
+ dirtyNodes.forEach((id) => {
32
+ const node = nodes[id];
33
+ if (!node || node.type !== 'window')
34
+ return;
35
+ const mesh = sceneRegistry.nodes.get(id);
36
+ if (!mesh)
37
+ return; // Keep dirty until mesh mounts
38
+ updateWindowMesh(node, mesh);
39
+ clearDirty(id);
40
+ // Rebuild the parent wall so its cutout reflects the updated window geometry
41
+ if (node.parentId) {
42
+ useScene.getState().dirtyNodes.add(node.parentId);
43
+ }
44
+ });
45
+ });
46
+ return null;
47
+ };
48
+ function addBox(parent, material, w, h, d, x, y, z) {
49
+ const m = new THREE.Mesh(new THREE.BoxGeometry(w, h, d), material);
50
+ m.position.set(x, y, z);
51
+ parent.add(m);
52
+ }
53
+ function updateWindowMesh(node, mesh) {
54
+ // Root mesh is an invisible hitbox; all visuals live in child meshes
55
+ mesh.geometry.dispose();
56
+ mesh.geometry = new THREE.BoxGeometry(node.width, node.height, node.frameDepth);
57
+ mesh.material = hitboxMaterial;
58
+ // Sync transform from node (React may lag behind the system by a frame during drag)
59
+ mesh.position.set(node.position[0], node.position[1], node.position[2]);
60
+ mesh.rotation.set(node.rotation[0], node.rotation[1], node.rotation[2]);
61
+ // Dispose and remove all old visual children; preserve 'cutout'
62
+ for (const child of [...mesh.children]) {
63
+ if (child.name === 'cutout')
64
+ continue;
65
+ if (child instanceof THREE.Mesh)
66
+ child.geometry.dispose();
67
+ mesh.remove(child);
68
+ }
69
+ const { width, height, frameDepth, frameThickness, columnRatios, rowRatios, columnDividerThickness, rowDividerThickness, sill, sillDepth, sillThickness, } = node;
70
+ const innerW = width - 2 * frameThickness;
71
+ const innerH = height - 2 * frameThickness;
72
+ // ── Frame members ──
73
+ // Top / bottom — full width
74
+ addBox(mesh, frameMaterial, width, frameThickness, frameDepth, 0, height / 2 - frameThickness / 2, 0);
75
+ addBox(mesh, frameMaterial, width, frameThickness, frameDepth, 0, -height / 2 + frameThickness / 2, 0);
76
+ // Left / right — inner height to avoid corner overlap
77
+ addBox(mesh, frameMaterial, frameThickness, innerH, frameDepth, -width / 2 + frameThickness / 2, 0, 0);
78
+ addBox(mesh, frameMaterial, frameThickness, innerH, frameDepth, width / 2 - frameThickness / 2, 0, 0);
79
+ // ── Pane grid ──
80
+ const numCols = columnRatios.length;
81
+ const numRows = rowRatios.length;
82
+ const usableW = innerW - (numCols - 1) * columnDividerThickness;
83
+ const usableH = innerH - (numRows - 1) * rowDividerThickness;
84
+ const colSum = columnRatios.reduce((a, b) => a + b, 0);
85
+ const rowSum = rowRatios.reduce((a, b) => a + b, 0);
86
+ const colWidths = columnRatios.map(r => (r / colSum) * usableW);
87
+ const rowHeights = rowRatios.map(r => (r / rowSum) * usableH);
88
+ // Compute column x-centers starting from left edge of inner area
89
+ const colXCenters = [];
90
+ let cx = -innerW / 2;
91
+ for (let c = 0; c < numCols; c++) {
92
+ colXCenters.push(cx + colWidths[c] / 2);
93
+ cx += colWidths[c];
94
+ if (c < numCols - 1)
95
+ cx += columnDividerThickness;
96
+ }
97
+ // Compute row y-centers starting from top edge of inner area (R1 = top)
98
+ const rowYCenters = [];
99
+ let cy = innerH / 2;
100
+ for (let r = 0; r < numRows; r++) {
101
+ rowYCenters.push(cy - rowHeights[r] / 2);
102
+ cy -= rowHeights[r];
103
+ if (r < numRows - 1)
104
+ cy -= rowDividerThickness;
105
+ }
106
+ // Column dividers — full inner height
107
+ cx = -innerW / 2;
108
+ for (let c = 0; c < numCols - 1; c++) {
109
+ cx += colWidths[c];
110
+ addBox(mesh, frameMaterial, columnDividerThickness, innerH, frameDepth, cx + columnDividerThickness / 2, 0, 0);
111
+ cx += columnDividerThickness;
112
+ }
113
+ // Row dividers — per column width, so they don't overlap column dividers (top to bottom)
114
+ cy = innerH / 2;
115
+ for (let r = 0; r < numRows - 1; r++) {
116
+ cy -= rowHeights[r];
117
+ const divY = cy - rowDividerThickness / 2;
118
+ for (let c = 0; c < numCols; c++) {
119
+ addBox(mesh, frameMaterial, colWidths[c], rowDividerThickness, frameDepth, colXCenters[c], divY, 0);
120
+ }
121
+ cy -= rowDividerThickness;
122
+ }
123
+ // Glass panes
124
+ const glassDepth = Math.max(0.004, frameDepth * 0.08);
125
+ for (let c = 0; c < numCols; c++) {
126
+ for (let r = 0; r < numRows; r++) {
127
+ addBox(mesh, glassMaterial, colWidths[c], rowHeights[r], glassDepth, colXCenters[c], rowYCenters[r], 0);
128
+ }
129
+ }
130
+ // ── Sill ──
131
+ if (sill) {
132
+ const sillW = width + sillDepth * 0.4; // slightly wider than frame
133
+ // Protrudes from the front face of the frame (+Z)
134
+ const sillZ = frameDepth / 2 + sillDepth / 2;
135
+ addBox(mesh, frameMaterial, sillW, sillThickness, sillDepth, 0, -height / 2 - sillThickness / 2, sillZ);
136
+ }
137
+ // ── Cutout (for wall CSG) — always full window dimensions, 1m deep ──
138
+ let cutout = mesh.getObjectByName('cutout');
139
+ if (!cutout) {
140
+ cutout = new THREE.Mesh();
141
+ cutout.name = 'cutout';
142
+ mesh.add(cutout);
143
+ }
144
+ cutout.geometry.dispose();
145
+ cutout.geometry = new THREE.BoxGeometry(node.width, node.height, 1.0);
146
+ cutout.visible = false;
147
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pascal-app/core",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Core library for Pascal 3D building editor",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -33,6 +33,7 @@
33
33
  "mitt": "^3.0.1",
34
34
  "nanoid": "^5.1.6",
35
35
  "three-bvh-csg": "^0.0.17",
36
+ "three-mesh-bvh": "^0.9.8",
36
37
  "zod": "^4.3.5",
37
38
  "zundo": "^2.3.0",
38
39
  "zustand": "^5"
@@ -53,8 +54,10 @@
53
54
  ],
54
55
  "repository": {
55
56
  "type": "git",
56
- "url": "https://github.com/your-username/pascal-editor.git",
57
+ "url": "https://github.com/pascalorg/editor.git",
57
58
  "directory": "packages/core"
58
59
  },
59
- "license": "MIT"
60
+ "license": "MIT",
61
+ "homepage": "https://github.com/pascalorg/editor/tree/main/packages/core#readme",
62
+ "bugs": "https://github.com/pascalorg/editor/issues"
60
63
  }