@pascal-app/core 0.3.0 → 0.3.2

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 (43) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/schema/index.d.ts +2 -1
  5. package/dist/schema/index.d.ts.map +1 -1
  6. package/dist/schema/index.js +3 -1
  7. package/dist/schema/material.d.ts +62 -0
  8. package/dist/schema/material.d.ts.map +1 -0
  9. package/dist/schema/material.js +130 -0
  10. package/dist/schema/nodes/ceiling.d.ts +31 -0
  11. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  12. package/dist/schema/nodes/ceiling.js +2 -2
  13. package/dist/schema/nodes/door.d.ts +33 -2
  14. package/dist/schema/nodes/door.d.ts.map +1 -1
  15. package/dist/schema/nodes/door.js +2 -0
  16. package/dist/schema/nodes/roof-segment.d.ts +31 -0
  17. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  18. package/dist/schema/nodes/roof-segment.js +2 -1
  19. package/dist/schema/nodes/roof.d.ts +31 -0
  20. package/dist/schema/nodes/roof.d.ts.map +1 -1
  21. package/dist/schema/nodes/roof.js +2 -1
  22. package/dist/schema/nodes/slab.d.ts +31 -0
  23. package/dist/schema/nodes/slab.d.ts.map +1 -1
  24. package/dist/schema/nodes/slab.js +2 -2
  25. package/dist/schema/nodes/wall.d.ts +31 -0
  26. package/dist/schema/nodes/wall.d.ts.map +1 -1
  27. package/dist/schema/nodes/wall.js +2 -1
  28. package/dist/schema/nodes/window.d.ts +31 -0
  29. package/dist/schema/nodes/window.d.ts.map +1 -1
  30. package/dist/schema/nodes/window.js +2 -1
  31. package/dist/schema/types.d.ts +218 -1
  32. package/dist/schema/types.d.ts.map +1 -1
  33. package/dist/store/actions/node-actions.d.ts.map +1 -1
  34. package/dist/store/actions/node-actions.js +43 -12
  35. package/dist/store/use-scene.d.ts +5 -0
  36. package/dist/store/use-scene.d.ts.map +1 -1
  37. package/dist/store/use-scene.js +14 -2
  38. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  39. package/dist/systems/wall/wall-system.js +0 -2
  40. package/dist/utils/clone-scene-graph.d.ts +18 -0
  41. package/dist/utils/clone-scene-graph.d.ts.map +1 -0
  42. package/dist/utils/clone-scene-graph.js +96 -0
  43. package/package.json +2 -2
@@ -17,6 +17,37 @@ export declare const WindowNode: z.ZodObject<{
17
17
  metadata: z.ZodDefault<z.ZodOptional<z.ZodJSONSchema>>;
18
18
  id: z.ZodDefault<z.ZodTemplateLiteral<`window_${string}`>>;
19
19
  type: z.ZodDefault<z.ZodLiteral<"window">>;
20
+ material: z.ZodOptional<z.ZodObject<{
21
+ preset: z.ZodOptional<z.ZodEnum<{
22
+ custom: "custom";
23
+ white: "white";
24
+ brick: "brick";
25
+ concrete: "concrete";
26
+ wood: "wood";
27
+ glass: "glass";
28
+ metal: "metal";
29
+ plaster: "plaster";
30
+ tile: "tile";
31
+ marble: "marble";
32
+ }>>;
33
+ properties: z.ZodOptional<z.ZodObject<{
34
+ color: z.ZodDefault<z.ZodString>;
35
+ roughness: z.ZodDefault<z.ZodNumber>;
36
+ metalness: z.ZodDefault<z.ZodNumber>;
37
+ opacity: z.ZodDefault<z.ZodNumber>;
38
+ transparent: z.ZodDefault<z.ZodBoolean>;
39
+ side: z.ZodDefault<z.ZodEnum<{
40
+ front: "front";
41
+ back: "back";
42
+ double: "double";
43
+ }>>;
44
+ }, z.core.$strip>>;
45
+ texture: z.ZodOptional<z.ZodObject<{
46
+ url: z.ZodString;
47
+ repeat: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
48
+ scale: z.ZodOptional<z.ZodNumber>;
49
+ }, z.core.$strip>>;
50
+ }, z.core.$strip>>;
20
51
  position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
21
52
  rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
22
53
  side: z.ZodOptional<z.ZodEnum<{
@@ -1 +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"}
1
+ {"version":3,"file":"window.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/window.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwCrB,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAA"}
@@ -1,10 +1,11 @@
1
1
  import dedent from 'dedent';
2
2
  import { z } from 'zod';
3
3
  import { BaseNode, nodeType, objectId } from '../base';
4
+ import { MaterialSchema } from '../material';
4
5
  export const WindowNode = BaseNode.extend({
5
6
  id: objectId('window'),
6
7
  type: nodeType('window'),
7
- // Position in wall-local coordinate system (center of window)
8
+ material: MaterialSchema.optional(),
8
9
  position: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
9
10
  rotation: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
10
11
  side: z.enum(['front', 'back']).optional(),
@@ -196,6 +196,37 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
196
196
  id: z.ZodDefault<z.ZodTemplateLiteral<`wall_${string}`>>;
197
197
  type: z.ZodDefault<z.ZodLiteral<"wall">>;
198
198
  children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`item_${string}`>>>>;
199
+ material: z.ZodOptional<z.ZodObject<{
200
+ preset: z.ZodOptional<z.ZodEnum<{
201
+ custom: "custom";
202
+ white: "white";
203
+ brick: "brick";
204
+ concrete: "concrete";
205
+ wood: "wood";
206
+ glass: "glass";
207
+ metal: "metal";
208
+ plaster: "plaster";
209
+ tile: "tile";
210
+ marble: "marble";
211
+ }>>;
212
+ properties: z.ZodOptional<z.ZodObject<{
213
+ color: z.ZodDefault<z.ZodString>;
214
+ roughness: z.ZodDefault<z.ZodNumber>;
215
+ metalness: z.ZodDefault<z.ZodNumber>;
216
+ opacity: z.ZodDefault<z.ZodNumber>;
217
+ transparent: z.ZodDefault<z.ZodBoolean>;
218
+ side: z.ZodDefault<z.ZodEnum<{
219
+ front: "front";
220
+ back: "back";
221
+ double: "double";
222
+ }>>;
223
+ }, z.core.$strip>>;
224
+ texture: z.ZodOptional<z.ZodObject<{
225
+ url: z.ZodString;
226
+ repeat: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
227
+ scale: z.ZodOptional<z.ZodNumber>;
228
+ }, z.core.$strip>>;
229
+ }, z.core.$strip>>;
199
230
  thickness: z.ZodOptional<z.ZodNumber>;
200
231
  height: z.ZodOptional<z.ZodNumber>;
201
232
  start: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
@@ -341,6 +372,37 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
341
372
  metadata: z.ZodDefault<z.ZodOptional<z.ZodJSONSchema>>;
342
373
  id: z.ZodDefault<z.ZodTemplateLiteral<`slab_${string}`>>;
343
374
  type: z.ZodDefault<z.ZodLiteral<"slab">>;
375
+ material: z.ZodOptional<z.ZodObject<{
376
+ preset: z.ZodOptional<z.ZodEnum<{
377
+ custom: "custom";
378
+ white: "white";
379
+ brick: "brick";
380
+ concrete: "concrete";
381
+ wood: "wood";
382
+ glass: "glass";
383
+ metal: "metal";
384
+ plaster: "plaster";
385
+ tile: "tile";
386
+ marble: "marble";
387
+ }>>;
388
+ properties: z.ZodOptional<z.ZodObject<{
389
+ color: z.ZodDefault<z.ZodString>;
390
+ roughness: z.ZodDefault<z.ZodNumber>;
391
+ metalness: z.ZodDefault<z.ZodNumber>;
392
+ opacity: z.ZodDefault<z.ZodNumber>;
393
+ transparent: z.ZodDefault<z.ZodBoolean>;
394
+ side: z.ZodDefault<z.ZodEnum<{
395
+ front: "front";
396
+ back: "back";
397
+ double: "double";
398
+ }>>;
399
+ }, z.core.$strip>>;
400
+ texture: z.ZodOptional<z.ZodObject<{
401
+ url: z.ZodString;
402
+ repeat: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
403
+ scale: z.ZodOptional<z.ZodNumber>;
404
+ }, z.core.$strip>>;
405
+ }, z.core.$strip>>;
344
406
  polygon: z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
345
407
  holes: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>>;
346
408
  elevation: z.ZodDefault<z.ZodNumber>;
@@ -363,6 +425,37 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
363
425
  id: z.ZodDefault<z.ZodTemplateLiteral<`ceiling_${string}`>>;
364
426
  type: z.ZodDefault<z.ZodLiteral<"ceiling">>;
365
427
  children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`item_${string}`>>>>;
428
+ material: z.ZodOptional<z.ZodObject<{
429
+ preset: z.ZodOptional<z.ZodEnum<{
430
+ custom: "custom";
431
+ white: "white";
432
+ brick: "brick";
433
+ concrete: "concrete";
434
+ wood: "wood";
435
+ glass: "glass";
436
+ metal: "metal";
437
+ plaster: "plaster";
438
+ tile: "tile";
439
+ marble: "marble";
440
+ }>>;
441
+ properties: z.ZodOptional<z.ZodObject<{
442
+ color: z.ZodDefault<z.ZodString>;
443
+ roughness: z.ZodDefault<z.ZodNumber>;
444
+ metalness: z.ZodDefault<z.ZodNumber>;
445
+ opacity: z.ZodDefault<z.ZodNumber>;
446
+ transparent: z.ZodDefault<z.ZodBoolean>;
447
+ side: z.ZodDefault<z.ZodEnum<{
448
+ front: "front";
449
+ back: "back";
450
+ double: "double";
451
+ }>>;
452
+ }, z.core.$strip>>;
453
+ texture: z.ZodOptional<z.ZodObject<{
454
+ url: z.ZodString;
455
+ repeat: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
456
+ scale: z.ZodOptional<z.ZodNumber>;
457
+ }, z.core.$strip>>;
458
+ }, z.core.$strip>>;
366
459
  polygon: z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
367
460
  holes: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>>;
368
461
  height: z.ZodDefault<z.ZodNumber>;
@@ -384,6 +477,37 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
384
477
  metadata: z.ZodDefault<z.ZodOptional<z.ZodJSONSchema>>;
385
478
  id: z.ZodDefault<z.ZodTemplateLiteral<`roof_${string}`>>;
386
479
  type: z.ZodDefault<z.ZodLiteral<"roof">>;
480
+ material: z.ZodOptional<z.ZodObject<{
481
+ preset: z.ZodOptional<z.ZodEnum<{
482
+ custom: "custom";
483
+ white: "white";
484
+ brick: "brick";
485
+ concrete: "concrete";
486
+ wood: "wood";
487
+ glass: "glass";
488
+ metal: "metal";
489
+ plaster: "plaster";
490
+ tile: "tile";
491
+ marble: "marble";
492
+ }>>;
493
+ properties: z.ZodOptional<z.ZodObject<{
494
+ color: z.ZodDefault<z.ZodString>;
495
+ roughness: z.ZodDefault<z.ZodNumber>;
496
+ metalness: z.ZodDefault<z.ZodNumber>;
497
+ opacity: z.ZodDefault<z.ZodNumber>;
498
+ transparent: z.ZodDefault<z.ZodBoolean>;
499
+ side: z.ZodDefault<z.ZodEnum<{
500
+ front: "front";
501
+ back: "back";
502
+ double: "double";
503
+ }>>;
504
+ }, z.core.$strip>>;
505
+ texture: z.ZodOptional<z.ZodObject<{
506
+ url: z.ZodString;
507
+ repeat: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
508
+ scale: z.ZodOptional<z.ZodNumber>;
509
+ }, z.core.$strip>>;
510
+ }, z.core.$strip>>;
387
511
  position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
388
512
  rotation: z.ZodDefault<z.ZodNumber>;
389
513
  children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`rseg_${string}`>>>>;
@@ -405,6 +529,37 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
405
529
  metadata: z.ZodDefault<z.ZodOptional<z.ZodJSONSchema>>;
406
530
  id: z.ZodDefault<z.ZodTemplateLiteral<`rseg_${string}`>>;
407
531
  type: z.ZodDefault<z.ZodLiteral<"roof-segment">>;
532
+ material: z.ZodOptional<z.ZodObject<{
533
+ preset: z.ZodOptional<z.ZodEnum<{
534
+ custom: "custom";
535
+ white: "white";
536
+ brick: "brick";
537
+ concrete: "concrete";
538
+ wood: "wood";
539
+ glass: "glass";
540
+ metal: "metal";
541
+ plaster: "plaster";
542
+ tile: "tile";
543
+ marble: "marble";
544
+ }>>;
545
+ properties: z.ZodOptional<z.ZodObject<{
546
+ color: z.ZodDefault<z.ZodString>;
547
+ roughness: z.ZodDefault<z.ZodNumber>;
548
+ metalness: z.ZodDefault<z.ZodNumber>;
549
+ opacity: z.ZodDefault<z.ZodNumber>;
550
+ transparent: z.ZodDefault<z.ZodBoolean>;
551
+ side: z.ZodDefault<z.ZodEnum<{
552
+ front: "front";
553
+ back: "back";
554
+ double: "double";
555
+ }>>;
556
+ }, z.core.$strip>>;
557
+ texture: z.ZodOptional<z.ZodObject<{
558
+ url: z.ZodString;
559
+ repeat: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
560
+ scale: z.ZodOptional<z.ZodNumber>;
561
+ }, z.core.$strip>>;
562
+ }, z.core.$strip>>;
408
563
  position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
409
564
  rotation: z.ZodDefault<z.ZodNumber>;
410
565
  roofType: z.ZodDefault<z.ZodEnum<{
@@ -488,6 +643,37 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
488
643
  metadata: z.ZodDefault<z.ZodOptional<z.ZodJSONSchema>>;
489
644
  id: z.ZodDefault<z.ZodTemplateLiteral<`window_${string}`>>;
490
645
  type: z.ZodDefault<z.ZodLiteral<"window">>;
646
+ material: z.ZodOptional<z.ZodObject<{
647
+ preset: z.ZodOptional<z.ZodEnum<{
648
+ custom: "custom";
649
+ white: "white";
650
+ brick: "brick";
651
+ concrete: "concrete";
652
+ wood: "wood";
653
+ glass: "glass";
654
+ metal: "metal";
655
+ plaster: "plaster";
656
+ tile: "tile";
657
+ marble: "marble";
658
+ }>>;
659
+ properties: z.ZodOptional<z.ZodObject<{
660
+ color: z.ZodDefault<z.ZodString>;
661
+ roughness: z.ZodDefault<z.ZodNumber>;
662
+ metalness: z.ZodDefault<z.ZodNumber>;
663
+ opacity: z.ZodDefault<z.ZodNumber>;
664
+ transparent: z.ZodDefault<z.ZodBoolean>;
665
+ side: z.ZodDefault<z.ZodEnum<{
666
+ front: "front";
667
+ back: "back";
668
+ double: "double";
669
+ }>>;
670
+ }, z.core.$strip>>;
671
+ texture: z.ZodOptional<z.ZodObject<{
672
+ url: z.ZodString;
673
+ repeat: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
674
+ scale: z.ZodOptional<z.ZodNumber>;
675
+ }, z.core.$strip>>;
676
+ }, z.core.$strip>>;
491
677
  position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
492
678
  rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
493
679
  side: z.ZodOptional<z.ZodEnum<{
@@ -524,6 +710,37 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
524
710
  metadata: z.ZodDefault<z.ZodOptional<z.ZodJSONSchema>>;
525
711
  id: z.ZodDefault<z.ZodTemplateLiteral<`door_${string}`>>;
526
712
  type: z.ZodDefault<z.ZodLiteral<"door">>;
713
+ material: z.ZodOptional<z.ZodObject<{
714
+ preset: z.ZodOptional<z.ZodEnum<{
715
+ custom: "custom";
716
+ white: "white";
717
+ brick: "brick";
718
+ concrete: "concrete";
719
+ wood: "wood";
720
+ glass: "glass";
721
+ metal: "metal";
722
+ plaster: "plaster";
723
+ tile: "tile";
724
+ marble: "marble";
725
+ }>>;
726
+ properties: z.ZodOptional<z.ZodObject<{
727
+ color: z.ZodDefault<z.ZodString>;
728
+ roughness: z.ZodDefault<z.ZodNumber>;
729
+ metalness: z.ZodDefault<z.ZodNumber>;
730
+ opacity: z.ZodDefault<z.ZodNumber>;
731
+ transparent: z.ZodDefault<z.ZodBoolean>;
732
+ side: z.ZodDefault<z.ZodEnum<{
733
+ front: "front";
734
+ back: "back";
735
+ double: "double";
736
+ }>>;
737
+ }, z.core.$strip>>;
738
+ texture: z.ZodOptional<z.ZodObject<{
739
+ url: z.ZodString;
740
+ repeat: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
741
+ scale: z.ZodOptional<z.ZodNumber>;
742
+ }, z.core.$strip>>;
743
+ }, z.core.$strip>>;
527
744
  position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
528
745
  rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
529
746
  side: z.ZodOptional<z.ZodEnum<{
@@ -547,8 +764,8 @@ export declare const AnyNode: z.ZodDiscriminatedUnion<[z.ZodObject<{
547
764
  }>>;
548
765
  segments: z.ZodDefault<z.ZodArray<z.ZodObject<{
549
766
  type: z.ZodEnum<{
550
- panel: "panel";
551
767
  glass: "glass";
768
+ panel: "panel";
552
769
  empty: "empty";
553
770
  }>;
554
771
  heightRatio: z.ZodNumber;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/schema/types.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,KAAK,CAAA;AAgBnB,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAelB,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;AAgBnB,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAelB,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 +1 @@
1
- {"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAEtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAI9C,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,EAAE,SA2C/C,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAAE,SAoDrD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,SAAS,EAAE,SA+DjB,CAAA"}
1
+ {"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAEtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAQ9C,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,EAAE,SA2C/C,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAAE,SAsErD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,SAAS,EAAE,SA4EjB,CAAA"}
@@ -1,3 +1,6 @@
1
+ // Track pending RAF for updateNodesAction to prevent multiple queued callbacks
2
+ let pendingRafId = null;
3
+ let pendingUpdates = new Set();
1
4
  export const createNodesAction = (set, get, ops) => {
2
5
  set((state) => {
3
6
  const nextNodes = { ...state.nodes };
@@ -39,6 +42,7 @@ export const createNodesAction = (set, get, ops) => {
39
42
  };
40
43
  export const updateNodesAction = (set, get, updates) => {
41
44
  const parentsToUpdate = new Set();
45
+ const idsToMarkDirty = new Set();
42
46
  set((state) => {
43
47
  const nextNodes = { ...state.nodes };
44
48
  for (const { id, data } of updates) {
@@ -73,14 +77,28 @@ export const updateNodesAction = (set, get, updates) => {
73
77
  }
74
78
  return { nodes: nextNodes };
75
79
  });
76
- // Mark dirty after the next frame to ensure React renders complete
77
- requestAnimationFrame(() => {
78
- updates.forEach((u) => {
79
- get().markDirty(u.id);
80
- });
81
- parentsToUpdate.forEach((pId) => {
82
- get().markDirty(pId);
80
+ // Collect all IDs that need to be marked dirty
81
+ for (const u of updates) {
82
+ idsToMarkDirty.add(u.id);
83
+ }
84
+ for (const pId of parentsToUpdate) {
85
+ idsToMarkDirty.add(pId);
86
+ }
87
+ // Add to pending updates set
88
+ for (const id of idsToMarkDirty) {
89
+ pendingUpdates.add(id);
90
+ }
91
+ // Cancel any pending RAF and schedule a new one
92
+ if (pendingRafId !== null) {
93
+ cancelAnimationFrame(pendingRafId);
94
+ }
95
+ pendingRafId = requestAnimationFrame(() => {
96
+ // Mark all pending updates as dirty
97
+ pendingUpdates.forEach((id) => {
98
+ get().markDirty(id);
83
99
  });
100
+ pendingUpdates.clear();
101
+ pendingRafId = null;
84
102
  });
85
103
  };
86
104
  export const deleteNodesAction = (set, get, ids) => {
@@ -89,7 +107,25 @@ export const deleteNodesAction = (set, get, ids) => {
89
107
  const nextNodes = { ...state.nodes };
90
108
  const nextCollections = { ...state.collections };
91
109
  let nextRootIds = [...state.rootNodeIds];
110
+ // Collect all IDs to delete (including descendants) in a first pass
111
+ // This avoids issues with recursive calls during state mutation
112
+ const allIdsToDelete = new Set();
113
+ const collectDescendants = (id) => {
114
+ const node = nextNodes[id];
115
+ if (!node)
116
+ return;
117
+ allIdsToDelete.add(id);
118
+ if ('children' in node && node.children) {
119
+ for (const childId of node.children) {
120
+ collectDescendants(childId);
121
+ }
122
+ }
123
+ };
92
124
  for (const id of ids) {
125
+ collectDescendants(id);
126
+ }
127
+ // Now process all nodes for deletion
128
+ for (const id of allIdsToDelete) {
93
129
  const node = nextNodes[id];
94
130
  if (!node)
95
131
  continue;
@@ -118,11 +154,6 @@ export const deleteNodesAction = (set, get, ids) => {
118
154
  }
119
155
  // 4. Delete the node itself
120
156
  delete nextNodes[id];
121
- // Inside the deleteNodes loop
122
- if ('children' in node && node.children.length > 0) {
123
- // Recursively delete all children first
124
- get().deleteNodes(node.children);
125
- }
126
157
  }
127
158
  return { nodes: nextNodes, rootNodeIds: nextRootIds, collections: nextCollections };
128
159
  });
@@ -36,5 +36,10 @@ type UseSceneStore = UseBoundStore<StoreApi<SceneState>> & {
36
36
  };
37
37
  declare const useScene: UseSceneStore;
38
38
  export default useScene;
39
+ /**
40
+ * Clears temporal history tracking variables to prevent memory leaks.
41
+ * Should be called when unloading a scene to release node references.
42
+ */
43
+ export declare function clearTemporalTracking(): void;
39
44
  export declare function clearSceneHistory(): void;
40
45
  //# sourceMappingURL=use-scene.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-scene.d.ts","sourceRoot":"","sources":["../../src/store/use-scene.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C,OAAO,EAAU,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAA;AAEnE,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAIrE,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AA8CzD,MAAM,MAAM,UAAU,GAAG;IAEvB,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IAGjC,WAAW,EAAE,SAAS,EAAE,CAAA;IAGxB,UAAU,EAAE,GAAG,CAAC,SAAS,CAAC,CAAA;IAG1B,WAAW,EAAE,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;IAG7C,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,UAAU,EAAE,MAAM,IAAI,CAAA;IACtB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,IAAI,CAAA;IAE/E,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IAClC,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IAEnC,UAAU,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,KAAK,IAAI,CAAA;IACzD,WAAW,EAAE,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;KAAE,EAAE,KAAK,IAAI,CAAA;IAErE,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;IAC3D,WAAW,EAAE,CAAC,OAAO,EAAE;QAAE,EAAE,EAAE,SAAS,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,EAAE,KAAK,IAAI,CAAA;IAE3E,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,WAAW,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,IAAI,CAAA;IAGvC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK,YAAY,CAAA;IACvE,gBAAgB,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,IAAI,CAAA;IAC5C,gBAAgB,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,CAAA;IACnF,eAAe,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;IAC9D,oBAAoB,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CACpE,CAAA;AAID,KAAK,aAAa,GAAG,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,GAAG;IACzD,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;CAC7F,CAAA;AAED,QAAA,MAAM,QAAQ,EAAE,aAqMf,CAAA;AAED,eAAe,QAAQ,CAAA;AAOvB,wBAAgB,iBAAiB,SAKhC"}
1
+ {"version":3,"file":"use-scene.d.ts","sourceRoot":"","sources":["../../src/store/use-scene.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C,OAAO,EAAU,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAA;AAEnE,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAIrE,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AA8CzD,MAAM,MAAM,UAAU,GAAG;IAEvB,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IAGjC,WAAW,EAAE,SAAS,EAAE,CAAA;IAGxB,UAAU,EAAE,GAAG,CAAC,SAAS,CAAC,CAAA;IAG1B,WAAW,EAAE,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;IAG7C,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,UAAU,EAAE,MAAM,IAAI,CAAA;IACtB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,IAAI,CAAA;IAE/E,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IAClC,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IAEnC,UAAU,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,KAAK,IAAI,CAAA;IACzD,WAAW,EAAE,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;KAAE,EAAE,KAAK,IAAI,CAAA;IAErE,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;IAC3D,WAAW,EAAE,CAAC,OAAO,EAAE;QAAE,EAAE,EAAE,SAAS,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,EAAE,KAAK,IAAI,CAAA;IAE3E,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,WAAW,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,IAAI,CAAA;IAGvC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK,YAAY,CAAA;IACvE,gBAAgB,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,IAAI,CAAA;IAC5C,gBAAgB,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,CAAA;IACnF,eAAe,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;IAC9D,oBAAoB,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CACpE,CAAA;AAID,KAAK,aAAa,GAAG,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,GAAG;IACzD,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;CAC7F,CAAA;AAED,QAAA,MAAM,QAAQ,EAAE,aA2Mf,CAAA;AAED,eAAe,QAAQ,CAAA;AAOvB;;;GAGG;AACH,wBAAgB,qBAAqB,SAIpC;AAED,wBAAgB,iBAAiB,SAGhC"}
@@ -56,6 +56,10 @@ const useScene = create()(temporal((set, get) => ({
56
56
  // 4. Collections
57
57
  collections: {},
58
58
  unloadScene: () => {
59
+ // Clear temporal tracking to prevent memory leaks from stale node references
60
+ prevPastLength = 0;
61
+ prevFutureLength = 0;
62
+ prevNodesSnapshot = null;
59
63
  set({
60
64
  nodes: {},
61
65
  rootNodeIds: [],
@@ -74,6 +78,7 @@ const useScene = create()(temporal((set, get) => ({
74
78
  nodes: patchedNodes,
75
79
  rootNodeIds,
76
80
  dirtyNodes: new Set(),
81
+ collections: {},
77
82
  });
78
83
  // Mark all nodes as dirty to trigger re-validation
79
84
  Object.values(patchedNodes).forEach((node) => {
@@ -222,12 +227,19 @@ export default useScene;
222
227
  let prevPastLength = 0;
223
228
  let prevFutureLength = 0;
224
229
  let prevNodesSnapshot = null;
225
- export function clearSceneHistory() {
226
- useScene.temporal.getState().clear();
230
+ /**
231
+ * Clears temporal history tracking variables to prevent memory leaks.
232
+ * Should be called when unloading a scene to release node references.
233
+ */
234
+ export function clearTemporalTracking() {
227
235
  prevPastLength = 0;
228
236
  prevFutureLength = 0;
229
237
  prevNodesSnapshot = null;
230
238
  }
239
+ export function clearSceneHistory() {
240
+ useScene.temporal.getState().clear();
241
+ clearTemporalTracking();
242
+ }
231
243
  // Subscribe to the temporal store (Undo/Redo events)
232
244
  useScene.temporal.subscribe((state) => {
233
245
  const currentPastLength = state.pastStates.length;
@@ -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;AAM9B,OAAO,KAAK,EAAE,OAAO,EAAa,QAAQ,EAAE,MAAM,cAAc,CAAA;AAGhE,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAA;AAUxB,eAAO,MAAM,UAAU,YAuDtB,CAAA;AA0DD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,OAAO,EAAE,EACxB,SAAS,EAAE,aAAa,EACxB,aAAa,SAAI,oFA+FlB"}
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;AAGhE,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAA;AASxB,eAAO,MAAM,UAAU,YAsDtB,CAAA;AA0DD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,OAAO,EAAE,EACxB,SAAS,EAAE,aAAa,EACxB,aAAa,SAAI,oFA+FlB"}
@@ -13,7 +13,6 @@ const csgEvaluator = new Evaluator();
13
13
  // ============================================================================
14
14
  // WALL SYSTEM
15
15
  // ============================================================================
16
- let useFrameNb = 0;
17
16
  export const WallSystem = () => {
18
17
  const dirtyNodes = useScene((state) => state.dirtyNodes);
19
18
  const clearDirty = useScene((state) => state.clearDirty);
@@ -23,7 +22,6 @@ export const WallSystem = () => {
23
22
  const nodes = useScene.getState().nodes;
24
23
  // Collect dirty walls and their levels
25
24
  const dirtyWallsByLevel = new Map();
26
- useFrameNb += 1;
27
25
  dirtyNodes.forEach((id) => {
28
26
  const node = nodes[id];
29
27
  if (!node || node.type !== 'wall')
@@ -0,0 +1,18 @@
1
+ import type { AnyNode, AnyNodeId } from '../schema';
2
+ import type { Collection, CollectionId } from '../schema/collections';
3
+ export type SceneGraph = {
4
+ nodes: Record<AnyNodeId, AnyNode>;
5
+ rootNodeIds: AnyNodeId[];
6
+ collections?: Record<CollectionId, Collection>;
7
+ };
8
+ /**
9
+ * Deep clones a scene graph with all node IDs regenerated while preserving
10
+ * parent-child relationships and other internal references.
11
+ *
12
+ * This is useful for:
13
+ * - Copying nodes between different projects
14
+ * - Duplicating a subset of a scene within the same project
15
+ * - Multi-scene in-memory scenarios
16
+ */
17
+ export declare function cloneSceneGraph(sceneGraph: SceneGraph): SceneGraph;
18
+ //# sourceMappingURL=clone-scene-graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clone-scene-graph.d.ts","sourceRoot":"","sources":["../../src/utils/clone-scene-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAEnD,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAErE,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IACjC,WAAW,EAAE,SAAS,EAAE,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;CAC/C,CAAA;AAUD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CA4FlE"}
@@ -0,0 +1,96 @@
1
+ import { generateId } from '../schema/base';
2
+ /**
3
+ * Extracts the type prefix from a node ID (e.g., "wall_abc123" -> "wall")
4
+ */
5
+ function extractIdPrefix(id) {
6
+ const underscoreIndex = id.indexOf('_');
7
+ return underscoreIndex === -1 ? 'node' : id.slice(0, underscoreIndex);
8
+ }
9
+ /**
10
+ * Deep clones a scene graph with all node IDs regenerated while preserving
11
+ * parent-child relationships and other internal references.
12
+ *
13
+ * This is useful for:
14
+ * - Copying nodes between different projects
15
+ * - Duplicating a subset of a scene within the same project
16
+ * - Multi-scene in-memory scenarios
17
+ */
18
+ export function cloneSceneGraph(sceneGraph) {
19
+ const { nodes, rootNodeIds, collections } = sceneGraph;
20
+ // Build ID mapping: old ID -> new ID
21
+ const idMap = new Map();
22
+ // Pass 1: Generate new IDs for all nodes
23
+ for (const nodeId of Object.keys(nodes)) {
24
+ const prefix = extractIdPrefix(nodeId);
25
+ idMap.set(nodeId, generateId(prefix));
26
+ }
27
+ // Pass 2: Deep clone nodes with remapped references
28
+ const clonedNodes = {};
29
+ for (const [oldId, node] of Object.entries(nodes)) {
30
+ const newId = idMap.get(oldId);
31
+ // structuredClone to avoid shared references between original and clone
32
+ const clonedNode = structuredClone({ ...node, id: newId });
33
+ // Remap parentId
34
+ if (clonedNode.parentId && typeof clonedNode.parentId === 'string') {
35
+ clonedNode.parentId = (idMap.get(clonedNode.parentId) ?? null);
36
+ }
37
+ // Remap children array (walls, levels, buildings, sites, items can have children)
38
+ if ('children' in clonedNode && Array.isArray(clonedNode.children)) {
39
+ ;
40
+ clonedNode.children = clonedNode.children
41
+ .map((childId) => idMap.get(childId))
42
+ .filter((id) => id !== undefined);
43
+ }
44
+ // Remap wallId (items/doors/windows attached to walls)
45
+ if ('wallId' in clonedNode && typeof clonedNode.wallId === 'string') {
46
+ ;
47
+ clonedNode.wallId = idMap.get(clonedNode.wallId);
48
+ }
49
+ clonedNodes[newId] = clonedNode;
50
+ }
51
+ // Remap root node IDs
52
+ const clonedRootNodeIds = rootNodeIds
53
+ .map((id) => idMap.get(id))
54
+ .filter((id) => id !== undefined);
55
+ // Clone and remap collections if present
56
+ let clonedCollections;
57
+ if (collections) {
58
+ clonedCollections = {};
59
+ const collectionIdMap = new Map();
60
+ // Generate new collection IDs
61
+ for (const collectionId of Object.keys(collections)) {
62
+ collectionIdMap.set(collectionId, generateId('collection'));
63
+ }
64
+ for (const [oldCollectionId, collection] of Object.entries(collections)) {
65
+ const newCollectionId = collectionIdMap.get(oldCollectionId);
66
+ clonedCollections[newCollectionId] = {
67
+ ...collection,
68
+ id: newCollectionId,
69
+ nodeIds: collection.nodeIds
70
+ .map((nodeId) => idMap.get(nodeId))
71
+ .filter((id) => id !== undefined),
72
+ controlNodeId: collection.controlNodeId
73
+ ? idMap.get(collection.controlNodeId)
74
+ : undefined,
75
+ };
76
+ // Update collectionIds on nodes that reference this collection
77
+ for (const oldNodeId of collection.nodeIds) {
78
+ const newNodeId = idMap.get(oldNodeId);
79
+ if (newNodeId && clonedNodes[newNodeId]) {
80
+ const node = clonedNodes[newNodeId];
81
+ if ('collectionIds' in node && Array.isArray(node.collectionIds)) {
82
+ const oldColIds = node.collectionIds;
83
+ node.collectionIds = oldColIds
84
+ .map((cid) => collectionIdMap.get(cid))
85
+ .filter((id) => id !== undefined);
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ return {
92
+ nodes: clonedNodes,
93
+ rootNodeIds: clonedRootNodeIds,
94
+ ...(clonedCollections && { collections: clonedCollections }),
95
+ };
96
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pascal-app/core",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Core library for Pascal 3D building editor",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,7 +25,7 @@
25
25
  "@react-three/drei": "^10",
26
26
  "@react-three/fiber": "^9",
27
27
  "react": "^18 || ^19",
28
- "three": "^0.183"
28
+ "three": "^0.182"
29
29
  },
30
30
  "dependencies": {
31
31
  "dedent": "^1.7.1",