@ifc-lite/viewer 1.1.6 → 1.5.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 (164) hide show
  1. package/LICENSE +373 -0
  2. package/dist/apple-touch-icon.png +0 -0
  3. package/dist/assets/Arrow.dom-B0e15b_b.js +20 -0
  4. package/dist/assets/arrow2-bb-jcVEo.js +2 -0
  5. package/dist/assets/arrow2_bg-4Y7xYo54.wasm +0 -0
  6. package/dist/assets/arrow2_bg-BlXl-cSQ.js +1 -0
  7. package/dist/assets/arrow2_bg-BoXCojjR.wasm +0 -0
  8. package/dist/assets/desktop-cache-oPzaWXYE.js +1 -0
  9. package/dist/assets/event-DIOks52T.js +1 -0
  10. package/dist/assets/ifc-cache-BAN4vcd4.js +1 -0
  11. package/dist/assets/ifc-lite_bg-C6kblxf9.wasm +0 -0
  12. package/dist/assets/index-Dgd6vzw_.js +65252 -0
  13. package/dist/assets/index-v3mcCUPN.css +1 -0
  14. package/dist/assets/native-bridge-Ci7NLjlZ.js +111 -0
  15. package/dist/assets/wasm-bridge-Dc82YpdZ.js +1 -0
  16. package/dist/favicon-16x16-cropped.png +0 -0
  17. package/dist/favicon-16x16.png +0 -0
  18. package/dist/favicon-192x192-cropped.png +0 -0
  19. package/dist/favicon-192x192.png +0 -0
  20. package/dist/favicon-32x32-cropped.png +0 -0
  21. package/dist/favicon-32x32.png +0 -0
  22. package/dist/favicon-48x48-cropped.png +0 -0
  23. package/dist/favicon-48x48.png +0 -0
  24. package/dist/favicon-512x512-cropped.png +0 -0
  25. package/dist/favicon-512x512.png +0 -0
  26. package/dist/favicon-64x64-cropped.png +0 -0
  27. package/dist/favicon-64x64.png +0 -0
  28. package/dist/favicon-96x96-cropped.png +0 -0
  29. package/dist/favicon-96x96.png +0 -0
  30. package/dist/favicon-square-512.png +0 -0
  31. package/dist/favicon.ico +0 -0
  32. package/dist/favicon.png +0 -0
  33. package/dist/favicon.svg +3 -0
  34. package/dist/index.html +44 -0
  35. package/dist/logo.png +0 -0
  36. package/dist/manifest.json +48 -0
  37. package/index.html +33 -2
  38. package/package.json +34 -17
  39. package/public/apple-touch-icon.png +0 -0
  40. package/public/favicon-16x16-cropped.png +0 -0
  41. package/public/favicon-16x16.png +0 -0
  42. package/public/favicon-192x192-cropped.png +0 -0
  43. package/public/favicon-192x192.png +0 -0
  44. package/public/favicon-32x32-cropped.png +0 -0
  45. package/public/favicon-32x32.png +0 -0
  46. package/public/favicon-48x48-cropped.png +0 -0
  47. package/public/favicon-48x48.png +0 -0
  48. package/public/favicon-512x512-cropped.png +0 -0
  49. package/public/favicon-512x512.png +0 -0
  50. package/public/favicon-64x64-cropped.png +0 -0
  51. package/public/favicon-64x64.png +0 -0
  52. package/public/favicon-96x96-cropped.png +0 -0
  53. package/public/favicon-96x96.png +0 -0
  54. package/public/favicon-square-512.png +0 -0
  55. package/public/favicon.ico +0 -0
  56. package/public/favicon.png +0 -0
  57. package/public/favicon.svg +3 -0
  58. package/public/logo.png +0 -0
  59. package/public/manifest.json +48 -0
  60. package/src/App.tsx +2 -0
  61. package/src/components/ui/alert.tsx +62 -0
  62. package/src/components/ui/badge.tsx +39 -0
  63. package/src/components/ui/dialog.tsx +120 -0
  64. package/src/components/ui/label.tsx +27 -0
  65. package/src/components/ui/select.tsx +151 -0
  66. package/src/components/ui/switch.tsx +30 -0
  67. package/src/components/ui/table.tsx +120 -0
  68. package/src/components/ui/tabs.tsx +1 -1
  69. package/src/components/viewer/BCFPanel.tsx +1164 -0
  70. package/src/components/viewer/BulkPropertyEditor.tsx +875 -0
  71. package/src/components/viewer/DataConnector.tsx +840 -0
  72. package/src/components/viewer/DrawingSettingsPanel.tsx +536 -0
  73. package/src/components/viewer/EntityContextMenu.tsx +45 -17
  74. package/src/components/viewer/ExportChangesButton.tsx +195 -0
  75. package/src/components/viewer/ExportDialog.tsx +402 -0
  76. package/src/components/viewer/HierarchyPanel.tsx +1132 -218
  77. package/src/components/viewer/IDSPanel.tsx +661 -0
  78. package/src/components/viewer/KeyboardShortcutsDialog.tsx +245 -39
  79. package/src/components/viewer/MainToolbar.tsx +418 -94
  80. package/src/components/viewer/PropertiesPanel.tsx +1355 -91
  81. package/src/components/viewer/PropertyEditor.tsx +611 -0
  82. package/src/components/viewer/Section2DPanel.tsx +3313 -0
  83. package/src/components/viewer/SheetSetupPanel.tsx +502 -0
  84. package/src/components/viewer/StatusBar.tsx +27 -16
  85. package/src/components/viewer/TitleBlockEditor.tsx +437 -0
  86. package/src/components/viewer/ToolOverlays.tsx +935 -127
  87. package/src/components/viewer/ViewerLayout.tsx +40 -11
  88. package/src/components/viewer/Viewport.tsx +1276 -336
  89. package/src/components/viewer/ViewportContainer.tsx +554 -18
  90. package/src/components/viewer/ViewportOverlays.tsx +24 -7
  91. package/src/hooks/useBCF.ts +504 -0
  92. package/src/hooks/useIDS.ts +1065 -0
  93. package/src/hooks/useIfc.ts +1534 -205
  94. package/src/hooks/useIfcCache.ts +279 -0
  95. package/src/hooks/useKeyboardShortcuts.ts +50 -8
  96. package/src/hooks/useModelSelection.ts +61 -0
  97. package/src/hooks/useViewerSelectors.ts +218 -0
  98. package/src/hooks/useWebGPU.ts +80 -0
  99. package/src/index.css +265 -27
  100. package/src/lib/platform.ts +23 -0
  101. package/src/services/cacheService.ts +142 -0
  102. package/src/services/desktop-cache.ts +143 -0
  103. package/src/services/fs-cache.ts +212 -0
  104. package/src/services/ifc-cache.ts +14 -6
  105. package/src/store/constants.ts +85 -0
  106. package/src/store/index.ts +214 -0
  107. package/src/store/slices/bcfSlice.ts +372 -0
  108. package/src/store/slices/cameraSlice.ts +63 -0
  109. package/src/store/slices/dataSlice.test.ts +226 -0
  110. package/src/store/slices/dataSlice.ts +112 -0
  111. package/src/store/slices/drawing2DSlice.ts +340 -0
  112. package/src/store/slices/hoverSlice.ts +40 -0
  113. package/src/store/slices/idsSlice.ts +310 -0
  114. package/src/store/slices/loadingSlice.ts +33 -0
  115. package/src/store/slices/measurementSlice.test.ts +217 -0
  116. package/src/store/slices/measurementSlice.ts +293 -0
  117. package/src/store/slices/modelSlice.test.ts +271 -0
  118. package/src/store/slices/modelSlice.ts +211 -0
  119. package/src/store/slices/mutationSlice.ts +502 -0
  120. package/src/store/slices/sectionSlice.test.ts +125 -0
  121. package/src/store/slices/sectionSlice.ts +58 -0
  122. package/src/store/slices/selectionSlice.test.ts +286 -0
  123. package/src/store/slices/selectionSlice.ts +263 -0
  124. package/src/store/slices/sheetSlice.ts +565 -0
  125. package/src/store/slices/uiSlice.ts +58 -0
  126. package/src/store/slices/visibilitySlice.test.ts +304 -0
  127. package/src/store/slices/visibilitySlice.ts +277 -0
  128. package/src/store/types.test.ts +135 -0
  129. package/src/store/types.ts +248 -0
  130. package/src/store.ts +40 -515
  131. package/src/utils/ifcConfig.ts +82 -0
  132. package/src/utils/localParsingUtils.ts +287 -0
  133. package/src/utils/serverDataModel.ts +783 -0
  134. package/src/utils/spatialHierarchy.ts +283 -0
  135. package/src/utils/viewportUtils.ts +334 -0
  136. package/src/vite-env.d.ts +23 -0
  137. package/src/webgpu-types.d.ts +128 -0
  138. package/src-tauri/Cargo.toml +29 -0
  139. package/src-tauri/build.rs +7 -0
  140. package/src-tauri/capabilities/default.json +18 -0
  141. package/src-tauri/icons/128x128.png +0 -0
  142. package/src-tauri/icons/128x128@2x.png +0 -0
  143. package/src-tauri/icons/32x32.png +0 -0
  144. package/src-tauri/icons/Square107x107Logo.png +0 -0
  145. package/src-tauri/icons/Square142x142Logo.png +0 -0
  146. package/src-tauri/icons/Square150x150Logo.png +0 -0
  147. package/src-tauri/icons/Square284x284Logo.png +0 -0
  148. package/src-tauri/icons/Square30x30Logo.png +0 -0
  149. package/src-tauri/icons/Square310x310Logo.png +0 -0
  150. package/src-tauri/icons/Square44x44Logo.png +0 -0
  151. package/src-tauri/icons/Square71x71Logo.png +0 -0
  152. package/src-tauri/icons/Square89x89Logo.png +0 -0
  153. package/src-tauri/icons/StoreLogo.png +0 -0
  154. package/src-tauri/icons/icon.icns +0 -0
  155. package/src-tauri/icons/icon.ico +0 -0
  156. package/src-tauri/icons/icon.png +0 -0
  157. package/src-tauri/src/lib.rs +21 -0
  158. package/src-tauri/src/main.rs +10 -0
  159. package/src-tauri/tauri.conf.json +39 -0
  160. package/vite.config.ts +174 -26
  161. package/public/ifc-lite_bg.wasm +0 -0
  162. package/public/web-ifc.wasm +0 -0
  163. package/src/components/Viewport.tsx +0 -723
  164. package/src/components/viewer/BoxSelectionOverlay.tsx +0 -53
@@ -0,0 +1,263 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+
5
+ /**
6
+ * Selection state slice
7
+ *
8
+ * Supports both single-model (legacy) and multi-model selection.
9
+ * Multi-model selection uses compound EntityRef identifiers.
10
+ */
11
+
12
+ import type { StateCreator } from 'zustand';
13
+ import type { EntityRef } from '../types.js';
14
+ import { entityRefToString, stringToEntityRef } from '../types.js';
15
+
16
+ export interface SelectionSlice {
17
+ // State (legacy - single model)
18
+ selectedEntityId: number | null;
19
+ selectedEntityIds: Set<number>;
20
+ selectedStoreys: Set<number>;
21
+
22
+ // State (multi-model)
23
+ /** Primary selected entity with model context */
24
+ selectedEntity: EntityRef | null;
25
+ /** Multi-selection across models: serialized EntityRef strings */
26
+ selectedEntitiesSet: Set<string>;
27
+ /** Array of selected entities for property panel display (e.g., unified storeys) */
28
+ selectedEntities: EntityRef[];
29
+ /** Selected model ID for metadata display (when clicking top-level model in hierarchy) */
30
+ selectedModelId: string | null;
31
+
32
+ // Actions (legacy - single model, maintained for backward compatibility)
33
+ setSelectedEntityId: (id: number | null) => void;
34
+ toggleStoreySelection: (id: number) => void;
35
+ setStoreySelection: (id: number) => void;
36
+ setStoreysSelection: (ids: number[]) => void;
37
+ clearStoreySelection: () => void;
38
+ addToSelection: (id: number) => void;
39
+ removeFromSelection: (id: number) => void;
40
+ toggleSelection: (id: number) => void;
41
+ setSelectedEntityIds: (ids: number[]) => void;
42
+ clearSelection: () => void;
43
+
44
+ // Actions (multi-model)
45
+ /** Set primary selection with model context */
46
+ setSelectedEntity: (ref: EntityRef | null) => void;
47
+ /** Add entity to multi-selection */
48
+ addEntityToSelection: (ref: EntityRef) => void;
49
+ /** Remove entity from multi-selection */
50
+ removeEntityFromSelection: (ref: EntityRef) => void;
51
+ /** Toggle entity in multi-selection */
52
+ toggleEntitySelection: (ref: EntityRef) => void;
53
+ /** Clear all entity selection (both single and multi) */
54
+ clearEntitySelection: () => void;
55
+ /** Check if entity is selected */
56
+ isEntitySelected: (ref: EntityRef) => boolean;
57
+ /** Get all selected entities for a specific model */
58
+ getSelectedEntitiesForModel: (modelId: string) => number[];
59
+ /** Set multiple entities for property panel display (e.g., unified storeys) */
60
+ setSelectedEntities: (refs: EntityRef[]) => void;
61
+ /** Set selected model for metadata display */
62
+ setSelectedModelId: (modelId: string | null) => void;
63
+ }
64
+
65
+ export const createSelectionSlice: StateCreator<SelectionSlice, [], [], SelectionSlice> = (set, get) => ({
66
+ // Initial state (legacy)
67
+ selectedEntityId: null,
68
+ selectedEntityIds: new Set(),
69
+ selectedStoreys: new Set(),
70
+
71
+ // Initial state (multi-model)
72
+ selectedEntity: null,
73
+ selectedEntitiesSet: new Set(),
74
+ selectedEntities: [],
75
+ selectedModelId: null,
76
+
77
+ // Actions (legacy - maintained for backward compatibility)
78
+ setSelectedEntityId: (selectedEntityId) => set((state) => ({
79
+ selectedEntityId,
80
+ // Clear model selection when an entity is selected (but not when clearing selection)
81
+ selectedModelId: selectedEntityId !== null ? null : state.selectedModelId,
82
+ })),
83
+
84
+ toggleStoreySelection: (id) => set((state) => {
85
+ const newSelection = new Set(state.selectedStoreys);
86
+ if (newSelection.has(id)) {
87
+ newSelection.delete(id);
88
+ } else {
89
+ newSelection.add(id);
90
+ }
91
+ return { selectedStoreys: newSelection };
92
+ }),
93
+
94
+ setStoreySelection: (id) => set((state) => {
95
+ // If already the only selected storey, deselect it (toggle behavior)
96
+ if (state.selectedStoreys.size === 1 && state.selectedStoreys.has(id)) {
97
+ return { selectedStoreys: new Set() };
98
+ }
99
+ // Otherwise, select only this storey
100
+ return { selectedStoreys: new Set([id]) };
101
+ }),
102
+
103
+ setStoreysSelection: (ids) => set({ selectedStoreys: new Set(ids) }),
104
+
105
+ clearStoreySelection: () => set({ selectedStoreys: new Set() }),
106
+
107
+ addToSelection: (id) => set((state) => {
108
+ const newSelection = new Set(state.selectedEntityIds);
109
+ newSelection.add(id);
110
+ return { selectedEntityIds: newSelection, selectedEntityId: id };
111
+ }),
112
+
113
+ removeFromSelection: (id) => set((state) => {
114
+ const newSelection = new Set(state.selectedEntityIds);
115
+ newSelection.delete(id);
116
+ const remaining = Array.from(newSelection);
117
+ return {
118
+ selectedEntityIds: newSelection,
119
+ selectedEntityId: remaining.length > 0 ? remaining[remaining.length - 1] : null,
120
+ };
121
+ }),
122
+
123
+ toggleSelection: (id) => set((state) => {
124
+ const newSelection = new Set(state.selectedEntityIds);
125
+ if (newSelection.has(id)) {
126
+ newSelection.delete(id);
127
+ } else {
128
+ newSelection.add(id);
129
+ }
130
+ const remaining = Array.from(newSelection);
131
+ return {
132
+ selectedEntityIds: newSelection,
133
+ selectedEntityId: remaining.length > 0 ? remaining[remaining.length - 1] : null,
134
+ };
135
+ }),
136
+
137
+ setSelectedEntityIds: (ids) => set({
138
+ selectedEntityIds: new Set(ids),
139
+ selectedEntityId: ids.length > 0 ? ids[ids.length - 1] : null,
140
+ }),
141
+
142
+ clearSelection: () => set({
143
+ selectedEntityIds: new Set(),
144
+ selectedEntityId: null,
145
+ }),
146
+
147
+ // Actions (multi-model)
148
+ // NOTE: This ONLY sets selectedEntity, NOT selectedEntityId.
149
+ // In multi-model mode, selectedEntityId is the GLOBAL ID (for renderer highlighting)
150
+ // and selectedEntity.expressId is the ORIGINAL express ID (for property lookup).
151
+ // The caller should use setSelectedEntityId(globalId) separately for highlighting.
152
+ setSelectedEntity: (ref) => set({
153
+ selectedEntity: ref,
154
+ selectedEntities: [], // Clear multi-entity selection when setting single entity
155
+ // NOTE: Don't clear selectedModelId here - it's cleared by setSelectedEntityId
156
+ // when an entity is actually selected. This prevents race conditions with
157
+ // useModelSelection which calls setSelectedEntity when selectedEntityId changes.
158
+ // DO NOT update selectedEntityId here - it would overwrite the globalId with expressId!
159
+ // The renderer needs the globalId in selectedEntityId for highlighting.
160
+ }),
161
+
162
+ addEntityToSelection: (ref) => set((state) => {
163
+ const key = entityRefToString(ref);
164
+ const newSet = new Set(state.selectedEntitiesSet);
165
+ newSet.add(key);
166
+ return {
167
+ selectedEntitiesSet: newSet,
168
+ selectedEntity: ref,
169
+ // NOTE: Don't update selectedEntityId here - caller should use setSelectedEntityId(globalId)
170
+ };
171
+ }),
172
+
173
+ removeEntityFromSelection: (ref) => set((state) => {
174
+ const key = entityRefToString(ref);
175
+ const newSet = new Set(state.selectedEntitiesSet);
176
+ newSet.delete(key);
177
+
178
+ // Update primary selection if needed
179
+ let newPrimary: EntityRef | null = state.selectedEntity;
180
+ if (state.selectedEntity?.modelId === ref.modelId && state.selectedEntity?.expressId === ref.expressId) {
181
+ // Primary was removed, pick another if available
182
+ const remaining = Array.from(newSet);
183
+ newPrimary = remaining.length > 0 ? stringToEntityRef(remaining[remaining.length - 1]) : null;
184
+ }
185
+
186
+ return {
187
+ selectedEntitiesSet: newSet,
188
+ selectedEntity: newPrimary,
189
+ // NOTE: Don't update selectedEntityId here - caller should manage it separately
190
+ // Clear it only if nothing is selected
191
+ selectedEntityId: newPrimary ? state.selectedEntityId : null,
192
+ };
193
+ }),
194
+
195
+ toggleEntitySelection: (ref) => set((state) => {
196
+ const key = entityRefToString(ref);
197
+ const newSet = new Set(state.selectedEntitiesSet);
198
+
199
+ if (newSet.has(key)) {
200
+ newSet.delete(key);
201
+ // Update primary if this was it
202
+ let newPrimary: EntityRef | null = state.selectedEntity;
203
+ if (state.selectedEntity?.modelId === ref.modelId && state.selectedEntity?.expressId === ref.expressId) {
204
+ const remaining = Array.from(newSet);
205
+ newPrimary = remaining.length > 0 ? stringToEntityRef(remaining[remaining.length - 1]) : null;
206
+ }
207
+ return {
208
+ selectedEntitiesSet: newSet,
209
+ selectedEntity: newPrimary,
210
+ // NOTE: Don't update selectedEntityId here - caller should manage it separately
211
+ selectedEntityId: newPrimary ? state.selectedEntityId : null,
212
+ };
213
+ } else {
214
+ newSet.add(key);
215
+ return {
216
+ selectedEntitiesSet: newSet,
217
+ selectedEntity: ref,
218
+ // NOTE: Don't update selectedEntityId here - caller should use setSelectedEntityId(globalId)
219
+ };
220
+ }
221
+ }),
222
+
223
+ clearEntitySelection: () => set({
224
+ selectedEntity: null,
225
+ selectedEntitiesSet: new Set(),
226
+ selectedEntities: [],
227
+ selectedEntityId: null,
228
+ selectedEntityIds: new Set(),
229
+ selectedModelId: null,
230
+ }),
231
+
232
+ isEntitySelected: (ref) => {
233
+ const key = entityRefToString(ref);
234
+ return get().selectedEntitiesSet.has(key);
235
+ },
236
+
237
+ getSelectedEntitiesForModel: (modelId) => {
238
+ const state = get();
239
+ const result: number[] = [];
240
+ for (const key of state.selectedEntitiesSet) {
241
+ const ref = stringToEntityRef(key);
242
+ if (ref.modelId === modelId) {
243
+ result.push(ref.expressId);
244
+ }
245
+ }
246
+ return result;
247
+ },
248
+
249
+ setSelectedEntities: (refs) => set({
250
+ selectedEntities: refs,
251
+ // Also set the primary selected entity to the first one
252
+ selectedEntity: refs.length > 0 ? refs[0] : null,
253
+ selectedModelId: null, // Clear model selection when selecting entities
254
+ }),
255
+
256
+ setSelectedModelId: (modelId) => set({
257
+ selectedModelId: modelId,
258
+ // Clear other selection when selecting a model
259
+ selectedEntity: null,
260
+ selectedEntities: [],
261
+ selectedEntityId: null,
262
+ }),
263
+ });