@ifc-lite/viewer 1.17.4 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/.turbo/turbo-build.log +20 -17
  2. package/.turbo/turbo-typecheck.log +1 -1
  3. package/CHANGELOG.md +630 -0
  4. package/DESKTOP_CONTRACT_VERSION +1 -1
  5. package/dist/assets/{basketViewActivator-BmnNtVfZ.js → basketViewActivator-Cm1QEk_R.js} +1 -1
  6. package/dist/assets/drawing-2d-DoxKMqbO.js +257 -0
  7. package/dist/assets/{exporters-ChAtBmlj.js → exporters-B_OBqIyD.js} +3479 -2845
  8. package/dist/assets/{geometry.worker-BQ0rzNo-.js → geometry.worker-xHHy-9DV.js} +1 -1
  9. package/dist/assets/ids-DQ5jY0E8.js +1 -0
  10. package/dist/assets/ifc-lite_bg-ADjKXSms.wasm +0 -0
  11. package/dist/assets/{index-Co8E2-FE.js → index-BKq-M3Mk.js} +55873 -40593
  12. package/dist/assets/index-COnQRuqY.css +1 -0
  13. package/dist/assets/{native-bridge-BRvbckFQ.js → native-bridge-SHXiQwFW.js} +104 -104
  14. package/dist/assets/sandbox-jez21HtV.js +9627 -0
  15. package/dist/assets/{server-client-BV8zHZ7Y.js → server-client-ncOQVNso.js} +1 -1
  16. package/dist/assets/{wasm-bridge-g01g7T9b.js → wasm-bridge-DyfBSB8z.js} +1 -1
  17. package/dist/index.html +8 -7
  18. package/index.html +1 -0
  19. package/package.json +13 -13
  20. package/src/App.tsx +16 -2
  21. package/src/apache-arrow.d.ts +30 -0
  22. package/src/components/viewer/AddElementPanel.tsx +758 -0
  23. package/src/components/viewer/BulkPropertyEditor.tsx +7 -0
  24. package/src/components/viewer/CesiumOverlay.tsx +62 -19
  25. package/src/components/viewer/ChatPanel.tsx +259 -93
  26. package/src/components/viewer/CommandPalette.tsx +56 -7
  27. package/src/components/viewer/EntityContextMenu.tsx +168 -4
  28. package/src/components/viewer/ExportChangesButton.tsx +25 -5
  29. package/src/components/viewer/ExportDialog.tsx +19 -1
  30. package/src/components/viewer/MainToolbar.tsx +73 -13
  31. package/src/components/viewer/PropertiesPanel.tsx +237 -23
  32. package/src/components/viewer/SearchInline.tsx +669 -0
  33. package/src/components/viewer/SearchModal.filter.builder.tsx +766 -0
  34. package/src/components/viewer/SearchModal.filter.tsx +514 -0
  35. package/src/components/viewer/SearchModal.text.tsx +388 -0
  36. package/src/components/viewer/SearchModal.tsx +235 -0
  37. package/src/components/viewer/SettingsPage.tsx +252 -101
  38. package/src/components/viewer/ThemeSwitch.tsx +63 -7
  39. package/src/components/viewer/ToolOverlays.tsx +5 -0
  40. package/src/components/viewer/ViewerLayout.tsx +25 -4
  41. package/src/components/viewer/Viewport.tsx +25 -3
  42. package/src/components/viewer/ViewportContainer.tsx +51 -64
  43. package/src/components/viewer/ViewportOverlays.tsx +5 -2
  44. package/src/components/viewer/annotations/AnnotationDropInput.tsx +203 -0
  45. package/src/components/viewer/annotations/AnnotationLayer.tsx +287 -0
  46. package/src/components/viewer/annotations/AnnotationPin.tsx +90 -0
  47. package/src/components/viewer/annotations/AnnotationPopover.tsx +296 -0
  48. package/src/components/viewer/bcf/BCFTopicDetail.tsx +4 -4
  49. package/src/components/viewer/chat/ModelSelector.tsx +90 -54
  50. package/src/components/viewer/lists/ListPanel.tsx +14 -21
  51. package/src/components/viewer/properties/GeoreferencingPanel.tsx +113 -51
  52. package/src/components/viewer/properties/LocationMap.tsx +9 -7
  53. package/src/components/viewer/properties/ModelMetadataPanel.tsx +1 -1
  54. package/src/components/viewer/properties/RawStepCard.tsx +332 -0
  55. package/src/components/viewer/properties/RawStepRow.tsx +261 -0
  56. package/src/components/viewer/properties/ScheduleCard.tsx +224 -0
  57. package/src/components/viewer/properties/TaskEditCard.tsx +510 -0
  58. package/src/components/viewer/properties/raw-step-format.ts +193 -0
  59. package/src/components/viewer/schedule/AnimationSettingsPopover.tsx +542 -0
  60. package/src/components/viewer/schedule/GanttDependencyArrows.tsx +89 -0
  61. package/src/components/viewer/schedule/GanttDragTooltip.tsx +48 -0
  62. package/src/components/viewer/schedule/GanttEmptyState.tsx +97 -0
  63. package/src/components/viewer/schedule/GanttPanel.tsx +295 -0
  64. package/src/components/viewer/schedule/GanttTaskBar.tsx +199 -0
  65. package/src/components/viewer/schedule/GanttTaskTree.tsx +250 -0
  66. package/src/components/viewer/schedule/GanttTimeline.tsx +305 -0
  67. package/src/components/viewer/schedule/GanttToolbar.tsx +406 -0
  68. package/src/components/viewer/schedule/GenerateAdvancedPanel.tsx +147 -0
  69. package/src/components/viewer/schedule/GenerateScheduleDialog.tsx +392 -0
  70. package/src/components/viewer/schedule/HeightStrategyPanel.tsx +120 -0
  71. package/src/components/viewer/schedule/generate-schedule.test.ts +439 -0
  72. package/src/components/viewer/schedule/generate-schedule.ts +648 -0
  73. package/src/components/viewer/schedule/schedule-animator.test.ts +452 -0
  74. package/src/components/viewer/schedule/schedule-animator.ts +488 -0
  75. package/src/components/viewer/schedule/schedule-selection.test.ts +148 -0
  76. package/src/components/viewer/schedule/schedule-selection.ts +163 -0
  77. package/src/components/viewer/schedule/schedule-utils.ts +223 -0
  78. package/src/components/viewer/schedule/useConstructionSequence.ts +156 -0
  79. package/src/components/viewer/schedule/useGanttBarDrag.test.ts +90 -0
  80. package/src/components/viewer/schedule/useGanttBarDrag.ts +305 -0
  81. package/src/components/viewer/schedule/useGanttSelection3DHighlight.ts +152 -0
  82. package/src/components/viewer/schedule/useOverlayCompositor.ts +108 -0
  83. package/src/components/viewer/selectionHandlers.ts +446 -0
  84. package/src/components/viewer/tools/AddElementOverlay.tsx +540 -0
  85. package/src/components/viewer/tools/SectionCapControls.tsx +237 -0
  86. package/src/components/viewer/tools/SectionPanel.tsx +39 -18
  87. package/src/components/viewer/useAnimationLoop.ts +9 -1
  88. package/src/components/viewer/useDuplicateShortcut.ts +77 -0
  89. package/src/components/viewer/useMouseControls.ts +9 -1
  90. package/src/components/viewer/useRenderUpdates.ts +1 -1
  91. package/src/hooks/ids/idsDataAccessor.ts +60 -24
  92. package/src/hooks/ingest/viewerModelIngest.ts +7 -2
  93. package/src/hooks/useIfcFederation.ts +326 -71
  94. package/src/hooks/useIfcLoader.ts +23 -10
  95. package/src/hooks/useKeyboardShortcuts.ts +25 -0
  96. package/src/hooks/useSandbox.ts +1 -1
  97. package/src/hooks/useSearchIndex.ts +125 -0
  98. package/src/hooks/useViewControls.ts +13 -5
  99. package/src/index.css +550 -10
  100. package/src/lib/desktop-entitlement.ts +2 -4
  101. package/src/lib/geo/cesium-bridge.ts +15 -7
  102. package/src/lib/geo/effective-georef.test.ts +73 -0
  103. package/src/lib/geo/effective-georef.ts +111 -0
  104. package/src/lib/geo/reproject.ts +105 -19
  105. package/src/lib/llm/byok-guard.test.ts +77 -0
  106. package/src/lib/llm/byok-guard.ts +39 -0
  107. package/src/lib/llm/free-models.test.ts +0 -6
  108. package/src/lib/llm/models.ts +104 -42
  109. package/src/lib/llm/stream-client.ts +74 -110
  110. package/src/lib/llm/stream-direct.test.ts +130 -0
  111. package/src/lib/llm/stream-direct.ts +316 -0
  112. package/src/lib/llm/system-prompt.test.ts +14 -0
  113. package/src/lib/llm/system-prompt.ts +102 -1
  114. package/src/lib/llm/types.ts +20 -2
  115. package/src/lib/recent-files.ts +38 -4
  116. package/src/lib/scripts/templates/bim-globals.d.ts +136 -114
  117. package/src/lib/scripts/templates/construction-schedule.ts +223 -0
  118. package/src/lib/scripts/templates.ts +7 -0
  119. package/src/lib/search/common-ifc-types.ts +36 -0
  120. package/src/lib/search/filter-evaluate.test.ts +537 -0
  121. package/src/lib/search/filter-evaluate.ts +610 -0
  122. package/src/lib/search/filter-rules.test.ts +119 -0
  123. package/src/lib/search/filter-rules.ts +198 -0
  124. package/src/lib/search/filter-schema.test.ts +233 -0
  125. package/src/lib/search/filter-schema.ts +146 -0
  126. package/src/lib/search/recent-searches.test.ts +116 -0
  127. package/src/lib/search/recent-searches.ts +93 -0
  128. package/src/lib/search/result-export.test.ts +101 -0
  129. package/src/lib/search/result-export.ts +104 -0
  130. package/src/lib/search/saved-filters.test.ts +118 -0
  131. package/src/lib/search/saved-filters.ts +154 -0
  132. package/src/lib/search/tier0-scan.test.ts +196 -0
  133. package/src/lib/search/tier0-scan.ts +237 -0
  134. package/src/lib/search/tier1-index.test.ts +242 -0
  135. package/src/lib/search/tier1-index.ts +448 -0
  136. package/src/main.tsx +1 -10
  137. package/src/sdk/adapters/export-adapter.test.ts +434 -1
  138. package/src/sdk/adapters/export-adapter.ts +404 -1
  139. package/src/sdk/adapters/export-schedule-splice.test.ts +127 -0
  140. package/src/sdk/adapters/export-schedule-splice.ts +87 -0
  141. package/src/sdk/adapters/model-compat.ts +8 -2
  142. package/src/sdk/adapters/schedule-adapter.ts +73 -0
  143. package/src/sdk/adapters/store-adapter.ts +201 -0
  144. package/src/sdk/adapters/visibility-adapter.ts +3 -0
  145. package/src/sdk/local-backend.ts +16 -8
  146. package/src/services/api-keys.ts +73 -0
  147. package/src/services/desktop-export.ts +3 -1
  148. package/src/services/desktop-native-metadata.ts +41 -18
  149. package/src/services/file-dialog.ts +4 -1
  150. package/src/services/tauri-modules.d.ts +25 -0
  151. package/src/store/basketVisibleSet.ts +3 -0
  152. package/src/store/constants.ts +20 -2
  153. package/src/store/globalId.ts +4 -1
  154. package/src/store/index.ts +82 -6
  155. package/src/store/slices/addElementMeshes.ts +365 -0
  156. package/src/store/slices/addElementSlice.ts +275 -0
  157. package/src/store/slices/annotationsSlice.test.ts +133 -0
  158. package/src/store/slices/annotationsSlice.ts +251 -0
  159. package/src/store/slices/cesiumSlice.ts +5 -0
  160. package/src/store/slices/chatSlice.test.ts +6 -76
  161. package/src/store/slices/chatSlice.ts +17 -58
  162. package/src/store/slices/dataSlice.test.ts +23 -4
  163. package/src/store/slices/dataSlice.ts +1 -1
  164. package/src/store/slices/modelSlice.test.ts +67 -9
  165. package/src/store/slices/modelSlice.ts +39 -7
  166. package/src/store/slices/mutationSlice.ts +964 -3
  167. package/src/store/slices/overlayCompositor.test.ts +164 -0
  168. package/src/store/slices/overlaySlice.test.ts +93 -0
  169. package/src/store/slices/overlaySlice.ts +151 -0
  170. package/src/store/slices/pinboardSlice.test.ts +6 -1
  171. package/src/store/slices/playbackSlice.ts +128 -0
  172. package/src/store/slices/schedule-edit-helpers.test.ts +97 -0
  173. package/src/store/slices/schedule-edit-helpers.ts +179 -0
  174. package/src/store/slices/scheduleSlice.test.ts +694 -0
  175. package/src/store/slices/scheduleSlice.ts +1330 -0
  176. package/src/store/slices/searchSlice.test.ts +342 -0
  177. package/src/store/slices/searchSlice.ts +341 -0
  178. package/src/store/slices/sectionSlice.test.ts +87 -7
  179. package/src/store/slices/sectionSlice.ts +151 -5
  180. package/src/store/slices/selectionSlice.test.ts +46 -0
  181. package/src/store/slices/selectionSlice.ts +20 -0
  182. package/src/store/slices/uiSlice.ts +28 -5
  183. package/src/store/types.ts +26 -0
  184. package/src/store.ts +14 -0
  185. package/src/utils/nativeSpatialDataStore.ts +4 -1
  186. package/src/utils/viewportUtils.ts +7 -2
  187. package/src/vite-env.d.ts +0 -4
  188. package/dist/assets/drawing-2d-gWfpdfYe.js +0 -257
  189. package/dist/assets/ids-B4jTqB1O.js +0 -1
  190. package/dist/assets/ifc-lite_bg-BX4E7TX8.wasm +0 -0
  191. package/dist/assets/index-DckuDqlv.css +0 -1
  192. package/dist/assets/sandbox-DZiNLNMk.js +0 -5933
  193. package/src/components/viewer/UpgradePage.tsx +0 -71
  194. package/src/lib/desktop/ClerkDesktopEntitlementSync.tsx +0 -175
  195. package/src/lib/llm/ClerkChatSync.tsx +0 -74
  196. package/src/lib/llm/clerk-auth.ts +0 -62
package/CHANGELOG.md CHANGED
@@ -1,5 +1,635 @@
1
1
  # @ifc-lite/viewer
2
2
 
3
+ ## 1.18.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#598](https://github.com/louistrue/ifc-lite/pull/598) [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c) Thanks [@louistrue](https://github.com/louistrue)! - Add Element tool — instant 3D appearance, off-surface placement, 3D ghost preview.
8
+
9
+ Three UX-blocker fixes that turn the Add Element tool into a real
10
+ authoring surface (previously every drop emitted STEP into the overlay
11
+ but the user saw nothing in the 3D scene until export+reparse).
12
+
13
+ - **Instant 3D appearance.** Every `add*` action now also builds a
14
+ renderer-frame mesh for the new element and injects it via the
15
+ same `appendGeometryBatch` action `duplicateEntity` uses. Walls,
16
+ beams, and members are oriented thickness-extruded boxes;
17
+ columns, doors, and windows are axis-aligned boxes;
18
+ slabs / roofs / plates / spaces are polygon extrusions (with fan
19
+ triangulation good enough for typical room shapes). Storey
20
+ elevation is read from the spatial hierarchy so multi-storey
21
+ placements drop on the right floor. The new mesh is tagged with
22
+ the federation-aware globalId so picking + selection work
23
+ immediately and the property panel opens on the new entity.
24
+ - **Off-surface placement.** A new
25
+ `raycastStoreyFloor()` helper unprojects the cursor to a ray and
26
+ intersects the storey floor plane (renderer Y =
27
+ `storeyElevation`). The hover preview and click handler both
28
+ fall back to it when the scene raycast misses, so columns can
29
+ drop onto empty floor outside the existing geometry. Snap-to-
30
+ surface still wins whenever there is a mesh under the cursor.
31
+ - **3D ghost preview.** The SVG overlay now projects the about-to-
32
+ commit element's 8 corners (or polygon ring) to screen and
33
+ renders the silhouette via a convex-hull outline. Single-click
34
+ types (column / door / window) show the ghost on hover before
35
+ any clicks; two-click types (wall / beam / member) show it once
36
+ the start point is placed. The ghost reads live per-type form
37
+ params, so adjusting Width / Height / Thickness updates it in
38
+ real time.
39
+
40
+ Also includes a panel polish: when the active type is `space` an
41
+ **Auto Spaces** section appears with snap tolerance, min area,
42
+ height, naming pattern, and IfcSpaceTypeEnum settings + Preview /
43
+ Generate buttons that drive the wall-graph face finder.
44
+
45
+ - [#598](https://github.com/louistrue/ifc-lite/pull/598) [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c) Thanks [@louistrue](https://github.com/louistrue)! - Annotate-in-3D — drop pins on the scene with notes.
46
+
47
+ Press `P` (or pick the new `MapPin` button on the main toolbar),
48
+ click anywhere in the 3D scene, type a note. A pin lands at the
49
+ world point you clicked on, persists to localStorage, and re-anchors
50
+ itself as you orbit / pan. Pins are 14px amber dots with a
51
+ 1-character glyph (numbered ≤ 9, dot beyond), drop shadow, idle-pulse
52
+ on first paint (respects `prefers-reduced-motion`), emerald selection
53
+ ring matching the existing constructive accent.
54
+
55
+ Flow:
56
+
57
+ - `P` toggles the Annotate tool. Toolbar gains a `MapPin` button
58
+ with an amber active-tone, distinct from the primary blue used
59
+ for Select / Walk / Measure / Section.
60
+ - Cursor switches to crosshair while annotating.
61
+ - Click → raycast into the scene → on hit, an inline note input
62
+ drops at the click site with a guiding "What's worth noting?"
63
+ label and the entity context inline (e.g. `· IfcSlab #2036`).
64
+ Misses are silent — annotations are anchored to surface points
65
+ by design, not floating in space.
66
+ - `Enter` saves, `⇧Enter` newline, `Esc` cancels. Outside-click
67
+ saves a non-empty draft and silently cancels an empty one.
68
+ - Click an existing pin → popover with note + relative time +
69
+ pen / trash icons. Edit mode mirrors the drop-input treatment.
70
+ - Tool stays active across drops so you can drop several pins
71
+ in sequence.
72
+
73
+ Architecture:
74
+
75
+ - New `annotationsSlice` — Map-keyed store (`begin/commit/cancel
76
+ Draft`, `update`, `remove`, `select`, `clearAll`). Notes are
77
+ clamped at 2000 chars, soft-warned at 200. Persists to
78
+ `ifc-lite:annotations:v1` in localStorage and survives a fresh
79
+ slice instantiation. Covered by 9 unit tests.
80
+ - New DOM-billboard overlay (`AnnotationLayer`) sitting on top of
81
+ the WebGPU canvas. A single rAF loop re-projects every pin's
82
+ world position to screen via `cameraCallbacks.projectToScreen`,
83
+ skipping `setState` when nothing changed (so the loop is cheap
84
+ when the camera is still). Pointer-events: none on the wrapper
85
+ so empty space passes through to canvas controls; pins +
86
+ popover opt back into pointer events explicitly.
87
+ - `AnnotationPin`, `AnnotationPopover`, `AnnotationDropInput` —
88
+ composable components, all amber-accented, edge-clamped,
89
+ backdrop-blurred where it matters.
90
+
91
+ Pins are NOT IFC entities — they live alongside the model as an
92
+ authoring overlay. Future PRs will wire BCF round-trip and
93
+ IfcAnnotation export, plus an annotations-list panel and category
94
+ tags.
95
+
96
+ - [#598](https://github.com/louistrue/ifc-lite/pull/598) [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c) Thanks [@louistrue](https://github.com/louistrue)! - Auto Spaces — diagnostics, broader wall coverage, and a sweep of
97
+ review feedback.
98
+
99
+ **Auto Spaces detection.** The "no enclosed regions detected"
100
+ failure mode now surfaces actionable counts — both in devtools
101
+ and in the panel itself.
102
+
103
+ - `extract-walls.ts` now tries the standard `Axis` representation
104
+ (`IfcShapeRepresentation` with `RepresentationIdentifier='Axis'`,
105
+ `IfcPolyline` items) **before** falling back to the
106
+ `addWallToStore` rectangle-profile convention. That covers
107
+ walls authored by Revit / ArchiCAD / IfcOpenShell — the previous
108
+ extractor only handled walls placed via the Add Element tool.
109
+ The placement chain is read once and the polyline endpoints are
110
+ transformed through it, so rotated walls work.
111
+ - Every wall that gets dropped is recorded with a typed reason
112
+ (`no-axis-or-rect-profile`, `placement-not-resolvable`,
113
+ `zero-length-axis`, …) — the panel summarises them as
114
+ `"3× no-axis-or-rect-profile, 1× zero-length-axis"`.
115
+ - `detectEnclosedAreas` exposes a
116
+ `detectEnclosedAreasWithStats(...)` companion that returns
117
+ per-stage counts (vertices, edges-after-split, faces total,
118
+ outer / below-min-area drops, largest area). The intersection
119
+ splitter's iteration cap now scales with input size
120
+ (`max(100, segments * 10)`) so dense floor plans don't bail
121
+ out early.
122
+ - `generateSpacesFromWalls` always logs a `console.info`
123
+ one-liner and threads a new `debug?: boolean` flag down to the
124
+ extractor + detector for verbose tracing. The viewer's Auto
125
+ Spaces panel exposes a "Verbose console logging" checkbox.
126
+ - The Auto Spaces diagnostic block now shows the graph stats
127
+ (`123v / 456e / 78f`), the drop counts, and per-reason wall
128
+ skips. Two amber hints fire automatically when walls were
129
+ extracted but no faces formed (likely snap tolerance), or
130
+ when nothing extracted (likely an unsupported geometry shape).
131
+
132
+ **Review-feedback sweep (PR #598).**
133
+
134
+ - `addElementMeshes.linearBox()` and the SVG `linearBoxCorners`
135
+ helper honour each endpoint's Y so a sloped beam previews as
136
+ a sloped prism instead of being flattened to the start.
137
+ - `bridge-store.requireStoreyId` rejects `0` (EXPRESS ids are
138
+ 1-based, `#0` is never valid).
139
+ - `addWindow` / `addDoor` `tsParamTypes` include
140
+ `UserDefinedPartitioningType` / `UserDefinedOperationType`
141
+ so typed sandbox callers can hit the IFC4 round-trip without
142
+ casts.
143
+ - `AnnotationLayer.resolveEntityType` no longer falls back to
144
+ `ifcDataStore` when the annotation's `modelId` is missing
145
+ from a federated `models` map (would resolve the wrong
146
+ entity in multi-model sessions). Single-model sessions keep
147
+ the fallback.
148
+ - `addDoorToStore` / `addWindowToStore` validate
149
+ `OperationType` / `PartitioningType` against the IFC4 enum
150
+ and re-route unknown values through
151
+ `.USERDEFINED.` + `User-defined…Type` so custom labels
152
+ round-trip cleanly.
153
+ - `addWallToStore` defaults `PredefinedType` to `.NOTDEFINED.`
154
+ (was `.STANDARD.`) to match the rest of the in-store
155
+ builders.
156
+ - `duplicateInStore` / `resolveDuplicateSource` allow
157
+ `OwnerHistory` to be `null` (IFC4 made it optional). The
158
+ duplicate emits a bare `$` token instead of `#null` for the
159
+ omitted case.
160
+ - `StoreEditor.addEntity` accepts an injected schema-aware
161
+ normalizer (`setEntityTypeNormalizer`); `@ifc-lite/sdk`
162
+ registers `normalizeIfcTypeName` + `isKnownType` at load
163
+ time so direct callers — CLI scripts, sandbox bridge,
164
+ unit tests — see registry-grade rejection of typos like
165
+ `IfcWal`, plus canonical PascalCase on `EntityRef.type`.
166
+
167
+ - [#598](https://github.com/louistrue/ifc-lite/pull/598) [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c) Thanks [@louistrue](https://github.com/louistrue)! - Auto Spaces — generate IfcSpace volumes from a storey's walls.
168
+
169
+ Pick the **Space** type in the Add Element panel and the new **Auto
170
+ Spaces** section appears underneath the dimensions. Hit **Preview** to
171
+ see every enclosed region the wall graph forms (live SVG overlay,
172
+ labelled with area), then **Generate** to commit one IfcSpace per
173
+ region. Settings: snap tolerance (collapse sloppy wall ends), min area
174
+ (drop closets and slivers), height (extrusion), name pattern, and
175
+ IfcSpaceTypeEnum.
176
+
177
+ **`@ifc-lite/create`** — three new modules, all parser-pure:
178
+
179
+ - `auto-space-detect.ts` — planar-graph face finder. Snap →
180
+ resolve crossings → DCEL half-edge graph → leftmost-turn cycle
181
+ walk → drop unbounded faces → filter by min area. Handles
182
+ multi-component layouts (two non-touching rooms find both),
183
+ T-junctions, and snap-induced corner merges. 8 fixture tests.
184
+ - `extract-walls.ts` — pulls every wall axis on a target storey
185
+ from a parsed `IfcDataStore`. Walks
186
+ IfcRelContainedInSpatialStructure → IfcWall → placement chain →
187
+ IfcRectangleProfileDef.XDim. Optional overlay reader includes
188
+ walls created via the Add Element tool without a re-parse.
189
+ - `generate-spaces.ts` — orchestration: extract → detect → emit
190
+ via `addSpaceToStore` polygon mode. `dryRun` runs detection only.
191
+
192
+ **`@ifc-lite/viewer`** — `mutationSlice.generateSpacesFromWalls`
193
+ returns the detection result. `AddElementPanel` gains the Auto Spaces
194
+ section; `AddElementOverlay` projects detected outlines back to screen
195
+ using the storey's elevation so the preview tracks the camera in
196
+ real time.
197
+
198
+ - [#598](https://github.com/louistrue/ifc-lite/pull/598) [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c) Thanks [@louistrue](https://github.com/louistrue)! - Add the `bim.store.*` namespace — high-level editing of an already-parsed
199
+ `IfcDataStore` via the existing mutation overlay. Closes the merge-roundtrip
200
+ gap from #592 (you can edit `IfcRectangleProfileDef.XDim` or drop a fresh
201
+ `IfcColumn` into a model without round-tripping through a script + re-parse).
202
+
203
+ **`@ifc-lite/mutations`** — new `StoreEditor` facade plus four
204
+ `MutablePropertyView` extensions: positional-attribute mutations, overlay
205
+ entity creation/deletion (with watermark seeding), and three helpers used by
206
+ the viewer's undo/redo (`removePositionalMutation`, `restoreFromTombstone`,
207
+ `restoreNewEntity`).
208
+
209
+ **`@ifc-lite/create`** — new `in-store/` module: `addColumnToStore` builds a
210
+ 12-entity IfcColumn sub-graph (placement, profile, extruded solid,
211
+ representation, product shape, rel-contained-in-spatial-structure) anchored
212
+ to a target `IfcBuildingStorey`. `resolveSpatialAnchor` walks the parsed
213
+ store to find the IfcOwnerHistory, the 'Body' representation context, and
214
+ the storey's local placement.
215
+
216
+ **`@ifc-lite/sdk`** — new `StoreNamespace` exposed as `bim.store` on
217
+ `BimContext`. Methods: `addEntity`, `removeEntity`, `setPositionalAttribute`,
218
+ `addColumn`. Backed by `StoreBackendMethods` on `BimBackend`; the
219
+ `RemoteBackend` proxy round-trips them through the transport.
220
+
221
+ **`@ifc-lite/sandbox`** — `bim.store.*` is bridged into the QuickJS sandbox
222
+ with full TypeScript types via `bim-globals.d.ts` and an LLM cheat sheet in
223
+ the system prompt. Gated on a new `store: true` permission (default
224
+ `false`, mirrors the existing `mutate` permission pattern).
225
+
226
+ **`@ifc-lite/cli`** — `HeadlessBackend.store` is now functional (was a
227
+ no-op before). Scripts run via the CLI can edit a parsed model and export it
228
+ with mutations applied.
229
+
230
+ **`@ifc-lite/viewer`** — three new UI surfaces:
231
+
232
+ - Raw STEP tab in `PropertiesPanel` — lists every positional STEP argument
233
+ with an inline pen-icon editor for scalar values (numbers, refs, enums,
234
+ null). Mutated rows show a purple dot and tinted background.
235
+ - `EntityContextMenu` gains "Delete entity" (red, calls `removeEntity`
236
+ with toast + undo support) and "Add column here…" (emerald, only enabled
237
+ when the right-clicked entity is an `IfcBuildingStorey`).
238
+ - `AddColumnDialog` modal — storey picker sorted by elevation, position
239
+ (storey-local metres), cross-section, height, name, optional collapsible
240
+ for Description/ObjectType/Tag. Anchor-resolution failures surface
241
+ inline, not as thrown exceptions.
242
+
243
+ Plus four new actions on `mutationSlice` (`setPositionalAttribute`,
244
+ `removeEntity`, `addColumn`, dialog open/close) backed by per-model
245
+ `StoreEditor` caches, with undo/redo wired for `UPDATE_POSITIONAL_ATTRIBUTE`,
246
+ `CREATE_ENTITY`, and `DELETE_ENTITY`.
247
+
248
+ **`@ifc-lite/parser`** — `package.json` `exports` re-ordered to put `types`
249
+ before `import` so downstream consumers using TS5 `nodenext` resolution
250
+ pick up the type declarations.
251
+
252
+ **`@ifc-lite/geometry`** — re-exports `MetadataBootstrapEntitySummary` and
253
+ `MetadataBootstrapSpatialNode` from the package index (used by viewer
254
+ desktop services).
255
+
256
+ **`@ifc-lite/renderer`** — `GPUBufferDescriptor` ambient declaration gains
257
+ `mappedAtCreation?: boolean`. Internal change; the renderer was already
258
+ using it at runtime to skip a Mojo IPC round-trip on Chrome/Dawn.
259
+
260
+ - [#598](https://github.com/louistrue/ifc-lite/pull/598) [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c) Thanks [@louistrue](https://github.com/louistrue)! - Duplicate-from-selection — pick any IfcRoot product, hit `⌘D` (or
261
+ right-click → Duplicate), get a fully-functional clone. The
262
+ duplicate is a first-class entity in the property panel, exports
263
+ cleanly to STEP with all its property associations preserved, and
264
+ ships in 6 directional variants sized to the source's bounding box.
265
+
266
+ **`@ifc-lite/create`**
267
+
268
+ - New `duplicateInStore(editor, source, options)` pure builder.
269
+ Emits a fresh placement chain (`IfcCartesianPoint` →
270
+ `IfcAxis2Placement3D` → `IfcLocalPlacement`) plus the duplicate
271
+ `IfcRoot` with a new GUID and the source's `Representation`
272
+ reference reused (geometry shared). Optional fresh
273
+ `IfcRelContainedInSpatialStructure` anchors to the source's
274
+ storey. Offset is configurable via `options.offset` — the slice
275
+ sizes it to the source's bbox.
276
+ - New `resolveDuplicateSource(store, expressId)` walks the parsed
277
+ `IfcDataStore` for placement / parent / location / storey /
278
+ associations.
279
+ - New `SourceAssociation` shape captures one
280
+ `IfcRelDefines*` / `IfcRelAssociates*` edge that references
281
+ the source. The builder replays each one against the duplicate
282
+ so the exported STEP carries identical psets / qsets /
283
+ materials / classifications / documents / type binding —
284
+ without modifying any existing rel.
285
+ - Resolver scans the five association rel types
286
+ (`IFCRELDEFINESBYPROPERTIES`, `IFCRELDEFINESBYTYPE`,
287
+ `IFCRELASSOCIATESMATERIAL`, `…CLASSIFICATION`, `…DOCUMENT`)
288
+ by direct numeric membership in `RelatedObjects`.
289
+ - `DuplicateBuildResult.associationRelIds: number[]` exposes the
290
+ fresh rel ids for caller introspection.
291
+ - 7 unit tests in `duplicate.test.ts`: full graph emission,
292
+ custom offset, no-storey path, root-placement parent, attribute
293
+ count guard, association replay (3 rel types in one go), and
294
+ the no-associations case.
295
+
296
+ **`@ifc-lite/mutations`**
297
+
298
+ - New `setEntityAlias(overlayId, sourceId | null)` /
299
+ `getEntityAlias(id)` / `resolveBaseEntityId(id)` public surface
300
+ on `MutablePropertyView`. Aliases redirect base property and
301
+ quantity reads from the duplicate to its source — so the
302
+ duplicate inherits psets/qsets without eagerly cloning them
303
+ into the overlay.
304
+ - Override slots stay scoped to the original (overlay) id, so
305
+ edits on the duplicate don't bleed into the source. Verified
306
+ by 4 new unit tests including the source-untouched path,
307
+ chain-cap (one hop, not transitive), and the self-alias guard.
308
+
309
+ **`@ifc-lite/viewer`**
310
+
311
+ - New `duplicateEntity(modelId, sourceExpressId, direction?)`
312
+ slice action. Wraps the create-package builder, sets the
313
+ mutation-view alias, and clones the source's mesh data into
314
+ the geometry result with the offset applied — so the duplicate
315
+ appears in 3D the moment the action fires, not just in the
316
+ export overlay. Per-vertex `entityIds` arrays are filled with
317
+ the new globalId so picking and selection resolve correctly.
318
+ - New `DuplicateDirection` type (`+X` / `-X` / `+Y` / `-Y` /
319
+ `+Z` / `-Z`). Magnitude per axis = the source's bounding-box
320
+ dimension on that axis, so a 3m wall steps 3m and a 0.4m
321
+ column steps 0.4m. Falls back to a 1m step when the source
322
+ has no mesh in geometry.
323
+ - Right-click menu's "Duplicate" item is now a `DuplicateRow`:
324
+ primary clickable label on the left (defaults to +X), 6 axis
325
+ chips on the right (→ ← ↗ ↙ ↑ ↓). Tooltips spell out
326
+ "+X (east)" through "−Z (down)".
327
+ - `⌘D` defaults to +X. `⇧⌘D` = +Z (up), `⌥⌘D` = +Y (north) —
328
+ modifier shortcuts for power users without forcing a mouse
329
+ trip to the chip row. Selection moves to the new globalId so
330
+ a Cmd+D chain ("stamp a row of columns") works without
331
+ re-clicking.
332
+ - **`resolveGlobalIdFromModels` two-pass overlay fallback** —
333
+ the federation resolver previously gated each model's id range
334
+ at parse-time `maxExpressId`, which excluded every
335
+ overlay-allocated id from selection. The fix: a second pass
336
+ consults each model's mutation view via `getNewEntity(localId)`
337
+ so overlay duplicates resolve to the right model with the
338
+ right local id. Without this, the property panel saw the
339
+ duplicate as "UNKNOWN / Unknown / no property sets" because
340
+ the alias couldn't take effect on a wrongly-resolved id.
341
+ - PropertiesPanel falls back to the overlay `NewEntity` record
342
+ for type / name / GUID / Description / ObjectType when the
343
+ parsed `entityNode` comes up empty. The bSDD attribute list
344
+ synthesises from the schema-defined positional names. The
345
+ Materials / Classifications / Documents / structural
346
+ Relationships sections all route through a new
347
+ `lookupExpressId` (alias-resolved) so they query the source's
348
+ parsed maps directly.
349
+
350
+ After: a freshly-duplicated wall is genuinely first-class — name
351
+ reads, properties show, quantities show, material layers show,
352
+ classifications show, documents show, and a round-tripped STEP
353
+ file carries every association.
354
+
355
+ - [#576](https://github.com/louistrue/ifc-lite/pull/576) [`1309f8c`](https://github.com/louistrue/ifc-lite/commit/1309f8cba128b3b6237ebfb9831bf359c426a742) Thanks [@louistrue](https://github.com/louistrue)! - Add the full IfcTask / 4D construction-schedule experience to the viewer.
356
+
357
+ **Gantt panel** — a lower-panel workspace combining a task tree, a zoomable
358
+ SVG timeline with task bars / milestones / dependency arrows / playback
359
+ cursor, a toolbar (work-schedule filter, play / pause / loop / speed, time
360
+ scale), and an empty state. Live Gantt ↔ 3D selection highlight (one-way,
361
+ no isolation) and playback-driven visibility through the rendererʼs
362
+ hidden-entity channel.
363
+
364
+ **Schedule editing** — Inspector Task card (name, identification,
365
+ predefined type, milestone, start / finish / duration with any-two-of-three
366
+ reconciliation, assigned products, delete with cascade). Undo / redo
367
+ (descriptor-based lightweight snapshots for field edits; full snapshot for
368
+ structural edits), store-scoped transactions (drag-coalesced), add / delete /
369
+ reorder tasks. IFC STEP export routes through a centralised schedule splice
370
+ helper so generated / edited schedules round-trip cleanly on every export
371
+ surface.
372
+
373
+ **Generate from hierarchy** — a Generate Schedule dialog produces a work
374
+ schedule + tasks from the modelʼs spatial hierarchy (Storey / Building) or
375
+ geometry (Height-slice, with optional Class / Type / Name subgroup). Linked
376
+ FS dependencies and ghost-preparation look-ahead are opt-in.
377
+
378
+ **4D animation** — Synchro-style phased lifecycle (preparation ghost →
379
+ ramp-in → active task-type colour → settling fade → complete), demolition
380
+ inversion, customizable palette, and configurable palette intensity /
381
+ look-ahead / hide-untasked products. Animation layers live in a priority-
382
+ composited overlay registry (`registerOverlayLayer`), with a single
383
+ compositor hook owning the write to the rendererʼs hidden-entity + colour-
384
+ override channels.
385
+
386
+ **LLM integration** — built-in "Construction schedule (4D)" script template,
387
+ PDF / spreadsheet chat attachments, and `bim.schedule.*` read APIs reachable
388
+ from the sandbox.
389
+
390
+ - [#598](https://github.com/louistrue/ifc-lite/pull/598) [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c) Thanks [@louistrue](https://github.com/louistrue)! - Raw STEP tab — drill into `#N` references and a tighter dev-leaning
391
+ visual treatment.
392
+
393
+ **Reference drill-through**
394
+
395
+ - Each `#N` token in the Raw STEP card is now a clickable chip.
396
+ Click → drills into the target entity and shows its positional
397
+ arguments inline; the breadcrumb at the top of the card tracks
398
+ the path back to the 3D-selected entity.
399
+ - **Auto-skip wrappers** — when the click target itself has only
400
+ a single positional arg and that arg is also a `#N`, the card
401
+ follows the chain in one click and lands on the first
402
+ "meaningful" entity. Capped at 16 hops to defend against
403
+ cyclic STEP graphs. So a real-world case like
404
+ `IfcRelDefinesByProperties → IfcPropertySet` steps cleanly,
405
+ and pure pass-through wrappers don't waste user clicks.
406
+ - Drill state resets when the 3D selection changes — drilling
407
+ stays scoped to a single click. Each breadcrumb segment is
408
+ clickable to jump back to that depth.
409
+ - Editing a `#N` ref still works via the pen icon — clicking the
410
+ chip itself navigates instead of entering edit mode, but the
411
+ hover-revealed pen still flips to inline-edit so a user can
412
+ re-type the reference target.
413
+ - Tombstoned entities short-circuit the auto-follow so the drill
414
+ doesn't render a deleted entity's body.
415
+
416
+ **True STEP literals on display**
417
+
418
+ - Tokens are read directly from the source bytes via a new
419
+ `extractRawStepTokens` helper, so refs render as `#42`, enums
420
+ stay `.AREA.`, and strings keep their on-disk quoted form. The
421
+ EntityExtractor's parsed JS shape strips reference prefixes
422
+ (it parses `#42` into the integer `42`), so the previous
423
+ formatter had no way to recover the distinction — `OwnerHistory`
424
+ would render as `18` instead of `#18`. Fixed.
425
+ - Overlay overrides serialize back through `serializeStepToken`
426
+ for parity with the unmodified base tokens.
427
+
428
+ **Overlay-aware row display**
429
+
430
+ - Edits to positional attributes now reflect immediately in the
431
+ row body. Previously the card re-extracted from the source
432
+ buffer and ignored the overlay map, so the displayed value
433
+ snapped back to the original after Save (only the purple
434
+ overlay-override dot updated correctly).
435
+
436
+ **Dev-leaning tab styling**
437
+
438
+ - Raw STEP tab restyled — replaces the "Raw" plain-text label
439
+ with a `</>` bracket glyph, shrinks the trigger to icon-only
440
+ width via `flex: 0 0 auto`. Frees up width so Properties /
441
+ Quantities / bSDD keep their text visible at the default
442
+ panel size, and signals "developer view" with a terminal-green
443
+ accent on hover / active state.
444
+
445
+ **Add-Column UI removed**
446
+
447
+ - The original `AddColumnDialog` + context-menu "Add column
448
+ here…" + EditToolbar "Column" button — premature for the
449
+ current workflow (single hard-coded element type with no
450
+ geometry preview). Removed cleanly:
451
+ `AddColumnDialog.tsx` (deleted), the `addColumnDialog` slice
452
+ state, the constructive `MenuItem` tone (only used by that
453
+ item), and the context-menu / toolbar entry points.
454
+ - Kept: the `addColumn` slice action and the
455
+ `bim.store.addColumn` SDK surface — those still drive scripts
456
+ and programmatic flows, just no UI affordance for now.
457
+
458
+ **Tombstoned mesh actually disappears**
459
+
460
+ - Delete entity now pairs the overlay tombstone with
461
+ `hideEntity(globalId)` so the rendered mesh is hidden from the
462
+ GPU buffers (and stops being pickable). Undo of `DELETE_ENTITY`
463
+ pairs `restoreFromTombstone` with `showEntity` so the entity
464
+ returns to the scene; redo re-hides. Symmetrical round-trip.
465
+
466
+ - [#588](https://github.com/louistrue/ifc-lite/pull/588) [`b75f0cc`](https://github.com/louistrue/ifc-lite/commit/b75f0cccb06c89f5e30272d6c04f986f3b47e574) Thanks [@louistrue](https://github.com/louistrue)! - Replace the SQL tab in the advanced search modal with a clean
467
+ chip-based **Filter** tab. Storey / IFC type / Predefined type / Name /
468
+ Property / Quantity rules compose with AND/OR + IsSet/IsNotSet and
469
+ run through an in-memory evaluator that scales to 4M-entity models
470
+ via `entityIndex.byType` / `spatialHierarchy.byStorey` prefilter,
471
+ cheap-first per-entity rule ordering, and async chunked yielding
472
+ with cancel + progress. The DuckDB engine, SQL editor, schema
473
+ browser, templates, error rewriter, and saved-SQL-queries module
474
+ have been removed — Builder is the whole UI now, with a single Run
475
+ button and CSV/JSON export. Builder dropdowns are schema-aware
476
+ (storeys + IFC types load eagerly, pset / qto names load lazily on
477
+ first use), the inline search-bar query promotes to a Name rule
478
+ with one click, multi-model row clicks route to the correct model,
479
+ and saved presets persist named `{name, combinator, rules}`
480
+ snapshots in localStorage.
481
+
482
+ ### Patch Changes
483
+
484
+ - [#588](https://github.com/louistrue/ifc-lite/pull/588) [`b75f0cc`](https://github.com/louistrue/ifc-lite/commit/b75f0cccb06c89f5e30272d6c04f986f3b47e574) Thanks [@louistrue](https://github.com/louistrue)! - Address PR #588 review feedback that survived the Filter migration:
485
+
486
+ - Inline-bar Enter now flushes the 80ms debounce by re-scanning against
487
+ the live `searchQuery`, so committing inside the debounce window
488
+ selects the entity matching what the input shows (not the prior
489
+ query) and records the correct recent.
490
+ - The 50ms `frameSelection` timer in the inline bar is tracked via a
491
+ ref and cleared on rapid selection changes / unmount instead of
492
+ leaking orphan callbacks.
493
+ - Shift+Enter additive selection in the inline bar and the row-level
494
+ additive path in the Search modal now TOGGLE via `toggleEntitySelection`,
495
+ so the same interaction can deselect a previously-added row.
496
+ - New `addEntitiesToSelection` batch action on the selection slice;
497
+ the Search modal's "Select all" path uses it so a 5K-row select-all
498
+ dispatches one Zustand `set` instead of N.
499
+ - Tier-0 scoring now keeps the max across name/type/objectType/description
500
+ fields (matching Tier-1's behaviour). Without this, an entity with a
501
+ substring name hit and a type-exact hit ranked lower than it should
502
+ on Tier-0, breaking the comparable-ordering guarantee when results
503
+ came from a mix of Tier-0 and Tier-1 models.
504
+
505
+ - Updated dependencies [[`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c), [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c), [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c), [`1309f8c`](https://github.com/louistrue/ifc-lite/commit/1309f8cba128b3b6237ebfb9831bf359c426a742), [`1309f8c`](https://github.com/louistrue/ifc-lite/commit/1309f8cba128b3b6237ebfb9831bf359c426a742), [`1309f8c`](https://github.com/louistrue/ifc-lite/commit/1309f8cba128b3b6237ebfb9831bf359c426a742), [`1309f8c`](https://github.com/louistrue/ifc-lite/commit/1309f8cba128b3b6237ebfb9831bf359c426a742), [`16d7a63`](https://github.com/louistrue/ifc-lite/commit/16d7a6361a78bb39a2bd61bba6990db5d3df0c04), [`945bb30`](https://github.com/louistrue/ifc-lite/commit/945bb30061ca044f4a51001f7299c17350ce99cf), [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c), [`370e084`](https://github.com/louistrue/ifc-lite/commit/370e084e94e8fce930bddf948344c4b639d196f3), [`18c6a37`](https://github.com/louistrue/ifc-lite/commit/18c6a37f1cc1426daa32ee60457dd0580a5257f5)]:
506
+ - @ifc-lite/mutations@1.15.0
507
+ - @ifc-lite/sdk@1.15.0
508
+ - @ifc-lite/sandbox@1.15.0
509
+ - @ifc-lite/parser@2.2.0
510
+ - @ifc-lite/geometry@1.16.6
511
+ - @ifc-lite/renderer@1.17.0
512
+ - @ifc-lite/query@1.14.7
513
+ - @ifc-lite/wasm@1.16.7
514
+ - @ifc-lite/export@1.18.0
515
+
516
+ ## 1.17.6
517
+
518
+ ### Patch Changes
519
+
520
+ - [#563](https://github.com/louistrue/ifc-lite/pull/563) [`7a6eb5e`](https://github.com/louistrue/ifc-lite/commit/7a6eb5e249a00a61d4e7b5574e017c949b083966) Thanks [@louistrue](https://github.com/louistrue)! - Rotate mesh normals alongside positions when aligning federated models and honour georef mutations during alignment, so secondary models keep correct shading and stay aligned when their georeferencing is edited after load.
521
+
522
+ - [#563](https://github.com/louistrue/ifc-lite/pull/563) [`7a6eb5e`](https://github.com/louistrue/ifc-lite/commit/7a6eb5e249a00a61d4e7b5574e017c949b083966) Thanks [@louistrue](https://github.com/louistrue)! - Extract LLM stream routing into a shared helper and handle Codex's truncation marker so long responses are no longer cut off mid-sentence. BYOK guard logic moves into its own module with unit tests covering the direct-stream path.
523
+
524
+ - Updated dependencies [[`7a6eb5e`](https://github.com/louistrue/ifc-lite/commit/7a6eb5e249a00a61d4e7b5574e017c949b083966), [`7a6eb5e`](https://github.com/louistrue/ifc-lite/commit/7a6eb5e249a00a61d4e7b5574e017c949b083966)]:
525
+ - @ifc-lite/wasm@1.16.6
526
+
527
+ ## 1.17.5
528
+
529
+ ### Patch Changes
530
+
531
+ - [#561](https://github.com/louistrue/ifc-lite/pull/561) [`8f4df0e`](https://github.com/louistrue/ifc-lite/commit/8f4df0e50e22419353829114b5af80cfd5d45805) Thanks [@louistrue](https://github.com/louistrue)! - 3D section cap with screen-space hatches, driven by exact cut polygons.
532
+
533
+ ### `@ifc-lite/renderer`
534
+
535
+ - **3D cut surface (cap) rendering.** `Section2DOverlayRenderer` gained
536
+ a fill pipeline that paints the user's cap style on top of the exact
537
+ polygons `SectionCutter` produces from triangle-plane intersection.
538
+ Eight built-in screen-space hatch patterns are supplied via the new
539
+ `section-cap-style.ts` module: `solid`, `diagonal`, `crossHatch`,
540
+ `horizontal`, `vertical`, `concrete` (clean dot grid, ISO 128-50),
541
+ `brick`, `insulation`. Pattern ids match the numeric branches in the
542
+ fill fragment shader and are pinned by unit tests so changes can't
543
+ drift silently. New `Section2DOverlayCapStyle` shape carries fill,
544
+ stroke, pattern id, spacing/angle/width, and a secondary cross-hatch
545
+ angle.
546
+ - **Outline + fill toggle independently.** `Section2DOverlayOptions`
547
+ has new `showFills` and `showOutlines` booleans, both honoured by
548
+ `Section2DOverlayRenderer.draw()`, so callers can hide the cut hatch
549
+ without losing the line drawing or vice versa.
550
+ - **Cap respects model depth.** Both fill and outline pipelines test
551
+ with `depthCompare: 'greater-equal'` (reverse-Z) and don't write
552
+ depth, so when the camera looks through closer model geometry the
553
+ cap is occluded naturally. Cap polygons live exactly on the plane,
554
+ so equal-depth ties tie cleanly with greater-equal.
555
+ - **Cap fill landed exactly on the plane.** Removed the old 0.3 m
556
+ vertical bias that made the hatch visibly drift off the slider
557
+ position; the fill now sits on the cut surface itself.
558
+ - **Depth format unified at `depth24plus-stencil8`.** Main, instanced,
559
+ section-plane preview, and 2D overlay pipelines all declare the same
560
+ depth/stencil format and route through `PIPELINE_CONSTANTS.DEPTH_FORMAT`
561
+ so the literal lives in exactly one place. All in-pass pipelines also
562
+ declare both colour attachments (main colour + objectId, the latter
563
+ with `writeMask: 0`) so WebGPU validation passes regardless of which
564
+ shaders render inside the section render pass.
565
+ - **`flipped` flag plumbed end-to-end.** Main and instanced fragment
566
+ shaders pack `enabled` (bit 0) + `flipped` (bit 1) into one flag slot
567
+ and negate the keep side when flipped — slider position stays where
568
+ it is, only the kept half swaps.
569
+ - **`SectionCapStyle`, `HatchPatternId`, `DEFAULT_CAP_STYLE`, and
570
+ `HATCH_PATTERN_IDS` exported from the package** as the canonical
571
+ styling primitives consumed by the viewer store and the fill shader.
572
+ - **Renderer log on first section enable** (`[Section] Y-up bounds
573
+ used for clip: …`) so a user can verify the slider range matches
574
+ their geometry without opening a debugger.
575
+
576
+ ### `@ifc-lite/drawing-2d`
577
+
578
+ - **Plane equation no longer changes when `flipped`.** Both
579
+ `SectionCutter` and `gpu-section-cutter` now build the plane normal
580
+ from `getAxisNormal(axis, false)` regardless of the flipped flag.
581
+ Previously the flipped normal was paired with an unchanged
582
+ `planeDistance`, which described a different plane (`y = -position`
583
+ instead of `y = position`) — the cutter then looked for intersections
584
+ far outside the model and produced an empty 2D drawing. `flipped` is
585
+ still honoured by `projectTo2D` so the resulting drawing mirrors
586
+ correctly when viewed from the opposite side.
587
+
588
+ ### `viewer`
589
+
590
+ - **`SectionCapControls` panel.** New compact controls inside the
591
+ expanded Section panel: independent Display toggles for _Surfaces_
592
+ (cap fill) and _Lines_ (outline), hatch pattern dropdown, fill +
593
+ stroke colour pickers, and Spacing / Angle / Width number inputs in
594
+ a 3-col grid. The hatch fieldset disables itself when Surfaces are
595
+ off so users can't tweak settings that don't apply. Every control
596
+ has an explicit `id`/`htmlFor` association via `useId()` for
597
+ assistive tech.
598
+ - **Flip button reflects state.** Now toggles `variant` to `default`,
599
+ carries `aria-pressed`, and swaps `aria-label`/`title` between
600
+ "Flip cut direction" and "Unflip cut direction".
601
+ - **Auto-enable on slider/axis change.** Moving the position slider or
602
+ picking a direction now sets `enabled: true` so users no longer get
603
+ stuck in a no-op "preview mode" wondering why nothing cuts. The
604
+ bottom toggle relabelled "Clip on/off" instead of the old
605
+ "Cutting/Preview" wording that read as if the cut was always live.
606
+ - **2D panel auto-fits on Flip.** `useViewControls` now triggers
607
+ `fitToView` on `sectionPlane.flipped` change as well as axis change,
608
+ so flipping doesn't park the polygons off-screen and leave the
609
+ panel blank.
610
+ - **Cap style persists across reloads.** `showCap`, `showOutlines`,
611
+ and the full `capStyle` (fill, stroke, pattern, spacing, angle,
612
+ width, secondary angle) round-trip to `localStorage` under the keys
613
+ `ifc-lite:section-cap-show`, `ifc-lite:section-outlines-show`, and
614
+ `ifc-lite:section-cap-style`. `resetSectionPlane()` clears them so
615
+ the default button actually resets. `resetViewerState()` (called on
616
+ every IFC load) preserves persisted cap settings and only clears
617
+ axis/position/enabled/flipped — so opening a new file no longer
618
+ wipes the user's hatch and colour choices.
619
+ - **Cap style types deduplicated.** `SectionCapHatchId` and
620
+ `SectionCapStyle` in the viewer store are now re-exports of the
621
+ renderer's `section-cap-style.ts`, so adding a new pattern only
622
+ requires editing the renderer.
623
+ - **localStorage failures are diagnosable.** Every persistence catch
624
+ in `sectionSlice` now logs via `console.warn` instead of a bare
625
+ `catch {}` — quota / private-mode / serialisation failures still
626
+ fall back gracefully but show up in devtools.
627
+
628
+ - Updated dependencies [[`8f4df0e`](https://github.com/louistrue/ifc-lite/commit/8f4df0e50e22419353829114b5af80cfd5d45805), [`7000011`](https://github.com/louistrue/ifc-lite/commit/7000011d6eb372c2dadf7c82f6e76a0583c6abc1)]:
629
+ - @ifc-lite/renderer@1.16.0
630
+ - @ifc-lite/drawing-2d@1.15.3
631
+ - @ifc-lite/wasm@1.16.5
632
+
3
633
  ## 1.17.4
4
634
 
5
635
  ### Patch Changes
@@ -1 +1 @@
1
- 1
1
+ 2
@@ -1 +1 @@
1
- import{u as o}from"./index-Co8E2-FE.js";import"./cesium-DUOzBlqv.js";import"./arrow-CZ5kQ26f.js";import"./exporters-ChAtBmlj.js";import"./bcf-DOG9_WPX.js";import"./zip-DBEtpeu6.js";import"./drawing-2d-gWfpdfYe.js";import"./lens-CSASnhAL.js";import"./sandbox-DZiNLNMk.js";import"./server-client-BV8zHZ7Y.js";import"./ids-B4jTqB1O.js";const n=700;function v(s){const e=o.getState(),i=e.basketViews.find(t=>t.id===s);if(i){if(e.setDrawing2D(null),e.setDrawing2DPanelVisible(!1),e.updateDrawing2DDisplayOptions({show3DOverlay:!1}),e.clearEntitySelection(),e.restoreBasketEntities(i.entityRefs,s),i.viewpoint){const t=i.transitionMs??n;e.cameraCallbacks.applyViewpoint?.(i.viewpoint,!0,t)}if(i.section){const t=i.section;o.setState({sectionPlane:{...t.plane},drawing2DPanelVisible:!1}),t.plane.enabled?(e.activeTool!=="section"&&e.setSuppressNextSection2DPanelAutoOpen(!0),e.setActiveTool("section")):e.activeTool==="section"&&e.setActiveTool("select")}else{const t=o.getState().sectionPlane;o.setState({sectionPlane:{...t,enabled:!1}}),e.activeTool==="section"&&e.setActiveTool("select")}}}export{v as activateBasketViewFromStore};
1
+ import{u as o}from"./index-BKq-M3Mk.js";import"./cesium-DUOzBlqv.js";import"./arrow-CZ5kQ26f.js";import"./exporters-B_OBqIyD.js";import"./bcf-DOG9_WPX.js";import"./zip-DBEtpeu6.js";import"./sandbox-jez21HtV.js";import"./lens-CSASnhAL.js";import"./drawing-2d-DoxKMqbO.js";import"./server-client-ncOQVNso.js";import"./ids-DQ5jY0E8.js";const n=700;function v(s){const e=o.getState(),i=e.basketViews.find(t=>t.id===s);if(i){if(e.setDrawing2D(null),e.setDrawing2DPanelVisible(!1),e.updateDrawing2DDisplayOptions({show3DOverlay:!1}),e.clearEntitySelection(),e.restoreBasketEntities(i.entityRefs,s),i.viewpoint){const t=i.transitionMs??n;e.cameraCallbacks.applyViewpoint?.(i.viewpoint,!0,t)}if(i.section){const t=i.section;o.setState({sectionPlane:{...t.plane},drawing2DPanelVisible:!1}),t.plane.enabled?(e.activeTool!=="section"&&e.setSuppressNextSection2DPanelAutoOpen(!0),e.setActiveTool("section")):e.activeTool==="section"&&e.setActiveTool("select")}else{const t=o.getState().sectionPlane;o.setState({sectionPlane:{...t,enabled:!1}}),e.activeTool==="section"&&e.setActiveTool("select")}}}export{v as activateBasketViewFromStore};