@ifc-lite/viewer 1.15.0 → 1.17.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 (62) hide show
  1. package/.turbo/turbo-build.log +46 -0
  2. package/.turbo/turbo-typecheck.log +4 -0
  3. package/CHANGELOG.md +35 -0
  4. package/dist/assets/{Arrow.dom-OVBBPqOB.js → Arrow.dom-CcoDLP6E.js} +1 -1
  5. package/dist/assets/{basketViewActivator-Bx6QU4ma.js → basketViewActivator-FtbS__bG.js} +1 -1
  6. package/dist/assets/{browser-BMqEoJw4.js → browser-CXd3z0DO.js} +1 -1
  7. package/dist/assets/ifc-lite-TI3u_Zyw.js +7 -0
  8. package/dist/assets/ifc-lite_bg-DeZrXTKQ.wasm +0 -0
  9. package/dist/assets/index-Ba4eoTe7.css +1 -0
  10. package/dist/assets/{index-DZY6uD8A.js → index-D99fzcwI.js} +32109 -28671
  11. package/dist/assets/{index-DsX-NCtx.js → index-DqNiuQep.js} +4 -4
  12. package/dist/assets/{native-bridge-D6tKFqGO.js → native-bridge-DjDj2M6p.js} +1 -1
  13. package/dist/assets/{wasm-bridge-D4kvZVDw.js → wasm-bridge-CDTF4ZQc.js} +1 -1
  14. package/dist/assets/workerHelpers-G7llXNMi.js +36 -0
  15. package/dist/index.html +7 -2
  16. package/index.html +5 -0
  17. package/package.json +15 -14
  18. package/src/components/viewer/BCFPanel.tsx +12 -0
  19. package/src/components/viewer/BulkPropertyEditor.tsx +315 -154
  20. package/src/components/viewer/CommandPalette.tsx +0 -6
  21. package/src/components/viewer/DataConnector.tsx +489 -284
  22. package/src/components/viewer/ExportDialog.tsx +66 -6
  23. package/src/components/viewer/KeyboardShortcutsDialog.tsx +227 -82
  24. package/src/components/viewer/MainToolbar.tsx +1 -5
  25. package/src/components/viewer/Viewport.tsx +42 -56
  26. package/src/components/viewer/ViewportContainer.tsx +3 -0
  27. package/src/components/viewer/ViewportOverlays.tsx +12 -10
  28. package/src/components/viewer/bcf/BCFOverlay.tsx +254 -0
  29. package/src/components/viewer/hierarchy/HierarchyNode.tsx +26 -20
  30. package/src/components/viewer/hierarchy/ifc-icons.ts +90 -0
  31. package/src/components/viewer/lists/ListPanel.tsx +0 -21
  32. package/src/components/viewer/lists/ListResultsTable.tsx +93 -5
  33. package/src/components/viewer/measureHandlers.ts +558 -0
  34. package/src/components/viewer/mouseHandlerTypes.ts +108 -0
  35. package/src/components/viewer/selectionHandlers.ts +86 -0
  36. package/src/components/viewer/useAnimationLoop.ts +116 -44
  37. package/src/components/viewer/useGeometryStreaming.ts +155 -367
  38. package/src/components/viewer/useKeyboardControls.ts +30 -46
  39. package/src/components/viewer/useMouseControls.ts +169 -695
  40. package/src/components/viewer/useRenderUpdates.ts +9 -59
  41. package/src/components/viewer/useTouchControls.ts +55 -40
  42. package/src/hooks/bcfIdLookup.ts +70 -0
  43. package/src/hooks/useBCF.ts +12 -31
  44. package/src/hooks/useIfcCache.ts +11 -29
  45. package/src/hooks/useIfcFederation.ts +5 -11
  46. package/src/hooks/useIfcLoader.ts +47 -56
  47. package/src/hooks/useIfcServer.ts +9 -1
  48. package/src/hooks/useKeyboardShortcuts.ts +28 -12
  49. package/src/hooks/useLatestRef.ts +24 -0
  50. package/src/sdk/adapters/export-adapter.ts +2 -2
  51. package/src/sdk/adapters/model-adapter.ts +1 -0
  52. package/src/sdk/local-backend.ts +2 -0
  53. package/src/store/basketVisibleSet.ts +12 -0
  54. package/src/store/slices/bcfSlice.ts +9 -0
  55. package/src/store/slices/pinboardSlice.ts +46 -45
  56. package/src/utils/loadingUtils.ts +46 -0
  57. package/src/utils/serverDataModel.ts +4 -3
  58. package/src/utils/spatialHierarchy.ts +1 -1
  59. package/src/vite-env.d.ts +6 -2
  60. package/vite.config.ts +75 -23
  61. package/dist/assets/ifc-lite_bg-CyWQTvp5.wasm +0 -0
  62. package/dist/assets/index-CJr7Itua.css +0 -1
@@ -34,7 +34,6 @@ import { useViewerStore } from '@/store';
34
34
  import { useIfc } from '@/hooks/useIfc';
35
35
  import {
36
36
  executeList,
37
- listResultToCSV,
38
37
  LIST_PRESETS,
39
38
  importListDefinition,
40
39
  exportListDefinition,
@@ -172,18 +171,6 @@ export function ListPanel({ onClose }: ListPanelProps) {
172
171
  }
173
172
  }, [editingList]);
174
173
 
175
- const handleExportCSV = useCallback(() => {
176
- if (!listResult) return;
177
- const csv = listResultToCSV(listResult);
178
- const blob = new Blob([csv], { type: 'text/csv' });
179
- const url = URL.createObjectURL(blob);
180
- const a = document.createElement('a');
181
- a.href = url;
182
- a.download = 'list-export.csv';
183
- a.click();
184
- setTimeout(() => URL.revokeObjectURL(url), 1000);
185
- }, [listResult]);
186
-
187
174
  const handleImport = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
188
175
  const file = e.target.files?.[0];
189
176
  if (!file) return;
@@ -228,14 +215,6 @@ export function ListPanel({ onClose }: ListPanelProps) {
228
215
  </TooltipTrigger>
229
216
  <TooltipContent>Edit Configuration</TooltipContent>
230
217
  </Tooltip>
231
- <Tooltip>
232
- <TooltipTrigger asChild>
233
- <Button variant="ghost" size="icon-sm" onClick={handleExportCSV}>
234
- <Download className="h-3.5 w-3.5" />
235
- </Button>
236
- </TooltipTrigger>
237
- <TooltipContent>Export CSV</TooltipContent>
238
- </Tooltip>
239
218
  <Tooltip>
240
219
  <TooltipTrigger asChild>
241
220
  <Button variant="ghost" size="icon-sm" onClick={() => setView('library')}>
@@ -12,10 +12,14 @@
12
12
 
13
13
  import React, { useCallback, useMemo, useRef, useState } from 'react';
14
14
  import { useVirtualizer } from '@tanstack/react-virtual';
15
- import { ArrowUp, ArrowDown, Search, Palette } from 'lucide-react';
15
+ import { ArrowUp, ArrowDown, Search, Palette, Eye, EyeOff, Download } from 'lucide-react';
16
16
  import { Input } from '@/components/ui/input';
17
+ import { Button } from '@/components/ui/button';
18
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
17
19
  import { useViewerStore } from '@/store';
20
+ import { getVisibleBasketEntityRefsFromStore } from '@/store/basketVisibleSet';
18
21
  import type { ListResult, ListRow, CellValue, ColumnDefinition } from '@ifc-lite/lists';
22
+ import { listResultToCSV } from '@ifc-lite/lists';
19
23
  import { cn } from '@/lib/utils';
20
24
  import { columnToAutoColor } from '@/lib/lists/columnToAutoColor';
21
25
  import { AUTO_COLOR_FROM_LIST_ID } from '@/store/slices/lensSlice';
@@ -29,6 +33,7 @@ export function ListResultsTable({ result }: ListResultsTableProps) {
29
33
  const [searchQuery, setSearchQuery] = useState('');
30
34
  const [sortCol, setSortCol] = useState<number | null>(null);
31
35
  const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');
36
+ const [filterByVisibility, setFilterByVisibility] = useState(true);
32
37
 
33
38
  const setSelectedEntityId = useViewerStore((s) => s.setSelectedEntityId);
34
39
  const setSelectedEntity = useViewerStore((s) => s.setSelectedEntity);
@@ -37,14 +42,49 @@ export function ListResultsTable({ result }: ListResultsTableProps) {
37
42
  const activeLensId = useViewerStore((s) => s.activeLensId);
38
43
  const [colorByColIdx, setColorByColIdx] = useState<number | null>(null);
39
44
 
45
+ // Subscribe to visibility state so we re-filter when 3D visibility changes
46
+ const hiddenEntities = useViewerStore((s) => s.hiddenEntities);
47
+ const isolatedEntities = useViewerStore((s) => s.isolatedEntities);
48
+ const classFilter = useViewerStore((s) => s.classFilter);
49
+ const lensHiddenIds = useViewerStore((s) => s.lensHiddenIds);
50
+ const selectedStoreys = useViewerStore((s) => s.selectedStoreys);
51
+ const typeVisibility = useViewerStore((s) => s.typeVisibility);
52
+ const hiddenEntitiesByModel = useViewerStore((s) => s.hiddenEntitiesByModel);
53
+ const isolatedEntitiesByModel = useViewerStore((s) => s.isolatedEntitiesByModel);
54
+ const models = useViewerStore((s) => s.models);
55
+ const activeBasketViewId = useViewerStore((s) => s.activeBasketViewId);
56
+ const geometryResult = useViewerStore((s) => s.geometryResult);
57
+
58
+ // Filter rows by 3D visibility
59
+ const visibilityFilteredRows = useMemo(() => {
60
+ if (!filterByVisibility) return result.rows;
61
+
62
+ const visibleRefs = getVisibleBasketEntityRefsFromStore();
63
+ const visibleSet = new Set<string>();
64
+ for (const ref of visibleRefs) {
65
+ visibleSet.add(`${ref.modelId}:${ref.expressId}`);
66
+ }
67
+
68
+ return result.rows.filter(row => {
69
+ // List uses 'default' for single-model, visibility uses 'legacy'
70
+ const modelId = row.modelId === 'default' ? 'legacy' : row.modelId;
71
+ return visibleSet.has(`${modelId}:${row.entityId}`);
72
+ });
73
+ }, [
74
+ result.rows, filterByVisibility,
75
+ hiddenEntities, isolatedEntities, classFilter, lensHiddenIds,
76
+ selectedStoreys, typeVisibility, hiddenEntitiesByModel,
77
+ isolatedEntitiesByModel, models, activeBasketViewId, geometryResult,
78
+ ]);
79
+
40
80
  // Filter rows by search query
41
81
  const filteredRows = useMemo(() => {
42
- if (!searchQuery) return result.rows;
82
+ if (!searchQuery) return visibilityFilteredRows;
43
83
  const q = searchQuery.toLowerCase();
44
- return result.rows.filter(row =>
84
+ return visibilityFilteredRows.filter(row =>
45
85
  row.values.some(v => v !== null && String(v).toLowerCase().includes(q))
46
86
  );
47
- }, [result.rows, searchQuery]);
87
+ }, [visibilityFilteredRows, searchQuery]);
48
88
 
49
89
  // Sort rows
50
90
  const sortedRows = useMemo(() => {
@@ -74,6 +114,23 @@ export function ListResultsTable({ result }: ListResultsTableProps) {
74
114
  setColorByColIdx(colIdx);
75
115
  }, [activateAutoColorFromColumn]);
76
116
 
117
+ const handleExportCSV = useCallback(() => {
118
+ const exportResult: ListResult = {
119
+ columns: result.columns,
120
+ rows: sortedRows,
121
+ totalCount: sortedRows.length,
122
+ executionTime: result.executionTime,
123
+ };
124
+ const csv = listResultToCSV(exportResult);
125
+ const blob = new Blob([csv], { type: 'text/csv' });
126
+ const url = URL.createObjectURL(blob);
127
+ const a = document.createElement('a');
128
+ a.href = url;
129
+ a.download = 'list-export.csv';
130
+ a.click();
131
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
132
+ }, [result.columns, result.executionTime, sortedRows]);
133
+
77
134
  const handleRowClick = useCallback((row: ListRow) => {
78
135
  setSelectedEntity({ modelId: row.modelId, expressId: row.entityId });
79
136
  // For single-model, selectedEntityId is the expressId
@@ -111,8 +168,39 @@ export function ListResultsTable({ result }: ListResultsTableProps) {
111
168
  className="h-7 text-xs border-0 shadow-none focus-visible:ring-0 px-0"
112
169
  />
113
170
  <span className="text-xs text-muted-foreground whitespace-nowrap">
114
- {sortedRows.length}{searchQuery ? ` / ${result.rows.length}` : ''} rows
171
+ {sortedRows.length}{(searchQuery || filterByVisibility) ? ` / ${result.rows.length}` : ''} rows
115
172
  </span>
173
+ <Tooltip>
174
+ <TooltipTrigger asChild>
175
+ <Button
176
+ variant="ghost"
177
+ size="icon-sm"
178
+ className={cn(
179
+ 'h-6 w-6 shrink-0',
180
+ filterByVisibility && 'text-primary',
181
+ )}
182
+ onClick={() => setFilterByVisibility(prev => !prev)}
183
+ >
184
+ {filterByVisibility ? <Eye className="h-3.5 w-3.5" /> : <EyeOff className="h-3.5 w-3.5" />}
185
+ </Button>
186
+ </TooltipTrigger>
187
+ <TooltipContent>
188
+ {filterByVisibility ? 'Showing visible objects only' : 'Showing all objects'}
189
+ </TooltipContent>
190
+ </Tooltip>
191
+ <Tooltip>
192
+ <TooltipTrigger asChild>
193
+ <Button
194
+ variant="ghost"
195
+ size="icon-sm"
196
+ className="h-6 w-6 shrink-0"
197
+ onClick={handleExportCSV}
198
+ >
199
+ <Download className="h-3.5 w-3.5" />
200
+ </Button>
201
+ </TooltipTrigger>
202
+ <TooltipContent>Export CSV</TooltipContent>
203
+ </Tooltip>
116
204
  </div>
117
205
 
118
206
  {/* Table */}