@ifc-lite/viewer 1.6.0 → 1.7.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 (95) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/dist/assets/{Arrow.dom-BjDQoB2M.js → Arrow.dom-BGPQieQQ.js} +1 -1
  3. package/dist/assets/ifc-lite_bg-DyIN_nBM.wasm +0 -0
  4. package/dist/assets/{index-YBtrHPu3.js → index-dgdgiQ9p.js} +40212 -30008
  5. package/dist/assets/index-yTqs8kgX.css +1 -0
  6. package/dist/assets/{native-bridge-CULtTDX3.js → native-bridge-DD0SNyQ5.js} +1 -1
  7. package/dist/assets/{wasm-bridge-CjL-lSak.js → wasm-bridge-D54YMO7X.js} +1 -1
  8. package/dist/index.html +2 -2
  9. package/package.json +18 -15
  10. package/src/components/viewer/BCFPanel.tsx +7 -789
  11. package/src/components/viewer/Drawing2DCanvas.tsx +1048 -0
  12. package/src/components/viewer/DrawingSettingsPanel.tsx +3 -3
  13. package/src/components/viewer/HierarchyPanel.tsx +110 -842
  14. package/src/components/viewer/IDSExportDialog.tsx +281 -0
  15. package/src/components/viewer/IDSPanel.tsx +126 -17
  16. package/src/components/viewer/KeyboardShortcutsDialog.tsx +9 -0
  17. package/src/components/viewer/LensPanel.tsx +603 -0
  18. package/src/components/viewer/MainToolbar.tsx +188 -21
  19. package/src/components/viewer/PropertiesPanel.tsx +171 -663
  20. package/src/components/viewer/PropertyEditor.tsx +866 -77
  21. package/src/components/viewer/Section2DPanel.tsx +76 -2648
  22. package/src/components/viewer/ToolOverlays.tsx +3 -1097
  23. package/src/components/viewer/ViewerLayout.tsx +132 -45
  24. package/src/components/viewer/Viewport.tsx +237 -1659
  25. package/src/components/viewer/ViewportContainer.tsx +11 -3
  26. package/src/components/viewer/bcf/BCFCreateTopicForm.tsx +134 -0
  27. package/src/components/viewer/bcf/BCFTopicDetail.tsx +388 -0
  28. package/src/components/viewer/bcf/BCFTopicList.tsx +239 -0
  29. package/src/components/viewer/bcf/bcfHelpers.tsx +109 -0
  30. package/src/components/viewer/hierarchy/HierarchyNode.tsx +328 -0
  31. package/src/components/viewer/hierarchy/treeDataBuilder.ts +464 -0
  32. package/src/components/viewer/hierarchy/types.ts +54 -0
  33. package/src/components/viewer/hierarchy/useHierarchyTree.ts +280 -0
  34. package/src/components/viewer/lists/ListBuilder.tsx +486 -0
  35. package/src/components/viewer/lists/ListPanel.tsx +540 -0
  36. package/src/components/viewer/lists/ListResultsTable.tsx +193 -0
  37. package/src/components/viewer/properties/ClassificationCard.tsx +70 -0
  38. package/src/components/viewer/properties/CoordinateDisplay.tsx +49 -0
  39. package/src/components/viewer/properties/DocumentCard.tsx +89 -0
  40. package/src/components/viewer/properties/MaterialCard.tsx +201 -0
  41. package/src/components/viewer/properties/ModelMetadataPanel.tsx +335 -0
  42. package/src/components/viewer/properties/PropertySetCard.tsx +132 -0
  43. package/src/components/viewer/properties/QuantitySetCard.tsx +79 -0
  44. package/src/components/viewer/properties/RelationshipsCard.tsx +100 -0
  45. package/src/components/viewer/properties/encodingUtils.ts +29 -0
  46. package/src/components/viewer/tools/MeasurePanel.tsx +218 -0
  47. package/src/components/viewer/tools/MeasurementVisuals.tsx +644 -0
  48. package/src/components/viewer/tools/SectionPanel.tsx +183 -0
  49. package/src/components/viewer/tools/SectionVisualization.tsx +78 -0
  50. package/src/components/viewer/tools/formatDistance.ts +18 -0
  51. package/src/components/viewer/tools/sectionConstants.ts +14 -0
  52. package/src/components/viewer/useAnimationLoop.ts +166 -0
  53. package/src/components/viewer/useGeometryStreaming.ts +398 -0
  54. package/src/components/viewer/useKeyboardControls.ts +221 -0
  55. package/src/components/viewer/useMouseControls.ts +1009 -0
  56. package/src/components/viewer/useRenderUpdates.ts +165 -0
  57. package/src/components/viewer/useTouchControls.ts +245 -0
  58. package/src/hooks/ids/idsColorSystem.ts +125 -0
  59. package/src/hooks/ids/idsDataAccessor.ts +237 -0
  60. package/src/hooks/ids/idsExportService.ts +444 -0
  61. package/src/hooks/useBCF.ts +7 -0
  62. package/src/hooks/useDrawingExport.ts +627 -0
  63. package/src/hooks/useDrawingGeneration.ts +627 -0
  64. package/src/hooks/useFloorplanView.ts +108 -0
  65. package/src/hooks/useIDS.ts +270 -463
  66. package/src/hooks/useIfc.ts +26 -1628
  67. package/src/hooks/useIfcFederation.ts +803 -0
  68. package/src/hooks/useIfcLoader.ts +508 -0
  69. package/src/hooks/useIfcServer.ts +465 -0
  70. package/src/hooks/useKeyboardShortcuts.ts +1 -1
  71. package/src/hooks/useLens.ts +129 -0
  72. package/src/hooks/useMeasure2D.ts +365 -0
  73. package/src/hooks/useViewControls.ts +218 -0
  74. package/src/lib/ifc4-pset-definitions.test.ts +161 -0
  75. package/src/lib/ifc4-pset-definitions.ts +621 -0
  76. package/src/lib/ifc4-qto-definitions.ts +315 -0
  77. package/src/lib/lens/adapter.ts +138 -0
  78. package/src/lib/lens/index.ts +5 -0
  79. package/src/lib/lists/adapter.ts +69 -0
  80. package/src/lib/lists/index.ts +28 -0
  81. package/src/lib/lists/persistence.ts +64 -0
  82. package/src/services/fs-cache.ts +1 -1
  83. package/src/services/tauri-modules.d.ts +25 -0
  84. package/src/store/index.ts +38 -2
  85. package/src/store/slices/cameraSlice.ts +14 -1
  86. package/src/store/slices/dataSlice.ts +14 -1
  87. package/src/store/slices/lensSlice.ts +184 -0
  88. package/src/store/slices/listSlice.ts +74 -0
  89. package/src/store/slices/pinboardSlice.ts +114 -0
  90. package/src/store/types.ts +5 -0
  91. package/src/utils/ifcConfig.ts +16 -3
  92. package/src/utils/serverDataModel.ts +64 -101
  93. package/src/vite-env.d.ts +3 -0
  94. package/dist/assets/ifc-lite_bg-C6kblxf9.wasm +0 -0
  95. package/dist/assets/index-v3mcCUPN.css +0 -1
@@ -15,6 +15,7 @@ import {
15
15
  Eye,
16
16
  EyeOff,
17
17
  Focus,
18
+ Crosshair,
18
19
  Home,
19
20
  Maximize2,
20
21
  Grid3x3,
@@ -35,6 +36,11 @@ import {
35
36
  Plus,
36
37
  MessageSquare,
37
38
  ClipboardCheck,
39
+ Pin,
40
+ PinOff,
41
+ Palette,
42
+ Orbit,
43
+ Trash2,
38
44
  } from 'lucide-react';
39
45
  import { Button } from '@/components/ui/button';
40
46
  import { Separator } from '@/components/ui/separator';
@@ -60,6 +66,7 @@ import { ExportDialog } from './ExportDialog';
60
66
  import { BulkPropertyEditor } from './BulkPropertyEditor';
61
67
  import { DataConnector } from './DataConnector';
62
68
  import { ExportChangesButton } from './ExportChangesButton';
69
+ import { useFloorplanView } from '@/hooks/useFloorplanView';
63
70
 
64
71
  type Tool = 'select' | 'pan' | 'orbit' | 'walk' | 'measure' | 'section';
65
72
 
@@ -141,6 +148,9 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
141
148
  const addModelInputRef = useRef<HTMLInputElement>(null);
142
149
  const { loadFile, loading, progress, geometryResult, ifcDataStore, models, clearAllModels, loadFilesSequentially, loadFederatedIfcx, addIfcxOverlays, addModel } = useIfc();
143
150
 
151
+ // Floorplan view
152
+ const { availableStoreys, activateFloorplan } = useFloorplanView();
153
+
144
154
  // Check if we have models loaded (for showing add model button)
145
155
  const hasModelsLoaded = models.size > 0 || (geometryResult?.meshes && geometryResult.meshes.length > 0);
146
156
  const activeTool = useViewerStore((state) => state.activeTool);
@@ -163,7 +173,21 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
163
173
  const toggleBcfPanel = useViewerStore((state) => state.toggleBcfPanel);
164
174
  const idsPanelVisible = useViewerStore((state) => state.idsPanelVisible);
165
175
  const toggleIdsPanel = useViewerStore((state) => state.toggleIdsPanel);
176
+ const listPanelVisible = useViewerStore((state) => state.listPanelVisible);
177
+ const toggleListPanel = useViewerStore((state) => state.toggleListPanel);
166
178
  const setRightPanelCollapsed = useViewerStore((state) => state.setRightPanelCollapsed);
179
+ const projectionMode = useViewerStore((state) => state.projectionMode);
180
+ const toggleProjectionMode = useViewerStore((state) => state.toggleProjectionMode);
181
+ // Pinboard state
182
+ const pinboardEntities = useViewerStore((state) => state.pinboardEntities);
183
+ const addToPinboard = useViewerStore((state) => state.addToPinboard);
184
+ const removeFromPinboard = useViewerStore((state) => state.removeFromPinboard);
185
+ const showPinboard = useViewerStore((state) => state.showPinboard);
186
+ const clearPinboard = useViewerStore((state) => state.clearPinboard);
187
+ const selectedEntity = useViewerStore((state) => state.selectedEntity);
188
+ // Lens state
189
+ const lensPanelVisible = useViewerStore((state) => state.lensPanelVisible);
190
+ const toggleLensPanel = useViewerStore((state) => state.toggleLensPanel);
167
191
 
168
192
  // Check which type geometries exist across ALL loaded models (federation-aware)
169
193
  const typeGeometryExists = useMemo(() => {
@@ -387,7 +411,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
387
411
 
388
412
  return (
389
413
  <div className="flex items-center gap-1 px-2 h-12 border-b bg-white dark:bg-black border-zinc-200 dark:border-zinc-800 relative z-50">
390
- {/* File Operations */}
414
+ {/* ── File Operations ── */}
391
415
  <input
392
416
  ref={fileInputRef}
393
417
  type="file"
@@ -541,6 +565,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
541
565
  {/* Export Changes Button - shows when there are pending mutations */}
542
566
  <ExportChangesButton />
543
567
 
568
+ {/* ── Panels ── */}
544
569
  {/* BCF Issues Button */}
545
570
  <Tooltip>
546
571
  <TooltipTrigger asChild>
@@ -585,9 +610,30 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
585
610
  <TooltipContent>IDS Validation</TooltipContent>
586
611
  </Tooltip>
587
612
 
613
+ {/* Lists Button */}
614
+ <Tooltip>
615
+ <TooltipTrigger asChild>
616
+ <Button
617
+ variant={listPanelVisible ? 'default' : 'ghost'}
618
+ size="icon-sm"
619
+ onClick={(e) => {
620
+ (e.currentTarget as HTMLButtonElement).blur();
621
+ if (!listPanelVisible) {
622
+ setRightPanelCollapsed(false);
623
+ }
624
+ toggleListPanel();
625
+ }}
626
+ className={cn(listPanelVisible && 'bg-primary text-primary-foreground')}
627
+ >
628
+ <FileSpreadsheet className="h-4 w-4" />
629
+ </Button>
630
+ </TooltipTrigger>
631
+ <TooltipContent>Lists</TooltipContent>
632
+ </Tooltip>
633
+
588
634
  <Separator orientation="vertical" className="h-6 mx-1" />
589
635
 
590
- {/* Navigation Tools */}
636
+ {/* ── Navigation Tools ── */}
591
637
  <ToolButton tool="select" icon={MousePointer2} label="Select" shortcut="V" activeTool={activeTool} onToolChange={setActiveTool} />
592
638
  <ToolButton tool="pan" icon={Hand} label="Pan" shortcut="P" activeTool={activeTool} onToolChange={setActiveTool} />
593
639
  <ToolButton tool="orbit" icon={Rotate3d} label="Orbit" shortcut="O" activeTool={activeTool} onToolChange={setActiveTool} />
@@ -595,16 +641,52 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
595
641
 
596
642
  <Separator orientation="vertical" className="h-6 mx-1" />
597
643
 
598
- {/* Measurement & Section */}
644
+ {/* ── Measurement & Section ── */}
599
645
  <ToolButton tool="measure" icon={Ruler} label="Measure" shortcut="M" activeTool={activeTool} onToolChange={setActiveTool} />
600
646
  <ToolButton tool="section" icon={Scissors} label="Section" shortcut="X" activeTool={activeTool} onToolChange={setActiveTool} />
601
647
 
648
+ {/* Floorplan dropdown */}
649
+ {availableStoreys.length > 0 && (
650
+ <DropdownMenu>
651
+ <Tooltip>
652
+ <TooltipTrigger asChild>
653
+ <DropdownMenuTrigger asChild>
654
+ <Button variant="ghost" size="icon-sm">
655
+ <Building2 className="h-4 w-4" />
656
+ </Button>
657
+ </DropdownMenuTrigger>
658
+ </TooltipTrigger>
659
+ <TooltipContent>Quick Floorplan</TooltipContent>
660
+ </Tooltip>
661
+ <DropdownMenuContent>
662
+ {availableStoreys.map((storey) => (
663
+ <DropdownMenuItem
664
+ key={`${storey.modelId}-${storey.expressId}`}
665
+ onClick={() => activateFloorplan(storey)}
666
+ >
667
+ <Building2 className="h-4 w-4 mr-2" />
668
+ {storey.name}
669
+ <span className="ml-auto text-xs opacity-60">{storey.elevation.toFixed(1)}m</span>
670
+ </DropdownMenuItem>
671
+ ))}
672
+ </DropdownMenuContent>
673
+ </DropdownMenu>
674
+ )}
675
+
602
676
  <Separator orientation="vertical" className="h-6 mx-1" />
603
677
 
604
- {/* Visibility */}
678
+ {/* ── Visibility & Filtering (all together) ── */}
605
679
  <ActionButton icon={Focus} label="Isolate Selection" onClick={handleIsolate} shortcut="I" disabled={!selectedEntityId} />
606
680
  <ActionButton icon={EyeOff} label="Hide Selection" onClick={handleHide} shortcut="Del" disabled={!selectedEntityId} />
607
681
  <ActionButton icon={Eye} label="Show All (Reset Filters)" onClick={handleShowAll} shortcut="A" />
682
+ <ActionButton icon={Maximize2} label="Fit All" onClick={() => cameraCallbacks.fitAll?.()} shortcut="Z" />
683
+ <ActionButton
684
+ icon={Crosshair}
685
+ label="Frame Selection"
686
+ onClick={() => cameraCallbacks.frameSelection?.()}
687
+ shortcut="F"
688
+ disabled={!selectedEntityId}
689
+ />
608
690
 
609
691
  <DropdownMenu>
610
692
  <Tooltip>
@@ -615,7 +697,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
615
697
  </Button>
616
698
  </DropdownMenuTrigger>
617
699
  </TooltipTrigger>
618
- <TooltipContent>Type Visibility</TooltipContent>
700
+ <TooltipContent>Class Visibility</TooltipContent>
619
701
  </Tooltip>
620
702
  <DropdownMenuContent>
621
703
  {typeGeometryExists.spaces && (
@@ -648,16 +730,113 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
648
730
  </DropdownMenuContent>
649
731
  </DropdownMenu>
650
732
 
733
+ {/* Pinboard dropdown */}
734
+ <DropdownMenu>
735
+ <Tooltip>
736
+ <TooltipTrigger asChild>
737
+ <DropdownMenuTrigger asChild>
738
+ <Button
739
+ variant={pinboardEntities.size > 0 ? 'default' : 'ghost'}
740
+ size="icon-sm"
741
+ className={cn(pinboardEntities.size > 0 && 'bg-primary text-primary-foreground relative')}
742
+ >
743
+ <Pin className="h-4 w-4" />
744
+ {pinboardEntities.size > 0 && (
745
+ <span className="absolute -top-1 -right-1 bg-primary text-primary-foreground text-[9px] font-bold rounded-full min-w-[14px] h-[14px] flex items-center justify-center px-0.5 border border-background">
746
+ {pinboardEntities.size}
747
+ </span>
748
+ )}
749
+ </Button>
750
+ </DropdownMenuTrigger>
751
+ </TooltipTrigger>
752
+ <TooltipContent>Pinboard ({pinboardEntities.size})</TooltipContent>
753
+ </Tooltip>
754
+ <DropdownMenuContent>
755
+ <DropdownMenuItem
756
+ onClick={() => { if (selectedEntity) addToPinboard([selectedEntity]); }}
757
+ disabled={!selectedEntity}
758
+ >
759
+ <Pin className="h-4 w-4 mr-2" />
760
+ Pin Selection
761
+ </DropdownMenuItem>
762
+ <DropdownMenuItem
763
+ onClick={() => { if (selectedEntity) removeFromPinboard([selectedEntity]); }}
764
+ disabled={!selectedEntity}
765
+ >
766
+ <PinOff className="h-4 w-4 mr-2" />
767
+ Unpin Selection
768
+ </DropdownMenuItem>
769
+ <DropdownMenuSeparator />
770
+ <DropdownMenuItem
771
+ onClick={() => showPinboard()}
772
+ disabled={pinboardEntities.size === 0}
773
+ >
774
+ <Eye className="h-4 w-4 mr-2" />
775
+ Show Pinboard
776
+ </DropdownMenuItem>
777
+ <DropdownMenuItem
778
+ onClick={() => clearPinboard()}
779
+ disabled={pinboardEntities.size === 0}
780
+ >
781
+ <Trash2 className="h-4 w-4 mr-2" />
782
+ Clear Pinboard
783
+ </DropdownMenuItem>
784
+ </DropdownMenuContent>
785
+ </DropdownMenu>
786
+
787
+ {/* Lens (rule-based filtering) */}
788
+ <Tooltip>
789
+ <TooltipTrigger asChild>
790
+ <Button
791
+ variant={lensPanelVisible ? 'default' : 'ghost'}
792
+ size="icon-sm"
793
+ onClick={(e) => {
794
+ (e.currentTarget as HTMLButtonElement).blur();
795
+ if (!lensPanelVisible) {
796
+ setRightPanelCollapsed(false);
797
+ }
798
+ toggleLensPanel();
799
+ }}
800
+ className={cn(lensPanelVisible && 'bg-primary text-primary-foreground')}
801
+ >
802
+ <Palette className="h-4 w-4" />
803
+ </Button>
804
+ </TooltipTrigger>
805
+ <TooltipContent>Lens (Color Rules)</TooltipContent>
806
+ </Tooltip>
807
+
651
808
  <Separator orientation="vertical" className="h-6 mx-1" />
652
809
 
653
- {/* Display Options */}
810
+ {/* ── Camera & View ── */}
811
+ <ActionButton icon={Home} label="Home (Isometric)" onClick={() => cameraCallbacks.home?.()} shortcut="H" />
812
+
813
+ {/* Orthographic / Perspective toggle */}
814
+ <Tooltip>
815
+ <TooltipTrigger asChild>
816
+ <Button
817
+ variant={projectionMode === 'orthographic' ? 'default' : 'ghost'}
818
+ size="icon-sm"
819
+ onClick={(e) => {
820
+ (e.currentTarget as HTMLButtonElement).blur();
821
+ toggleProjectionMode();
822
+ }}
823
+ className={cn(projectionMode === 'orthographic' && 'bg-primary text-primary-foreground')}
824
+ >
825
+ <Orbit className="h-4 w-4" />
826
+ </Button>
827
+ </TooltipTrigger>
828
+ <TooltipContent>
829
+ {projectionMode === 'orthographic' ? 'Switch to Perspective' : 'Switch to Orthographic'}
830
+ </TooltipContent>
831
+ </Tooltip>
832
+
833
+ {/* Hover Tooltips toggle */}
654
834
  <Tooltip>
655
835
  <TooltipTrigger asChild>
656
836
  <Button
657
837
  variant={hoverTooltipsEnabled ? 'default' : 'ghost'}
658
838
  size="icon-sm"
659
839
  onClick={(e) => {
660
- // Blur button to close tooltip after click
661
840
  (e.currentTarget as HTMLButtonElement).blur();
662
841
  toggleHoverTooltips();
663
842
  }}
@@ -671,19 +850,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
671
850
  </TooltipContent>
672
851
  </Tooltip>
673
852
 
674
- <Separator orientation="vertical" className="h-6 mx-1" />
675
-
676
- {/* Camera */}
677
- <ActionButton icon={Home} label="Home (Isometric)" onClick={() => cameraCallbacks.home?.()} shortcut="H" />
678
- <ActionButton icon={Maximize2} label="Fit All" onClick={() => cameraCallbacks.fitAll?.()} shortcut="Z" />
679
- <ActionButton
680
- icon={Focus}
681
- label="Frame Selection"
682
- onClick={() => cameraCallbacks.frameSelection?.()}
683
- shortcut="F"
684
- disabled={!selectedEntityId}
685
- />
686
-
853
+ {/* Preset Views dropdown */}
687
854
  <DropdownMenu>
688
855
  <Tooltip>
689
856
  <TooltipTrigger asChild>
@@ -693,7 +860,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
693
860
  </Button>
694
861
  </DropdownMenuTrigger>
695
862
  </TooltipTrigger>
696
- <TooltipContent>Preset Views (0-6)</TooltipContent>
863
+ <TooltipContent>Preset Views</TooltipContent>
697
864
  </Tooltip>
698
865
  <DropdownMenuContent>
699
866
  <DropdownMenuItem onClick={() => cameraCallbacks.home?.()}>