@pascal-app/core 0.8.0 → 0.9.1

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 (272) hide show
  1. package/dist/events/bus.d.ts +39 -13
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/hooks/scene-registry/scene-registry.d.ts +6 -22
  4. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  5. package/dist/hooks/scene-registry/scene-registry.js +33 -26
  6. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
  7. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
  8. package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
  9. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
  10. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
  11. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +273 -0
  12. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +13 -2
  13. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  14. package/dist/hooks/spatial-grid/spatial-grid-manager.js +95 -9
  15. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
  16. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  17. package/dist/hooks/spatial-grid/spatial-grid-sync.js +91 -18
  18. package/dist/index.d.ts +22 -4
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +19 -2
  21. package/dist/lib/polygon-relations.d.ts +10 -0
  22. package/dist/lib/polygon-relations.d.ts.map +1 -0
  23. package/dist/lib/polygon-relations.js +73 -0
  24. package/dist/lib/space-detection-pause.test.d.ts +2 -0
  25. package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
  26. package/dist/lib/space-detection-pause.test.js +42 -0
  27. package/dist/lib/space-detection.d.ts +30 -1
  28. package/dist/lib/space-detection.d.ts.map +1 -1
  29. package/dist/lib/space-detection.js +215 -36
  30. package/dist/lib/space-detection.test.d.ts +2 -0
  31. package/dist/lib/space-detection.test.d.ts.map +1 -0
  32. package/dist/lib/space-detection.test.js +77 -0
  33. package/dist/material-library.d.ts +1 -1
  34. package/dist/material-library.d.ts.map +1 -1
  35. package/dist/material-library.js +1940 -143
  36. package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
  37. package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
  38. package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
  39. package/dist/registry/handles.d.ts +321 -0
  40. package/dist/registry/handles.d.ts.map +1 -0
  41. package/dist/registry/handles.js +24 -0
  42. package/dist/registry/index.d.ts +7 -0
  43. package/dist/registry/index.d.ts.map +1 -0
  44. package/dist/registry/index.js +4 -0
  45. package/dist/registry/registry.d.ts +93 -0
  46. package/dist/registry/registry.d.ts.map +1 -0
  47. package/dist/registry/registry.js +201 -0
  48. package/dist/registry/registry.test.d.ts +2 -0
  49. package/dist/registry/registry.test.d.ts.map +1 -0
  50. package/dist/registry/registry.test.js +183 -0
  51. package/dist/registry/relations-resolver.d.ts +42 -0
  52. package/dist/registry/relations-resolver.d.ts.map +1 -0
  53. package/dist/registry/relations-resolver.js +77 -0
  54. package/dist/registry/relations-resolver.test.d.ts +2 -0
  55. package/dist/registry/relations-resolver.test.d.ts.map +1 -0
  56. package/dist/registry/relations-resolver.test.js +183 -0
  57. package/dist/registry/scene-api.d.ts +44 -0
  58. package/dist/registry/scene-api.d.ts.map +1 -0
  59. package/dist/registry/scene-api.js +111 -0
  60. package/dist/registry/scene-api.test.d.ts +2 -0
  61. package/dist/registry/scene-api.test.d.ts.map +1 -0
  62. package/dist/registry/scene-api.test.js +183 -0
  63. package/dist/registry/subtree.d.ts +67 -0
  64. package/dist/registry/subtree.d.ts.map +1 -0
  65. package/dist/registry/subtree.js +119 -0
  66. package/dist/registry/subtree.test.d.ts +2 -0
  67. package/dist/registry/subtree.test.d.ts.map +1 -0
  68. package/dist/registry/subtree.test.js +133 -0
  69. package/dist/registry/types.d.ts +1420 -0
  70. package/dist/registry/types.d.ts.map +1 -0
  71. package/dist/registry/types.js +1 -0
  72. package/dist/schema/index.d.ts +17 -2
  73. package/dist/schema/index.d.ts.map +1 -1
  74. package/dist/schema/index.js +17 -3
  75. package/dist/schema/material.d.ts +10 -0
  76. package/dist/schema/material.d.ts.map +1 -1
  77. package/dist/schema/material.js +10 -0
  78. package/dist/schema/nodes/box-vent.d.ts +74 -0
  79. package/dist/schema/nodes/box-vent.d.ts.map +1 -0
  80. package/dist/schema/nodes/box-vent.js +63 -0
  81. package/dist/schema/nodes/building.d.ts +1 -1
  82. package/dist/schema/nodes/building.d.ts.map +1 -1
  83. package/dist/schema/nodes/building.js +3 -2
  84. package/dist/schema/nodes/ceiling.d.ts +2 -0
  85. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  86. package/dist/schema/nodes/ceiling.js +1 -1
  87. package/dist/schema/nodes/chimney.d.ts +158 -0
  88. package/dist/schema/nodes/chimney.d.ts.map +1 -0
  89. package/dist/schema/nodes/chimney.js +67 -0
  90. package/dist/schema/nodes/column.d.ts +597 -5
  91. package/dist/schema/nodes/column.d.ts.map +1 -1
  92. package/dist/schema/nodes/column.js +586 -6
  93. package/dist/schema/nodes/cupola.d.ts +66 -0
  94. package/dist/schema/nodes/cupola.d.ts.map +1 -0
  95. package/dist/schema/nodes/cupola.js +28 -0
  96. package/dist/schema/nodes/dormer.d.ts +228 -0
  97. package/dist/schema/nodes/dormer.d.ts.map +1 -0
  98. package/dist/schema/nodes/dormer.js +119 -0
  99. package/dist/schema/nodes/downspout.d.ts +75 -0
  100. package/dist/schema/nodes/downspout.d.ts.map +1 -0
  101. package/dist/schema/nodes/downspout.js +63 -0
  102. package/dist/schema/nodes/elevator.d.ts +105 -0
  103. package/dist/schema/nodes/elevator.d.ts.map +1 -0
  104. package/dist/schema/nodes/elevator.js +54 -0
  105. package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
  106. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
  107. package/dist/schema/nodes/eyebrow-vent.js +40 -0
  108. package/dist/schema/nodes/fence.d.ts +1 -0
  109. package/dist/schema/nodes/fence.d.ts.map +1 -1
  110. package/dist/schema/nodes/fence.js +2 -0
  111. package/dist/schema/nodes/gutter.d.ts +84 -0
  112. package/dist/schema/nodes/gutter.d.ts.map +1 -0
  113. package/dist/schema/nodes/gutter.js +76 -0
  114. package/dist/schema/nodes/item.d.ts +4 -0
  115. package/dist/schema/nodes/item.d.ts.map +1 -1
  116. package/dist/schema/nodes/item.js +9 -0
  117. package/dist/schema/nodes/level.d.ts +1 -1
  118. package/dist/schema/nodes/level.d.ts.map +1 -1
  119. package/dist/schema/nodes/level.js +2 -0
  120. package/dist/schema/nodes/ridge-vent.d.ts +67 -0
  121. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
  122. package/dist/schema/nodes/ridge-vent.js +30 -0
  123. package/dist/schema/nodes/roof-segment.d.ts +195 -1
  124. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  125. package/dist/schema/nodes/roof-segment.js +254 -3
  126. package/dist/schema/nodes/roof.d.ts.map +1 -1
  127. package/dist/schema/nodes/roof.js +4 -16
  128. package/dist/schema/nodes/shelf.d.ts +105 -0
  129. package/dist/schema/nodes/shelf.d.ts.map +1 -0
  130. package/dist/schema/nodes/shelf.js +84 -0
  131. package/dist/schema/nodes/site.d.ts +1 -122
  132. package/dist/schema/nodes/site.d.ts.map +1 -1
  133. package/dist/schema/nodes/site.js +2 -6
  134. package/dist/schema/nodes/skylight.d.ts +279 -0
  135. package/dist/schema/nodes/skylight.d.ts.map +1 -0
  136. package/dist/schema/nodes/skylight.js +152 -0
  137. package/dist/schema/nodes/slab.d.ts +2 -0
  138. package/dist/schema/nodes/slab.d.ts.map +1 -1
  139. package/dist/schema/nodes/slab.js +1 -1
  140. package/dist/schema/nodes/solar-panel.d.ts +117 -0
  141. package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
  142. package/dist/schema/nodes/solar-panel.js +47 -0
  143. package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
  144. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
  145. package/dist/schema/nodes/surface-hole-metadata.js +3 -1
  146. package/dist/schema/nodes/turbine-vent.d.ts +68 -0
  147. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
  148. package/dist/schema/nodes/turbine-vent.js +41 -0
  149. package/dist/schema/types.d.ts +1325 -125
  150. package/dist/schema/types.d.ts.map +1 -1
  151. package/dist/schema/types.js +26 -0
  152. package/dist/services/alignment-anchors.d.ts +100 -0
  153. package/dist/services/alignment-anchors.d.ts.map +1 -0
  154. package/dist/services/alignment-anchors.js +283 -0
  155. package/dist/services/alignment-anchors.test.d.ts +2 -0
  156. package/dist/services/alignment-anchors.test.d.ts.map +1 -0
  157. package/dist/services/alignment-anchors.test.js +383 -0
  158. package/dist/services/alignment.d.ts +126 -0
  159. package/dist/services/alignment.d.ts.map +1 -0
  160. package/dist/services/alignment.js +169 -0
  161. package/dist/services/alignment.test.d.ts +2 -0
  162. package/dist/services/alignment.test.d.ts.map +1 -0
  163. package/dist/services/alignment.test.js +92 -0
  164. package/dist/services/drag-session.d.ts +47 -0
  165. package/dist/services/drag-session.d.ts.map +1 -0
  166. package/dist/services/drag-session.js +100 -0
  167. package/dist/services/drag-session.test.d.ts +2 -0
  168. package/dist/services/drag-session.test.d.ts.map +1 -0
  169. package/dist/services/drag-session.test.js +219 -0
  170. package/dist/services/hosting.d.ts +80 -0
  171. package/dist/services/hosting.d.ts.map +1 -0
  172. package/dist/services/hosting.js +122 -0
  173. package/dist/services/hosting.test.d.ts +2 -0
  174. package/dist/services/hosting.test.d.ts.map +1 -0
  175. package/dist/services/hosting.test.js +199 -0
  176. package/dist/services/index.d.ts +7 -0
  177. package/dist/services/index.d.ts.map +1 -0
  178. package/dist/services/index.js +6 -0
  179. package/dist/services/movement.d.ts +48 -0
  180. package/dist/services/movement.d.ts.map +1 -0
  181. package/dist/services/movement.js +65 -0
  182. package/dist/services/movement.test.d.ts +2 -0
  183. package/dist/services/movement.test.d.ts.map +1 -0
  184. package/dist/services/movement.test.js +133 -0
  185. package/dist/services/single-undo-dance.test.d.ts +2 -0
  186. package/dist/services/single-undo-dance.test.d.ts.map +1 -0
  187. package/dist/services/single-undo-dance.test.js +210 -0
  188. package/dist/services/snap.d.ts +72 -0
  189. package/dist/services/snap.d.ts.map +1 -0
  190. package/dist/services/snap.js +120 -0
  191. package/dist/services/snap.test.d.ts +2 -0
  192. package/dist/services/snap.test.d.ts.map +1 -0
  193. package/dist/services/snap.test.js +95 -0
  194. package/dist/solar-panel-presets.d.ts +17 -0
  195. package/dist/solar-panel-presets.d.ts.map +1 -0
  196. package/dist/solar-panel-presets.js +43 -0
  197. package/dist/store/actions/node-actions.d.ts +14 -2
  198. package/dist/store/actions/node-actions.d.ts.map +1 -1
  199. package/dist/store/actions/node-actions.js +492 -15
  200. package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
  201. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
  202. package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
  203. package/dist/store/actions/reparent.test.d.ts +2 -0
  204. package/dist/store/actions/reparent.test.d.ts.map +1 -0
  205. package/dist/store/actions/reparent.test.js +212 -0
  206. package/dist/store/use-interactive.d.ts +39 -0
  207. package/dist/store/use-interactive.d.ts.map +1 -1
  208. package/dist/store/use-interactive.js +75 -0
  209. package/dist/store/use-live-node-overrides.d.ts +22 -0
  210. package/dist/store/use-live-node-overrides.d.ts.map +1 -0
  211. package/dist/store/use-live-node-overrides.js +59 -0
  212. package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
  213. package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
  214. package/dist/store/use-scene-elevator-migration.test.js +109 -0
  215. package/dist/store/use-scene.d.ts +11 -0
  216. package/dist/store/use-scene.d.ts.map +1 -1
  217. package/dist/store/use-scene.js +196 -6
  218. package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
  219. package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
  220. package/dist/systems/elevator/elevator-dispatch.js +57 -0
  221. package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
  222. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
  223. package/dist/systems/elevator/elevator-geometry.js +50 -0
  224. package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
  225. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
  226. package/dist/systems/elevator/elevator-opening-sync.js +218 -0
  227. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
  228. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
  229. package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
  230. package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
  231. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
  232. package/dist/systems/elevator/elevator-opening-system.js +48 -0
  233. package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
  234. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
  235. package/dist/systems/elevator/elevator-runtime-system.js +8 -0
  236. package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
  237. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
  238. package/dist/systems/elevator/elevator-runtime.js +225 -0
  239. package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
  240. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
  241. package/dist/systems/elevator/elevator-runtime.test.js +64 -0
  242. package/dist/systems/elevator/elevator-service.d.ts +19 -0
  243. package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
  244. package/dist/systems/elevator/elevator-service.js +111 -0
  245. package/dist/systems/stair/stair-footprint.d.ts +44 -0
  246. package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
  247. package/dist/systems/stair/stair-footprint.js +168 -0
  248. package/dist/systems/stair/stair-opening-preview.d.ts +1777 -0
  249. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
  250. package/dist/systems/stair/stair-opening-preview.js +93 -0
  251. package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
  252. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
  253. package/dist/systems/stair/stair-opening-preview.test.js +80 -0
  254. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  255. package/dist/systems/stair/stair-opening-sync.js +172 -147
  256. package/dist/systems/stair/stair-opening-sync.test.js +385 -2
  257. package/dist/systems/stair/stair-opening-system.d.ts +2 -0
  258. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
  259. package/dist/systems/stair/stair-opening-system.js +104 -0
  260. package/dist/systems/wall/wall-move.d.ts +48 -0
  261. package/dist/systems/wall/wall-move.d.ts.map +1 -0
  262. package/dist/systems/wall/wall-move.js +193 -0
  263. package/dist/utils/clone-scene-graph.d.ts +6 -4
  264. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  265. package/dist/utils/clone-scene-graph.js +6 -4
  266. package/dist/utils/clone-scene-graph.test.d.ts +2 -0
  267. package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
  268. package/dist/utils/clone-scene-graph.test.js +59 -0
  269. package/dist/validation/validate-build-json.d.ts +43 -0
  270. package/dist/validation/validate-build-json.d.ts.map +1 -0
  271. package/dist/validation/validate-build-json.js +227 -0
  272. package/package.json +9 -2
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import type { MaterialSchema as MaterialSchemaType } from '../material';
2
3
  export declare const RoofType: z.ZodEnum<{
3
4
  flat: "flat";
4
5
  hip: "hip";
@@ -9,6 +10,20 @@ export declare const RoofType: z.ZodEnum<{
9
10
  mansard: "mansard";
10
11
  }>;
11
12
  export type RoofType = z.infer<typeof RoofType>;
13
+ export declare const ROOF_SHAPE_DEFAULTS: {
14
+ /** Gambrel: lower (steep) face occupies this fraction of the horizontal half-depth. */
15
+ readonly gambrelLowerWidthRatio: 0.5;
16
+ /** Gambrel: lower (steep) face rises this fraction of the way to the peak. */
17
+ readonly gambrelLowerHeightRatio: 0.6;
18
+ /** Mansard: steep face occupies this fraction of `min(width, depth)`. */
19
+ readonly mansardSteepWidthRatio: 0.15;
20
+ /** Mansard: steep face rises this fraction of the way to the peak. */
21
+ readonly mansardSteepHeightRatio: 0.7;
22
+ /** Dutch: hip face occupies this fraction of `min(width, depth)`. */
23
+ readonly dutchHipWidthRatio: 0.25;
24
+ /** Dutch: hip face rises this fraction of the way to the peak. */
25
+ readonly dutchHipHeightRatio: 0.5;
26
+ };
12
27
  export declare const RoofSegmentNode: z.ZodObject<{
13
28
  object: z.ZodDefault<z.ZodLiteral<"node">>;
14
29
  name: z.ZodOptional<z.ZodString>;
@@ -60,6 +75,105 @@ export declare const RoofSegmentNode: z.ZodObject<{
60
75
  }, z.core.$strip>>;
61
76
  }, z.core.$strip>>;
62
77
  materialPreset: z.ZodOptional<z.ZodString>;
78
+ topMaterial: z.ZodOptional<z.ZodObject<{
79
+ id: z.ZodOptional<z.ZodString>;
80
+ preset: z.ZodOptional<z.ZodEnum<{
81
+ custom: "custom";
82
+ white: "white";
83
+ brick: "brick";
84
+ concrete: "concrete";
85
+ wood: "wood";
86
+ glass: "glass";
87
+ metal: "metal";
88
+ plaster: "plaster";
89
+ tile: "tile";
90
+ marble: "marble";
91
+ }>>;
92
+ properties: z.ZodOptional<z.ZodObject<{
93
+ color: z.ZodDefault<z.ZodString>;
94
+ roughness: z.ZodDefault<z.ZodNumber>;
95
+ metalness: z.ZodDefault<z.ZodNumber>;
96
+ opacity: z.ZodDefault<z.ZodNumber>;
97
+ transparent: z.ZodDefault<z.ZodBoolean>;
98
+ side: z.ZodDefault<z.ZodEnum<{
99
+ front: "front";
100
+ back: "back";
101
+ double: "double";
102
+ }>>;
103
+ }, z.core.$strip>>;
104
+ texture: z.ZodOptional<z.ZodObject<{
105
+ url: z.ZodString;
106
+ repeat: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
107
+ scale: z.ZodOptional<z.ZodNumber>;
108
+ }, z.core.$strip>>;
109
+ }, z.core.$strip>>;
110
+ topMaterialPreset: z.ZodOptional<z.ZodString>;
111
+ edgeMaterial: z.ZodOptional<z.ZodObject<{
112
+ id: z.ZodOptional<z.ZodString>;
113
+ preset: z.ZodOptional<z.ZodEnum<{
114
+ custom: "custom";
115
+ white: "white";
116
+ brick: "brick";
117
+ concrete: "concrete";
118
+ wood: "wood";
119
+ glass: "glass";
120
+ metal: "metal";
121
+ plaster: "plaster";
122
+ tile: "tile";
123
+ marble: "marble";
124
+ }>>;
125
+ properties: z.ZodOptional<z.ZodObject<{
126
+ color: z.ZodDefault<z.ZodString>;
127
+ roughness: z.ZodDefault<z.ZodNumber>;
128
+ metalness: z.ZodDefault<z.ZodNumber>;
129
+ opacity: z.ZodDefault<z.ZodNumber>;
130
+ transparent: z.ZodDefault<z.ZodBoolean>;
131
+ side: z.ZodDefault<z.ZodEnum<{
132
+ front: "front";
133
+ back: "back";
134
+ double: "double";
135
+ }>>;
136
+ }, z.core.$strip>>;
137
+ texture: z.ZodOptional<z.ZodObject<{
138
+ url: z.ZodString;
139
+ repeat: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
140
+ scale: z.ZodOptional<z.ZodNumber>;
141
+ }, z.core.$strip>>;
142
+ }, z.core.$strip>>;
143
+ edgeMaterialPreset: z.ZodOptional<z.ZodString>;
144
+ wallMaterial: z.ZodOptional<z.ZodObject<{
145
+ id: z.ZodOptional<z.ZodString>;
146
+ preset: z.ZodOptional<z.ZodEnum<{
147
+ custom: "custom";
148
+ white: "white";
149
+ brick: "brick";
150
+ concrete: "concrete";
151
+ wood: "wood";
152
+ glass: "glass";
153
+ metal: "metal";
154
+ plaster: "plaster";
155
+ tile: "tile";
156
+ marble: "marble";
157
+ }>>;
158
+ properties: z.ZodOptional<z.ZodObject<{
159
+ color: z.ZodDefault<z.ZodString>;
160
+ roughness: z.ZodDefault<z.ZodNumber>;
161
+ metalness: z.ZodDefault<z.ZodNumber>;
162
+ opacity: z.ZodDefault<z.ZodNumber>;
163
+ transparent: z.ZodDefault<z.ZodBoolean>;
164
+ side: z.ZodDefault<z.ZodEnum<{
165
+ front: "front";
166
+ back: "back";
167
+ double: "double";
168
+ }>>;
169
+ }, z.core.$strip>>;
170
+ texture: z.ZodOptional<z.ZodObject<{
171
+ url: z.ZodString;
172
+ repeat: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
173
+ scale: z.ZodOptional<z.ZodNumber>;
174
+ }, z.core.$strip>>;
175
+ }, z.core.$strip>>;
176
+ wallMaterialPreset: z.ZodOptional<z.ZodString>;
63
177
  position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
64
178
  rotation: z.ZodDefault<z.ZodNumber>;
65
179
  roofType: z.ZodDefault<z.ZodEnum<{
@@ -74,11 +188,91 @@ export declare const RoofSegmentNode: z.ZodObject<{
74
188
  width: z.ZodDefault<z.ZodNumber>;
75
189
  depth: z.ZodDefault<z.ZodNumber>;
76
190
  wallHeight: z.ZodDefault<z.ZodNumber>;
77
- roofHeight: z.ZodDefault<z.ZodNumber>;
191
+ pitch: z.ZodDefault<z.ZodNumber>;
78
192
  wallThickness: z.ZodDefault<z.ZodNumber>;
79
193
  deckThickness: z.ZodDefault<z.ZodNumber>;
80
194
  overhang: z.ZodDefault<z.ZodNumber>;
81
195
  shingleThickness: z.ZodDefault<z.ZodNumber>;
196
+ gambrelLowerWidthRatio: z.ZodDefault<z.ZodNumber>;
197
+ gambrelLowerHeightRatio: z.ZodDefault<z.ZodNumber>;
198
+ mansardSteepWidthRatio: z.ZodDefault<z.ZodNumber>;
199
+ mansardSteepHeightRatio: z.ZodDefault<z.ZodNumber>;
200
+ dutchHipWidthRatio: z.ZodDefault<z.ZodNumber>;
201
+ dutchHipHeightRatio: z.ZodDefault<z.ZodNumber>;
202
+ children: z.ZodDefault<z.ZodArray<z.ZodString>>;
82
203
  }, z.core.$strip>;
83
204
  export type RoofSegmentNode = z.infer<typeof RoofSegmentNode>;
205
+ /** Shape of the per-type ratios consumed by the slope helpers. */
206
+ type ShapeRatios = {
207
+ gambrelLowerWidthRatio: number;
208
+ gambrelLowerHeightRatio: number;
209
+ mansardSteepWidthRatio: number;
210
+ mansardSteepHeightRatio: number;
211
+ dutchHipWidthRatio: number;
212
+ dutchHipHeightRatio: number;
213
+ };
214
+ type PitchInputs = {
215
+ roofType: RoofType;
216
+ width: number;
217
+ depth: number;
218
+ } & Partial<ShapeRatios>;
219
+ export type SegmentSlopeFrame = {
220
+ /** Horizontal half-span of the primary slope face (eave-to-ridge). */
221
+ run: number;
222
+ /** Vertical height of the primary slope face. */
223
+ rise: number;
224
+ /** tan(pitch). 0 for flat or zero-pitch segments. */
225
+ tanTheta: number;
226
+ /** cos(pitch). 1 for flat or zero-pitch segments. */
227
+ cosTheta: number;
228
+ /** sin(pitch). 0 for flat or zero-pitch segments. */
229
+ sinTheta: number;
230
+ /** Overall eave-to-peak height of the assembled roof. */
231
+ activeRh: number;
232
+ };
233
+ /**
234
+ * One stop for the slope math every roof-segment consumer needs. Builds
235
+ * `run`, `rise`, the trig triple, and the overall peak height from the
236
+ * segment's pitch + footprint + roofType. Before this helper existed,
237
+ * the table was duplicated in three places (the brush builder, the
238
+ * skylight surface-frame routine, and the segment-hit raycaster) and
239
+ * silently drifted when a new roof type was added.
240
+ */
241
+ export declare function getSegmentSlopeFrame(node: Pick<RoofSegmentNode, 'roofType' | 'pitch' | 'width' | 'depth'> & Partial<ShapeRatios>): SegmentSlopeFrame;
242
+ /**
243
+ * The eave-to-peak height of the assembled segment, derived from pitch +
244
+ * footprint + roofType. Replaces the legacy `roofHeight` field on the node.
245
+ */
246
+ export declare function getActiveRoofHeight(node: Parameters<typeof getSegmentSlopeFrame>[0]): number;
247
+ /** Segment-local surface height used by roof accessory placement and hit disambiguation. */
248
+ export declare function getRoofSegmentSurfaceY(node: Pick<RoofSegmentNode, 'roofType' | 'width' | 'depth' | 'wallHeight'> & Parameters<typeof getSegmentSlopeFrame>[0], localX: number, localZ: number): number;
249
+ /**
250
+ * Inverse of `getActiveRoofHeight` — recover the pitch a legacy
251
+ * `roofHeight` value would correspond to. Used by the scene migration.
252
+ * Ratio overrides are optional and default to the shape defaults.
253
+ */
254
+ export declare function getPitchFromActiveRoofHeight(input: PitchInputs & {
255
+ roofHeight: number;
256
+ }): number;
257
+ export type RoofSegmentSurfaceMaterialRole = 'top' | 'edge' | 'wall';
258
+ export type RoofSegmentSurfaceMaterialSpec = {
259
+ material?: MaterialSchemaType;
260
+ materialPreset?: string;
261
+ };
262
+ /**
263
+ * Resolve the segment-level material for one of the three surface roles.
264
+ * Falls back through: role-specific field → catch-all `material`. Pass the
265
+ * parent roof to `parentFallback` when you want the roof's role material
266
+ * to fill in for an unset segment slot — typical from the renderer.
267
+ */
268
+ export declare function getEffectiveSegmentSurfaceMaterial(node: RoofSegmentNode, role: RoofSegmentSurfaceMaterialRole, parentFallback?: RoofSegmentSurfaceMaterialSpec): RoofSegmentSurfaceMaterialSpec;
269
+ /**
270
+ * Returns true when the segment has any segment-level material override —
271
+ * either the legacy catch-all or any of the three role-specific fields.
272
+ * Used by `RoofRenderer` and `updateMergedRoofGeometry` to decide whether
273
+ * the segment should be drawn as its own mesh or folded into the merged
274
+ * shell.
275
+ */
276
+ export declare function hasSegmentMaterialOverride(node: RoofSegmentNode): boolean;
277
+ export {};
84
278
  //# sourceMappingURL=roof-segment.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"roof-segment.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/roof-segment.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,QAAQ;;;;;;;;EAA0E,CAAA;AAE/F,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA;AAE/C,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkC3B,CAAA;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA"}
1
+ {"version":3,"file":"roof-segment.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/roof-segment.ts"],"names":[],"mappings":"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"}
@@ -3,11 +3,40 @@ import { z } from 'zod';
3
3
  import { BaseNode, nodeType, objectId } from '../base';
4
4
  import { MaterialSchema } from '../material';
5
5
  export const RoofType = z.enum(['hip', 'gable', 'shed', 'gambrel', 'dutch', 'mansard', 'flat']);
6
+ // Default shape ratios. Tuning these used to require editing the geometry
7
+ // code in two places; they are now schema fields with these defaults.
8
+ export const ROOF_SHAPE_DEFAULTS = {
9
+ /** Gambrel: lower (steep) face occupies this fraction of the horizontal half-depth. */
10
+ gambrelLowerWidthRatio: 0.5,
11
+ /** Gambrel: lower (steep) face rises this fraction of the way to the peak. */
12
+ gambrelLowerHeightRatio: 0.6,
13
+ /** Mansard: steep face occupies this fraction of `min(width, depth)`. */
14
+ mansardSteepWidthRatio: 0.15,
15
+ /** Mansard: steep face rises this fraction of the way to the peak. */
16
+ mansardSteepHeightRatio: 0.7,
17
+ /** Dutch: hip face occupies this fraction of `min(width, depth)`. */
18
+ dutchHipWidthRatio: 0.25,
19
+ /** Dutch: hip face rises this fraction of the way to the peak. */
20
+ dutchHipHeightRatio: 0.5,
21
+ };
6
22
  export const RoofSegmentNode = BaseNode.extend({
7
23
  id: objectId('rseg'),
8
24
  type: nodeType('roof-segment'),
25
+ // Catch-all material — splatted across all 4 slots in the renderer
26
+ // when no role-specific override is set. Kept for back-compat with
27
+ // older scenes; new paint operations should prefer the role fields.
9
28
  material: MaterialSchema.optional(),
10
29
  materialPreset: z.string().optional(),
30
+ // Role-specific overrides mirror the parent roof's surface roles.
31
+ // When set they win over the segment's catch-all `material` and over
32
+ // the parent roof's role / catch-all materials. Resolution order:
33
+ // segment role → segment catch-all → roof role → roof catch-all.
34
+ topMaterial: MaterialSchema.optional(),
35
+ topMaterialPreset: z.string().optional(),
36
+ edgeMaterial: MaterialSchema.optional(),
37
+ edgeMaterialPreset: z.string().optional(),
38
+ wallMaterial: MaterialSchema.optional(),
39
+ wallMaterialPreset: z.string().optional(),
11
40
  position: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
12
41
  // Rotation around Y axis in radians
13
42
  rotation: z.number().default(0),
@@ -16,14 +45,60 @@ export const RoofSegmentNode = BaseNode.extend({
16
45
  // Footprint dimensions
17
46
  width: z.number().default(8),
18
47
  depth: z.number().default(6),
19
- // Vertical dimensions
48
+ // Wall height beneath the roof
20
49
  wallHeight: z.number().default(0.5),
21
- roofHeight: z.number().default(2.5),
50
+ // Roof pitch in degrees — angle of the primary slope face.
51
+ // For gable/hip/shed this is the only slope; for gambrel/mansard/dutch
52
+ // it is the steep (lower) slope. The overall peak height is derived
53
+ // from pitch + footprint + roofType via getActiveRoofHeight().
54
+ pitch: z.number().min(0).max(85).default(40),
22
55
  // Structure thicknesses
23
56
  wallThickness: z.number().default(0.1),
24
57
  deckThickness: z.number().default(0.1),
25
58
  overhang: z.number().default(0.3),
26
59
  shingleThickness: z.number().default(0.05),
60
+ // Shape-specific ratios. Only the pair matching `roofType` is read; the
61
+ // rest are inert. Defined on every segment so the panel can flip
62
+ // roofType without losing the previous shape's tuning.
63
+ gambrelLowerWidthRatio: z
64
+ .number()
65
+ .min(0.1)
66
+ .max(0.9)
67
+ .default(ROOF_SHAPE_DEFAULTS.gambrelLowerWidthRatio),
68
+ gambrelLowerHeightRatio: z
69
+ .number()
70
+ .min(0.1)
71
+ .max(0.9)
72
+ .default(ROOF_SHAPE_DEFAULTS.gambrelLowerHeightRatio),
73
+ mansardSteepWidthRatio: z
74
+ .number()
75
+ .min(0.05)
76
+ .max(0.45)
77
+ .default(ROOF_SHAPE_DEFAULTS.mansardSteepWidthRatio),
78
+ mansardSteepHeightRatio: z
79
+ .number()
80
+ .min(0.1)
81
+ .max(0.9)
82
+ .default(ROOF_SHAPE_DEFAULTS.mansardSteepHeightRatio),
83
+ dutchHipWidthRatio: z
84
+ .number()
85
+ .min(0.05)
86
+ .max(0.45)
87
+ .default(ROOF_SHAPE_DEFAULTS.dutchHipWidthRatio),
88
+ dutchHipHeightRatio: z
89
+ .number()
90
+ .min(0.1)
91
+ .max(0.9)
92
+ .default(ROOF_SHAPE_DEFAULTS.dutchHipHeightRatio),
93
+ // Hosted accessories — chimney, dormer, skylight, box-vent,
94
+ // ridge-vent, solar-panel, gutter. Each accessory's `parentId` points back
95
+ // here; the segment renderer mounts them recursively via
96
+ // `<NodeRenderer>` so they inherit the segment's transform stack.
97
+ // Required for `createNode(child, segmentId)` to append the child
98
+ // to this array — see
99
+ // `wiki/architecture/node-definitions.md` ("Host kinds need a
100
+ // `children` field on the schema").
101
+ children: z.array(z.string()).default([]),
27
102
  }).describe(dedent `
28
103
  Roof segment node - an individual roof module within a roof group.
29
104
  Each segment generates a complete architectural volume (walls + roof).
@@ -31,8 +106,184 @@ export const RoofSegmentNode = BaseNode.extend({
31
106
  - roofType: hip, gable, shed, gambrel, dutch, mansard, flat
32
107
  - width/depth: footprint dimensions
33
108
  - wallHeight: height of walls below the roof
34
- - roofHeight: height of the roof peak above the walls
109
+ - pitch: roof slope in degrees (angle of the primary slope face)
35
110
  - wallThickness/deckThickness: structural thicknesses
36
111
  - overhang: eave overhang distance
37
112
  - shingleThickness: outer shingle layer thickness
113
+ - gambrelLowerWidthRatio / gambrelLowerHeightRatio: kink position on gambrel roofs
114
+ - mansardSteepWidthRatio / mansardSteepHeightRatio: waist position on mansard roofs
115
+ - dutchHipWidthRatio / dutchHipHeightRatio: hip-to-gable split on dutch roofs
38
116
  `);
117
+ function withRatioDefaults(input) {
118
+ return {
119
+ ...input,
120
+ gambrelLowerWidthRatio: input.gambrelLowerWidthRatio ?? ROOF_SHAPE_DEFAULTS.gambrelLowerWidthRatio,
121
+ gambrelLowerHeightRatio: input.gambrelLowerHeightRatio ?? ROOF_SHAPE_DEFAULTS.gambrelLowerHeightRatio,
122
+ mansardSteepWidthRatio: input.mansardSteepWidthRatio ?? ROOF_SHAPE_DEFAULTS.mansardSteepWidthRatio,
123
+ mansardSteepHeightRatio: input.mansardSteepHeightRatio ?? ROOF_SHAPE_DEFAULTS.mansardSteepHeightRatio,
124
+ dutchHipWidthRatio: input.dutchHipWidthRatio ?? ROOF_SHAPE_DEFAULTS.dutchHipWidthRatio,
125
+ dutchHipHeightRatio: input.dutchHipHeightRatio ?? ROOF_SHAPE_DEFAULTS.dutchHipHeightRatio,
126
+ };
127
+ }
128
+ function getPrimarySlopeRun(input) {
129
+ const min = Math.min(input.width, input.depth);
130
+ switch (input.roofType) {
131
+ case 'shed':
132
+ return input.depth;
133
+ case 'gable':
134
+ return input.depth / 2;
135
+ case 'gambrel':
136
+ return (input.depth / 2) * input.gambrelLowerWidthRatio;
137
+ case 'mansard':
138
+ return min * input.mansardSteepWidthRatio;
139
+ case 'dutch':
140
+ return min * input.dutchHipWidthRatio;
141
+ default:
142
+ return min / 2;
143
+ }
144
+ }
145
+ // Fraction of the overall peak height that is taken up by the primary slope.
146
+ function getPrimarySlopeRiseFraction(input) {
147
+ switch (input.roofType) {
148
+ case 'gambrel':
149
+ return input.gambrelLowerHeightRatio;
150
+ case 'mansard':
151
+ return input.mansardSteepHeightRatio;
152
+ case 'dutch':
153
+ return input.dutchHipHeightRatio;
154
+ default:
155
+ return 1;
156
+ }
157
+ }
158
+ /**
159
+ * One stop for the slope math every roof-segment consumer needs. Builds
160
+ * `run`, `rise`, the trig triple, and the overall peak height from the
161
+ * segment's pitch + footprint + roofType. Before this helper existed,
162
+ * the table was duplicated in three places (the brush builder, the
163
+ * skylight surface-frame routine, and the segment-hit raycaster) and
164
+ * silently drifted when a new roof type was added.
165
+ */
166
+ export function getSegmentSlopeFrame(node) {
167
+ const ratios = withRatioDefaults(node);
168
+ const run = getPrimarySlopeRun(ratios);
169
+ // `!(pitch > 0)` (not `pitch <= 0`) so a missing/NaN pitch — e.g. a segment
170
+ // from an older migration that only set `roofHeight`, or stale persisted data —
171
+ // resolves to a flat frame instead of computing `Math.tan(NaN)` → NaN geometry,
172
+ // which poisons the merged-roof CSG ("Coplanar clip not handled" + NaN positions).
173
+ if (node.roofType === 'flat' || !(node.pitch > 0)) {
174
+ return { run, rise: 0, tanTheta: 0, cosTheta: 1, sinTheta: 0, activeRh: 0 };
175
+ }
176
+ const pitchRad = (node.pitch * Math.PI) / 180;
177
+ const tanTheta = Math.tan(pitchRad);
178
+ const cosTheta = Math.cos(pitchRad) || 1;
179
+ const sinTheta = Math.sin(pitchRad);
180
+ const rise = run * tanTheta;
181
+ const activeRh = rise / getPrimarySlopeRiseFraction(ratios);
182
+ return { run, rise, tanTheta, cosTheta, sinTheta, activeRh };
183
+ }
184
+ /**
185
+ * The eave-to-peak height of the assembled segment, derived from pitch +
186
+ * footprint + roofType. Replaces the legacy `roofHeight` field on the node.
187
+ */
188
+ export function getActiveRoofHeight(node) {
189
+ return getSegmentSlopeFrame(node).activeRh;
190
+ }
191
+ /** Segment-local surface height used by roof accessory placement and hit disambiguation. */
192
+ export function getRoofSegmentSurfaceY(node, localX, localZ) {
193
+ const activeRh = getActiveRoofHeight(node);
194
+ const peakY = node.wallHeight + activeRh;
195
+ if (activeRh === 0)
196
+ return node.wallHeight;
197
+ if (node.roofType === 'gable' ||
198
+ node.roofType === 'gambrel' ||
199
+ node.roofType === 'mansard' ||
200
+ node.roofType === 'dutch') {
201
+ const t = node.depth > 0 ? Math.abs(localZ) / (node.depth / 2) : 0;
202
+ return peakY - t * activeRh;
203
+ }
204
+ if (node.roofType === 'shed') {
205
+ const t = (localZ + node.depth / 2) / (node.depth || 1);
206
+ return peakY - t * activeRh;
207
+ }
208
+ if (node.roofType === 'hip') {
209
+ const fx = node.width > 0 ? Math.abs(localX) / (node.width / 2) : 0;
210
+ const fz = node.depth > 0 ? Math.abs(localZ) / (node.depth / 2) : 0;
211
+ return peakY - Math.max(fx, fz) * activeRh;
212
+ }
213
+ const t = node.depth > 0 ? Math.abs(localZ) / (node.depth / 2) : 0;
214
+ return peakY - t * activeRh;
215
+ }
216
+ /**
217
+ * Inverse of `getActiveRoofHeight` — recover the pitch a legacy
218
+ * `roofHeight` value would correspond to. Used by the scene migration.
219
+ * Ratio overrides are optional and default to the shape defaults.
220
+ */
221
+ export function getPitchFromActiveRoofHeight(input) {
222
+ if (input.roofType === 'flat' || input.roofHeight <= 0)
223
+ return 0;
224
+ const ratios = withRatioDefaults(input);
225
+ const run = getPrimarySlopeRun(ratios);
226
+ if (run <= 0)
227
+ return 0;
228
+ const rise = input.roofHeight * getPrimarySlopeRiseFraction(ratios);
229
+ return (Math.atan2(rise, run) * 180) / Math.PI;
230
+ }
231
+ function getLegacyRoofSegmentSurfaceMaterial(node) {
232
+ return {
233
+ material: node.material,
234
+ materialPreset: typeof node.materialPreset === 'string' ? node.materialPreset : undefined,
235
+ };
236
+ }
237
+ /**
238
+ * Resolve the segment-level material for one of the three surface roles.
239
+ * Falls back through: role-specific field → catch-all `material`. Pass the
240
+ * parent roof to `parentFallback` when you want the roof's role material
241
+ * to fill in for an unset segment slot — typical from the renderer.
242
+ */
243
+ export function getEffectiveSegmentSurfaceMaterial(node, role, parentFallback) {
244
+ if (role === 'top') {
245
+ if (node.topMaterial !== undefined || typeof node.topMaterialPreset === 'string') {
246
+ return {
247
+ material: node.topMaterial,
248
+ materialPreset: typeof node.topMaterialPreset === 'string' ? node.topMaterialPreset : undefined,
249
+ };
250
+ }
251
+ }
252
+ else if (role === 'edge') {
253
+ if (node.edgeMaterial !== undefined || typeof node.edgeMaterialPreset === 'string') {
254
+ return {
255
+ material: node.edgeMaterial,
256
+ materialPreset: typeof node.edgeMaterialPreset === 'string' ? node.edgeMaterialPreset : undefined,
257
+ };
258
+ }
259
+ }
260
+ else if (role === 'wall') {
261
+ if (node.wallMaterial !== undefined || typeof node.wallMaterialPreset === 'string') {
262
+ return {
263
+ material: node.wallMaterial,
264
+ materialPreset: typeof node.wallMaterialPreset === 'string' ? node.wallMaterialPreset : undefined,
265
+ };
266
+ }
267
+ }
268
+ const legacy = getLegacyRoofSegmentSurfaceMaterial(node);
269
+ if (legacy.material !== undefined || legacy.materialPreset !== undefined)
270
+ return legacy;
271
+ return parentFallback ?? { material: undefined, materialPreset: undefined };
272
+ }
273
+ /**
274
+ * Returns true when the segment has any segment-level material override —
275
+ * either the legacy catch-all or any of the three role-specific fields.
276
+ * Used by `RoofRenderer` and `updateMergedRoofGeometry` to decide whether
277
+ * the segment should be drawn as its own mesh or folded into the merged
278
+ * shell.
279
+ */
280
+ export function hasSegmentMaterialOverride(node) {
281
+ return (node.material !== undefined ||
282
+ typeof node.materialPreset === 'string' ||
283
+ node.topMaterial !== undefined ||
284
+ typeof node.topMaterialPreset === 'string' ||
285
+ node.edgeMaterial !== undefined ||
286
+ typeof node.edgeMaterialPreset === 'string' ||
287
+ node.wallMaterial !== undefined ||
288
+ typeof node.wallMaterialPreset === 'string');
289
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"roof.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/roof.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,cAAc,IAAI,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAIvE,MAAM,MAAM,uBAAuB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAA;AAC7D,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyBpB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA;AAS/C,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,uBAAuB,GAC5B,uBAAuB,CAoDzB"}
1
+ {"version":3,"file":"roof.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/roof.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,cAAc,IAAI,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAIvE,MAAM,MAAM,uBAAuB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAA;AAC7D,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyBpB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA;AAS/C,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,uBAAuB,GAC5B,uBAAuB,CAoCzB"}
@@ -58,21 +58,9 @@ export function getEffectiveRoofSurfaceMaterial(node, role) {
58
58
  };
59
59
  }
60
60
  }
61
- if (role === 'edge') {
62
- if (node.wallMaterial !== undefined || typeof node.wallMaterialPreset === 'string') {
63
- return {
64
- material: node.wallMaterial,
65
- materialPreset: typeof node.wallMaterialPreset === 'string' ? node.wallMaterialPreset : undefined,
66
- };
67
- }
68
- }
69
- if (role === 'wall') {
70
- if (node.edgeMaterial !== undefined || typeof node.edgeMaterialPreset === 'string') {
71
- return {
72
- material: node.edgeMaterial,
73
- materialPreset: typeof node.edgeMaterialPreset === 'string' ? node.edgeMaterialPreset : undefined,
74
- };
75
- }
76
- }
61
+ // No cross-role fallback: an unset role resolves only to the legacy
62
+ // catch-all (which covers all three roles for back-compat) and otherwise
63
+ // to the caller's theme default. Painting one surface must never bleed
64
+ // onto the others.
77
65
  return getLegacyRoofSurfaceMaterial(node);
78
66
  }
@@ -0,0 +1,105 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Parametric shelf — a configurable furniture unit with one or more
4
+ * horizontal boards that host other items.
5
+ *
6
+ * Four styles share the same dimensional schema:
7
+ *
8
+ * - `wall-shelf` — open boards held by end brackets. `rows > 1` stacks
9
+ * evenly-spaced boards. Brackets style: `minimal | industrial | hidden`.
10
+ * The v1 archetype.
11
+ * - `bookshelf` — full-height cabinet: side panels + multiple shelf
12
+ * boards. `columns > 1` adds vertical dividers between sections.
13
+ * `withBack` toggles a back panel. `withSides` toggles the side
14
+ * panels (`false` = open silhouette held by cross-brace posts).
15
+ * - `open-rack` — industrial wire-rack style: four corner posts, no
16
+ * side panels, slim boards. `withBack` adds an X-brace.
17
+ * - `cubby` — grid of pigeonhole cubicles: `rows × columns` cells
18
+ * formed by full back + sides + inner dividers. Each cubicle hosts
19
+ * items on its own bottom surface.
20
+ *
21
+ * `height` is the distance from floor to the underside of the topmost
22
+ * board (legacy v1 semantic, preserved so v1 scenes load with identical
23
+ * top-board placement). For `rows > 1`, boards are evenly spaced from
24
+ * `height / rows` up to `height`. For `cubby`, the height divides into
25
+ * `rows` equal-height cubicles.
26
+ *
27
+ * Items host on each row's top surface via `capabilities.surfaces.custom`.
28
+ */
29
+ export declare const ShelfNode: z.ZodObject<{
30
+ object: z.ZodDefault<z.ZodLiteral<"node">>;
31
+ name: z.ZodOptional<z.ZodString>;
32
+ parentId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
33
+ visible: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
34
+ camera: z.ZodOptional<z.ZodObject<{
35
+ position: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
36
+ target: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
37
+ mode: z.ZodDefault<z.ZodEnum<{
38
+ perspective: "perspective";
39
+ orthographic: "orthographic";
40
+ }>>;
41
+ fov: z.ZodOptional<z.ZodNumber>;
42
+ zoom: z.ZodOptional<z.ZodNumber>;
43
+ }, z.core.$strip>>;
44
+ metadata: z.ZodDefault<z.ZodOptional<z.ZodJSONSchema>>;
45
+ id: z.ZodDefault<z.ZodTemplateLiteral<`shelf_${string}`>>;
46
+ type: z.ZodDefault<z.ZodLiteral<"shelf">>;
47
+ children: z.ZodDefault<z.ZodArray<z.ZodDefault<z.ZodTemplateLiteral<`item_${string}`>>>>;
48
+ position: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
49
+ rotation: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
50
+ width: z.ZodDefault<z.ZodNumber>;
51
+ depth: z.ZodDefault<z.ZodNumber>;
52
+ thickness: z.ZodDefault<z.ZodNumber>;
53
+ height: z.ZodDefault<z.ZodNumber>;
54
+ style: z.ZodDefault<z.ZodEnum<{
55
+ "wall-shelf": "wall-shelf";
56
+ bookshelf: "bookshelf";
57
+ "open-rack": "open-rack";
58
+ cubby: "cubby";
59
+ }>>;
60
+ rows: z.ZodDefault<z.ZodNumber>;
61
+ columns: z.ZodDefault<z.ZodNumber>;
62
+ withBack: z.ZodDefault<z.ZodBoolean>;
63
+ withSides: z.ZodDefault<z.ZodBoolean>;
64
+ withBottom: z.ZodDefault<z.ZodBoolean>;
65
+ bracketStyle: z.ZodDefault<z.ZodEnum<{
66
+ minimal: "minimal";
67
+ industrial: "industrial";
68
+ hidden: "hidden";
69
+ }>>;
70
+ material: z.ZodOptional<z.ZodObject<{
71
+ id: z.ZodOptional<z.ZodString>;
72
+ preset: z.ZodOptional<z.ZodEnum<{
73
+ custom: "custom";
74
+ white: "white";
75
+ brick: "brick";
76
+ concrete: "concrete";
77
+ wood: "wood";
78
+ glass: "glass";
79
+ metal: "metal";
80
+ plaster: "plaster";
81
+ tile: "tile";
82
+ marble: "marble";
83
+ }>>;
84
+ properties: z.ZodOptional<z.ZodObject<{
85
+ color: z.ZodDefault<z.ZodString>;
86
+ roughness: z.ZodDefault<z.ZodNumber>;
87
+ metalness: z.ZodDefault<z.ZodNumber>;
88
+ opacity: z.ZodDefault<z.ZodNumber>;
89
+ transparent: z.ZodDefault<z.ZodBoolean>;
90
+ side: z.ZodDefault<z.ZodEnum<{
91
+ front: "front";
92
+ back: "back";
93
+ double: "double";
94
+ }>>;
95
+ }, z.core.$strip>>;
96
+ texture: z.ZodOptional<z.ZodObject<{
97
+ url: z.ZodString;
98
+ repeat: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
99
+ scale: z.ZodOptional<z.ZodNumber>;
100
+ }, z.core.$strip>>;
101
+ }, z.core.$strip>>;
102
+ materialPreset: z.ZodOptional<z.ZodString>;
103
+ }, z.core.$strip>;
104
+ export type ShelfNode = z.infer<typeof ShelfNode>;
105
+ //# sourceMappingURL=shelf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shelf.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/shelf.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAKvB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwDpB,CAAA;AAEF,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAA"}