@ifc-lite/viewer 1.17.6 → 1.19.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.
- package/.turbo/turbo-build.log +20 -15
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +949 -0
- package/dist/assets/{basketViewActivator-86rgogji.js → basketViewActivator-RZy5c3Td.js} +1 -1
- package/dist/assets/decode-worker-Collf_X_.js +1320 -0
- package/dist/assets/{exporters-CcPS9MK5.js → exporters-BraHBeoi.js} +4194 -3025
- package/dist/assets/{geometry.worker-BFUYA08u.js → geometry.worker-DQEZB2rB.js} +1 -1
- package/dist/assets/ifc-lite_bg-4yUkDRD8.wasm +0 -0
- package/dist/assets/index-0XpVr_S5.css +1 -0
- package/dist/assets/{index-Bfms9I4A.js → index-BOi3BuUI.js} +46423 -31181
- package/dist/assets/index-XwKzDuw6.js +22 -0
- package/dist/assets/{native-bridge-DUyLCMZS.js → native-bridge-CpBeOPQa.js} +1 -1
- package/dist/assets/sandbox-Baez7n-t.js +9682 -0
- package/dist/assets/{server-client-BuZK7OST.js → server-client-BB6cMAXE.js} +1 -1
- package/dist/assets/{wasm-bridge-JsqEGDV8.js → wasm-bridge-CAYCUHbE.js} +1 -1
- package/dist/index.html +6 -6
- package/package.json +11 -10
- package/src/apache-arrow.d.ts +30 -0
- package/src/components/viewer/AddElementPanel.tsx +758 -0
- package/src/components/viewer/BulkPropertyEditor.tsx +7 -0
- package/src/components/viewer/ChatPanel.tsx +64 -2
- package/src/components/viewer/CommandPalette.tsx +56 -7
- package/src/components/viewer/EntityContextMenu.tsx +168 -4
- package/src/components/viewer/ExportChangesButton.tsx +25 -5
- package/src/components/viewer/ExportDialog.tsx +19 -1
- package/src/components/viewer/MainToolbar.tsx +73 -12
- package/src/components/viewer/PointCloudPanel.tsx +174 -0
- package/src/components/viewer/PropertiesPanel.tsx +222 -22
- package/src/components/viewer/SearchInline.tsx +669 -0
- package/src/components/viewer/SearchModal.filter.builder.tsx +766 -0
- package/src/components/viewer/SearchModal.filter.tsx +514 -0
- package/src/components/viewer/SearchModal.text.tsx +388 -0
- package/src/components/viewer/SearchModal.tsx +235 -0
- package/src/components/viewer/ToolOverlays.tsx +5 -0
- package/src/components/viewer/ViewerLayout.tsx +24 -4
- package/src/components/viewer/Viewport.tsx +29 -2
- package/src/components/viewer/ViewportContainer.tsx +45 -5
- package/src/components/viewer/ViewportOverlays.tsx +13 -2
- package/src/components/viewer/annotations/AnnotationDropInput.tsx +203 -0
- package/src/components/viewer/annotations/AnnotationLayer.tsx +287 -0
- package/src/components/viewer/annotations/AnnotationPin.tsx +90 -0
- package/src/components/viewer/annotations/AnnotationPopover.tsx +296 -0
- package/src/components/viewer/bcf/BCFTopicDetail.tsx +1 -1
- package/src/components/viewer/lists/ListPanel.tsx +14 -21
- package/src/components/viewer/properties/RawStepCard.tsx +332 -0
- package/src/components/viewer/properties/RawStepRow.tsx +261 -0
- package/src/components/viewer/properties/ScheduleCard.tsx +224 -0
- package/src/components/viewer/properties/TaskEditCard.tsx +510 -0
- package/src/components/viewer/properties/raw-step-format.ts +193 -0
- package/src/components/viewer/schedule/AnimationSettingsPopover.tsx +542 -0
- package/src/components/viewer/schedule/GanttDependencyArrows.tsx +89 -0
- package/src/components/viewer/schedule/GanttDragTooltip.tsx +48 -0
- package/src/components/viewer/schedule/GanttEmptyState.tsx +97 -0
- package/src/components/viewer/schedule/GanttPanel.tsx +295 -0
- package/src/components/viewer/schedule/GanttTaskBar.tsx +199 -0
- package/src/components/viewer/schedule/GanttTaskTree.tsx +250 -0
- package/src/components/viewer/schedule/GanttTimeline.tsx +305 -0
- package/src/components/viewer/schedule/GanttToolbar.tsx +406 -0
- package/src/components/viewer/schedule/GenerateAdvancedPanel.tsx +147 -0
- package/src/components/viewer/schedule/GenerateScheduleDialog.tsx +392 -0
- package/src/components/viewer/schedule/HeightStrategyPanel.tsx +120 -0
- package/src/components/viewer/schedule/generate-schedule.test.ts +439 -0
- package/src/components/viewer/schedule/generate-schedule.ts +648 -0
- package/src/components/viewer/schedule/schedule-animator.test.ts +452 -0
- package/src/components/viewer/schedule/schedule-animator.ts +488 -0
- package/src/components/viewer/schedule/schedule-selection.test.ts +148 -0
- package/src/components/viewer/schedule/schedule-selection.ts +163 -0
- package/src/components/viewer/schedule/schedule-utils.ts +223 -0
- package/src/components/viewer/schedule/useConstructionSequence.ts +156 -0
- package/src/components/viewer/schedule/useGanttBarDrag.test.ts +90 -0
- package/src/components/viewer/schedule/useGanttBarDrag.ts +305 -0
- package/src/components/viewer/schedule/useGanttSelection3DHighlight.ts +152 -0
- package/src/components/viewer/schedule/useOverlayCompositor.ts +108 -0
- package/src/components/viewer/selectionHandlers.ts +446 -0
- package/src/components/viewer/tools/AddElementOverlay.tsx +581 -0
- package/src/components/viewer/useDuplicateShortcut.ts +77 -0
- package/src/components/viewer/useMouseControls.ts +9 -1
- package/src/components/viewer/usePointCloudLifecycle.ts +64 -0
- package/src/components/viewer/usePointCloudSync.ts +98 -0
- package/src/hooks/ingest/pointCloudIngest.ts +391 -0
- package/src/hooks/ingest/viewerModelIngest.ts +32 -3
- package/src/hooks/useIfcFederation.ts +72 -3
- package/src/hooks/useIfcLoader.ts +89 -13
- package/src/hooks/useKeyboardShortcuts.ts +25 -0
- package/src/hooks/useSandbox.ts +1 -1
- package/src/hooks/useSearchIndex.ts +125 -0
- package/src/index.css +66 -0
- package/src/lib/llm/system-prompt.test.ts +14 -0
- package/src/lib/llm/system-prompt.ts +102 -1
- package/src/lib/llm/types.ts +6 -0
- package/src/lib/recent-files.ts +38 -4
- package/src/lib/scripts/templates/bim-globals.d.ts +136 -114
- package/src/lib/scripts/templates/construction-schedule.ts +223 -0
- package/src/lib/scripts/templates.ts +7 -0
- package/src/lib/search/common-ifc-types.ts +36 -0
- package/src/lib/search/filter-evaluate.test.ts +537 -0
- package/src/lib/search/filter-evaluate.ts +610 -0
- package/src/lib/search/filter-rules.test.ts +119 -0
- package/src/lib/search/filter-rules.ts +198 -0
- package/src/lib/search/filter-schema.test.ts +233 -0
- package/src/lib/search/filter-schema.ts +146 -0
- package/src/lib/search/recent-searches.test.ts +116 -0
- package/src/lib/search/recent-searches.ts +93 -0
- package/src/lib/search/result-export.test.ts +101 -0
- package/src/lib/search/result-export.ts +104 -0
- package/src/lib/search/saved-filters.test.ts +118 -0
- package/src/lib/search/saved-filters.ts +154 -0
- package/src/lib/search/tier0-scan.test.ts +196 -0
- package/src/lib/search/tier0-scan.ts +237 -0
- package/src/lib/search/tier1-index.test.ts +242 -0
- package/src/lib/search/tier1-index.ts +448 -0
- package/src/sdk/adapters/export-adapter.test.ts +434 -1
- package/src/sdk/adapters/export-adapter.ts +404 -1
- package/src/sdk/adapters/export-schedule-splice.test.ts +127 -0
- package/src/sdk/adapters/export-schedule-splice.ts +87 -0
- package/src/sdk/adapters/model-compat.ts +8 -2
- package/src/sdk/adapters/schedule-adapter.ts +73 -0
- package/src/sdk/adapters/store-adapter.ts +201 -0
- package/src/sdk/adapters/visibility-adapter.ts +3 -0
- package/src/sdk/local-backend.ts +16 -8
- package/src/services/desktop-export.ts +3 -1
- package/src/services/desktop-native-metadata.ts +41 -18
- package/src/services/file-dialog.ts +8 -3
- package/src/services/tauri-modules.d.ts +25 -0
- package/src/store/basketVisibleSet.ts +3 -0
- package/src/store/globalId.ts +4 -1
- package/src/store/index.ts +79 -1
- package/src/store/slices/addElementMeshes.ts +365 -0
- package/src/store/slices/addElementSlice.ts +275 -0
- package/src/store/slices/annotationsSlice.test.ts +133 -0
- package/src/store/slices/annotationsSlice.ts +251 -0
- package/src/store/slices/dataSlice.test.ts +23 -4
- package/src/store/slices/dataSlice.ts +1 -1
- package/src/store/slices/modelSlice.test.ts +67 -9
- package/src/store/slices/modelSlice.ts +39 -7
- package/src/store/slices/mutationSlice.ts +964 -3
- package/src/store/slices/overlayCompositor.test.ts +164 -0
- package/src/store/slices/overlaySlice.test.ts +93 -0
- package/src/store/slices/overlaySlice.ts +151 -0
- package/src/store/slices/pinboardSlice.test.ts +6 -1
- package/src/store/slices/playbackSlice.ts +128 -0
- package/src/store/slices/pointCloudSlice.ts +102 -0
- package/src/store/slices/schedule-edit-helpers.test.ts +97 -0
- package/src/store/slices/schedule-edit-helpers.ts +179 -0
- package/src/store/slices/scheduleSlice.test.ts +694 -0
- package/src/store/slices/scheduleSlice.ts +1330 -0
- package/src/store/slices/searchSlice.test.ts +342 -0
- package/src/store/slices/searchSlice.ts +341 -0
- package/src/store/slices/selectionSlice.test.ts +46 -0
- package/src/store/slices/selectionSlice.ts +20 -0
- package/src/store/types.ts +7 -0
- package/src/store.ts +14 -0
- package/vite.config.ts +1 -0
- package/dist/assets/ifc-lite_bg-BINvzoCP.wasm +0 -0
- package/dist/assets/index-_bfZsDCC.css +0 -1
- package/dist/assets/sandbox-C8575tul.js +0 -5951
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
PersonStanding,
|
|
11
11
|
Ruler,
|
|
12
12
|
Scissors,
|
|
13
|
+
MapPin,
|
|
13
14
|
Eye,
|
|
14
15
|
EyeOff,
|
|
15
16
|
Equal,
|
|
@@ -37,6 +38,7 @@ import {
|
|
|
37
38
|
Layout,
|
|
38
39
|
LayoutTemplate,
|
|
39
40
|
FileCode2,
|
|
41
|
+
CalendarClock,
|
|
40
42
|
Globe2,
|
|
41
43
|
Settings,
|
|
42
44
|
} from 'lucide-react';
|
|
@@ -66,6 +68,7 @@ import { ExportDialog } from './ExportDialog';
|
|
|
66
68
|
import { BulkPropertyEditor } from './BulkPropertyEditor';
|
|
67
69
|
import { DataConnector } from './DataConnector';
|
|
68
70
|
import { ExportChangesButton } from './ExportChangesButton';
|
|
71
|
+
import { SearchInline } from './SearchInline';
|
|
69
72
|
// CesiumSettingsDialog removed — settings now shown as overlay on Cesium viewer
|
|
70
73
|
import { useFloorplanView } from '@/hooks/useFloorplanView';
|
|
71
74
|
import { buildDesktopUpgradeUrl, hasDesktopFeatureAccess, type DesktopFeature } from '@/lib/desktop-product';
|
|
@@ -84,7 +87,7 @@ import {
|
|
|
84
87
|
subscribeAnalysisExtensions,
|
|
85
88
|
} from '@/services/analysis-extensions';
|
|
86
89
|
|
|
87
|
-
type Tool = 'select' | 'walk' | 'measure' | 'section';
|
|
90
|
+
type Tool = 'select' | 'walk' | 'measure' | 'section' | 'annotate';
|
|
88
91
|
type WorkspacePanel = 'script' | 'list' | 'bcf' | 'ids' | 'lens' | string;
|
|
89
92
|
|
|
90
93
|
function isNativeFileHandle(file: File | NativeFileHandle): file is NativeFileHandle {
|
|
@@ -100,21 +103,39 @@ interface ToolButtonProps {
|
|
|
100
103
|
shortcut?: string;
|
|
101
104
|
activeTool: string;
|
|
102
105
|
onToolChange: (tool: Tool) => void;
|
|
106
|
+
/**
|
|
107
|
+
* Tailwind classes applied when this tool is active. Defaults to the
|
|
108
|
+
* shared `bg-primary text-primary-foreground` shape; pass a per-tool
|
|
109
|
+
* accent (e.g. amber for Annotate) to set tools apart visually
|
|
110
|
+
* without breaking the toolbar's tool-button rhythm.
|
|
111
|
+
*/
|
|
112
|
+
activeAccentClass?: string;
|
|
103
113
|
}
|
|
104
114
|
|
|
105
|
-
function ToolButton({
|
|
115
|
+
function ToolButton({
|
|
116
|
+
tool,
|
|
117
|
+
icon: Icon,
|
|
118
|
+
label,
|
|
119
|
+
shortcut,
|
|
120
|
+
activeTool,
|
|
121
|
+
onToolChange,
|
|
122
|
+
activeAccentClass,
|
|
123
|
+
}: ToolButtonProps) {
|
|
124
|
+
const isActive = activeTool === tool;
|
|
106
125
|
return (
|
|
107
126
|
<Tooltip>
|
|
108
127
|
<TooltipTrigger asChild>
|
|
109
128
|
<Button
|
|
110
|
-
variant={
|
|
129
|
+
variant={isActive ? 'default' : 'ghost'}
|
|
111
130
|
size="icon-sm"
|
|
112
131
|
onClick={(e) => {
|
|
113
132
|
// Blur button to close tooltip after click
|
|
114
133
|
(e.currentTarget as HTMLButtonElement).blur();
|
|
115
134
|
onToolChange(tool);
|
|
116
135
|
}}
|
|
117
|
-
className={cn(
|
|
136
|
+
className={cn(
|
|
137
|
+
isActive && (activeAccentClass ?? 'bg-primary text-primary-foreground'),
|
|
138
|
+
)}
|
|
118
139
|
>
|
|
119
140
|
<Icon className="h-4 w-4" />
|
|
120
141
|
</Button>
|
|
@@ -305,6 +326,8 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
305
326
|
const setLensPanelVisible = useViewerStore((state) => state.setLensPanelVisible);
|
|
306
327
|
const scriptPanelVisible = useViewerStore((state) => state.scriptPanelVisible);
|
|
307
328
|
const setScriptPanelVisible = useViewerStore((state) => state.setScriptPanelVisible);
|
|
329
|
+
const ganttPanelVisible = useViewerStore((state) => state.ganttPanelVisible);
|
|
330
|
+
const setGanttPanelVisible = useViewerStore((state) => state.setGanttPanelVisible);
|
|
308
331
|
// Cesium 3D overlay state
|
|
309
332
|
const cesiumAvailable = useViewerStore((state) => state.cesiumAvailable);
|
|
310
333
|
const cesiumEnabled = useViewerStore((state) => state.cesiumEnabled);
|
|
@@ -402,6 +425,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
402
425
|
// Filter to supported files (IFC, IFCX, GLB)
|
|
403
426
|
const supportedFiles = Array.from(files).filter(
|
|
404
427
|
f => f.name.endsWith('.ifc') || f.name.endsWith('.ifcx') || f.name.endsWith('.glb')
|
|
428
|
+
|| f.name.toLowerCase().endsWith('.las') || f.name.toLowerCase().endsWith('.laz') || f.name.toLowerCase().endsWith('.ply') || f.name.toLowerCase().endsWith('.pcd') || f.name.toLowerCase().endsWith('.e57')
|
|
405
429
|
);
|
|
406
430
|
|
|
407
431
|
if (supportedFiles.length === 0) return;
|
|
@@ -442,6 +466,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
442
466
|
// Filter to supported files (IFC, IFCX, GLB)
|
|
443
467
|
const supportedFiles = Array.from(files).filter(
|
|
444
468
|
f => f.name.endsWith('.ifc') || f.name.endsWith('.ifcx') || f.name.endsWith('.glb')
|
|
469
|
+
|| f.name.toLowerCase().endsWith('.las') || f.name.toLowerCase().endsWith('.laz') || f.name.toLowerCase().endsWith('.ply') || f.name.toLowerCase().endsWith('.pcd') || f.name.toLowerCase().endsWith('.e57')
|
|
445
470
|
);
|
|
446
471
|
|
|
447
472
|
if (supportedFiles.length === 0) return;
|
|
@@ -508,21 +533,31 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
508
533
|
return false;
|
|
509
534
|
}, [desktopEntitlement, promptDesktopUpgrade]);
|
|
510
535
|
|
|
511
|
-
const handleToggleBottomPanel = useCallback((panel: 'script' | 'list') => {
|
|
536
|
+
const handleToggleBottomPanel = useCallback((panel: 'script' | 'list' | 'gantt') => {
|
|
512
537
|
if (activeAnalysisExtension?.placement === 'bottom') {
|
|
513
538
|
closeActiveAnalysisExtension();
|
|
514
539
|
}
|
|
515
|
-
const
|
|
516
|
-
const
|
|
517
|
-
const
|
|
540
|
+
const nextScriptVisible = panel === 'script' ? !scriptPanelVisible : false;
|
|
541
|
+
const nextListVisible = panel === 'list' ? !listPanelVisible : false;
|
|
542
|
+
const nextGanttVisible = panel === 'gantt' ? !ganttPanelVisible : false;
|
|
518
543
|
|
|
519
544
|
setScriptPanelVisible(nextScriptVisible);
|
|
520
545
|
setListPanelVisible(nextListVisible);
|
|
546
|
+
setGanttPanelVisible(nextGanttVisible);
|
|
521
547
|
|
|
522
|
-
if (nextScriptVisible || nextListVisible) {
|
|
548
|
+
if (nextScriptVisible || nextListVisible || nextGanttVisible) {
|
|
523
549
|
setRightPanelCollapsed(false);
|
|
524
550
|
}
|
|
525
|
-
}, [
|
|
551
|
+
}, [
|
|
552
|
+
activeAnalysisExtension?.placement,
|
|
553
|
+
ganttPanelVisible,
|
|
554
|
+
listPanelVisible,
|
|
555
|
+
scriptPanelVisible,
|
|
556
|
+
setGanttPanelVisible,
|
|
557
|
+
setListPanelVisible,
|
|
558
|
+
setRightPanelCollapsed,
|
|
559
|
+
setScriptPanelVisible,
|
|
560
|
+
]);
|
|
526
561
|
|
|
527
562
|
const handleToggleRightPanel = useCallback((panel: 'bcf' | 'ids' | 'lens') => {
|
|
528
563
|
if (activeAnalysisExtension?.placement !== 'bottom') {
|
|
@@ -577,6 +612,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
577
612
|
if ((extension.placement ?? 'right') === 'bottom') {
|
|
578
613
|
setScriptPanelVisible(false);
|
|
579
614
|
setListPanelVisible(false);
|
|
615
|
+
setGanttPanelVisible(false);
|
|
580
616
|
setRightPanelCollapsed(false);
|
|
581
617
|
return;
|
|
582
618
|
}
|
|
@@ -589,6 +625,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
589
625
|
analysisExtensionState.activeId,
|
|
590
626
|
analysisExtensionState.extensions,
|
|
591
627
|
setBcfPanelVisible,
|
|
628
|
+
setGanttPanelVisible,
|
|
592
629
|
setIdsPanelVisible,
|
|
593
630
|
setLensPanelVisible,
|
|
594
631
|
setListPanelVisible,
|
|
@@ -600,6 +637,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
600
637
|
const panels = new Set<WorkspacePanel>();
|
|
601
638
|
if (scriptPanelVisible) panels.add('script');
|
|
602
639
|
if (listPanelVisible) panels.add('list');
|
|
640
|
+
if (ganttPanelVisible) panels.add('gantt');
|
|
603
641
|
if (bcfPanelVisible) panels.add('bcf');
|
|
604
642
|
if (idsPanelVisible) panels.add('ids');
|
|
605
643
|
if (lensPanelVisible) panels.add('lens');
|
|
@@ -608,6 +646,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
608
646
|
}, [
|
|
609
647
|
analysisExtensionState.activeId,
|
|
610
648
|
bcfPanelVisible,
|
|
649
|
+
ganttPanelVisible,
|
|
611
650
|
idsPanelVisible,
|
|
612
651
|
lensPanelVisible,
|
|
613
652
|
listPanelVisible,
|
|
@@ -619,6 +658,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
619
658
|
if (activeWorkspacePanels.size > 1) return 'Multiple Panels';
|
|
620
659
|
if (activeWorkspacePanels.has('script')) return 'Script Editor';
|
|
621
660
|
if (activeWorkspacePanels.has('list')) return 'Lists';
|
|
661
|
+
if (activeWorkspacePanels.has('gantt')) return 'Schedule';
|
|
622
662
|
if (activeWorkspacePanels.has('bcf')) return 'BCF Issues';
|
|
623
663
|
if (activeWorkspacePanels.has('ids')) return 'IDS Validation';
|
|
624
664
|
if (activeWorkspacePanels.has('lens')) return 'Lens Rules';
|
|
@@ -741,7 +781,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
741
781
|
id="file-input-open"
|
|
742
782
|
ref={fileInputRef}
|
|
743
783
|
type="file"
|
|
744
|
-
accept=".ifc,.ifcx,.glb"
|
|
784
|
+
accept=".ifc,.ifcx,.glb,.las,.laz,.ply,.pcd,.e57"
|
|
745
785
|
multiple
|
|
746
786
|
onChange={handleFileSelect}
|
|
747
787
|
className="hidden"
|
|
@@ -749,7 +789,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
749
789
|
<input
|
|
750
790
|
ref={addModelInputRef}
|
|
751
791
|
type="file"
|
|
752
|
-
accept=".ifc,.ifcx,.glb"
|
|
792
|
+
accept=".ifc,.ifcx,.glb,.las,.laz,.ply,.pcd,.e57"
|
|
753
793
|
multiple
|
|
754
794
|
onChange={handleAddModelSelect}
|
|
755
795
|
className="hidden"
|
|
@@ -948,6 +988,13 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
948
988
|
<FileSpreadsheet className="h-4 w-4 mr-2" />
|
|
949
989
|
Lists
|
|
950
990
|
</DropdownMenuCheckboxItem>
|
|
991
|
+
<DropdownMenuCheckboxItem
|
|
992
|
+
checked={activeWorkspacePanels.has('gantt')}
|
|
993
|
+
onCheckedChange={() => handleToggleBottomPanel('gantt')}
|
|
994
|
+
>
|
|
995
|
+
<CalendarClock className="h-4 w-4 mr-2" />
|
|
996
|
+
Schedule (Gantt)
|
|
997
|
+
</DropdownMenuCheckboxItem>
|
|
951
998
|
<DropdownMenuSeparator />
|
|
952
999
|
<DropdownMenuCheckboxItem
|
|
953
1000
|
checked={activeWorkspacePanels.has('bcf')}
|
|
@@ -1011,6 +1058,11 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1011
1058
|
|
|
1012
1059
|
<Separator orientation="vertical" className="h-6 mx-1" />
|
|
1013
1060
|
|
|
1061
|
+
{/* ── Search (Tier-0 inline; ⌘F or / to focus) ── */}
|
|
1062
|
+
<SearchInline />
|
|
1063
|
+
|
|
1064
|
+
<Separator orientation="vertical" className="h-6 mx-1" />
|
|
1065
|
+
|
|
1014
1066
|
{/* ── Navigation Tools ── */}
|
|
1015
1067
|
<ToolButton tool="select" icon={MousePointer2} label="Select" shortcut="V" activeTool={activeTool} onToolChange={setActiveTool} />
|
|
1016
1068
|
<ToolButton tool="walk" icon={PersonStanding} label="Walk Mode" shortcut="C" activeTool={activeTool} onToolChange={setActiveTool} />
|
|
@@ -1020,6 +1072,15 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1020
1072
|
{/* ── Measurement & Section ── */}
|
|
1021
1073
|
<ToolButton tool="measure" icon={Ruler} label="Measure" shortcut="M" activeTool={activeTool} onToolChange={setActiveTool} />
|
|
1022
1074
|
<ToolButton tool="section" icon={Scissors} label="Section" shortcut="X" activeTool={activeTool} onToolChange={setActiveTool} />
|
|
1075
|
+
<ToolButton
|
|
1076
|
+
tool="annotate"
|
|
1077
|
+
icon={MapPin}
|
|
1078
|
+
label="Annotate"
|
|
1079
|
+
shortcut="P"
|
|
1080
|
+
activeTool={activeTool}
|
|
1081
|
+
onToolChange={setActiveTool}
|
|
1082
|
+
activeAccentClass="bg-amber-500 text-white hover:bg-amber-500/90"
|
|
1083
|
+
/>
|
|
1023
1084
|
|
|
1024
1085
|
{/* Floorplan dropdown */}
|
|
1025
1086
|
{availableStoreys.length > 0 && (
|
|
@@ -0,0 +1,174 @@
|
|
|
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
|
+
* Compact panel that exposes point cloud rendering controls (color mode,
|
|
7
|
+
* size mode, point size, EDL). Renders only when point cloud assets are
|
|
8
|
+
* loaded — sits over the canvas without affecting layout for IFC-only
|
|
9
|
+
* models.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { useViewerStore } from '@/store';
|
|
13
|
+
import type { PointColorModeUi, PointSizeModeUi } from '@/store/slices/pointCloudSlice';
|
|
14
|
+
import { cn } from '@/lib/utils';
|
|
15
|
+
|
|
16
|
+
const COLOR_MODES: Array<{ value: PointColorModeUi; label: string; hint: string }> = [
|
|
17
|
+
{ value: 'rgb', label: 'RGB', hint: 'Per-point colour from the source' },
|
|
18
|
+
{ value: 'classification', label: 'Classification', hint: 'ASPRS class palette (ground, vegetation, building...)' },
|
|
19
|
+
{ value: 'intensity', label: 'Intensity', hint: 'Greyscale ramp from per-point intensity' },
|
|
20
|
+
{ value: 'height', label: 'Height', hint: 'Cool-warm ramp by Y-up world height' },
|
|
21
|
+
{ value: 'fixed', label: 'Solid', hint: 'Single colour override' },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const SIZE_MODES: Array<{ value: PointSizeModeUi; label: string; hint: string }> = [
|
|
25
|
+
{ value: 'fixed-px', label: 'Fixed', hint: 'Always render at the slider value (in pixels)' },
|
|
26
|
+
{ value: 'attenuated', label: 'Auto', hint: 'Adaptive (closer = bigger), clamped to the slider as max' },
|
|
27
|
+
{ value: 'adaptive-world', label: 'World', hint: 'Pure world-space radius — splat covers N mm in source space' },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export interface PointCloudPanelProps {
|
|
31
|
+
/** Number of currently-loaded point cloud assets — panel hides when 0. */
|
|
32
|
+
assetCount: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function PointCloudPanel({ assetCount }: PointCloudPanelProps) {
|
|
36
|
+
const colorMode = useViewerStore((s) => s.pointCloudColorMode);
|
|
37
|
+
const setColorMode = useViewerStore((s) => s.setPointCloudColorMode);
|
|
38
|
+
const sizeMode = useViewerStore((s) => s.pointCloudSizeMode);
|
|
39
|
+
const setSizeMode = useViewerStore((s) => s.setPointCloudSizeMode);
|
|
40
|
+
const pointSize = useViewerStore((s) => s.pointCloudPointSize);
|
|
41
|
+
const setPointSize = useViewerStore((s) => s.setPointCloudPointSize);
|
|
42
|
+
const worldRadius = useViewerStore((s) => s.pointCloudWorldRadius);
|
|
43
|
+
const setWorldRadius = useViewerStore((s) => s.setPointCloudWorldRadius);
|
|
44
|
+
const edlEnabled = useViewerStore((s) => s.pointCloudEdlEnabled);
|
|
45
|
+
const setEdlEnabled = useViewerStore((s) => s.setPointCloudEdlEnabled);
|
|
46
|
+
const edlStrength = useViewerStore((s) => s.pointCloudEdlStrength);
|
|
47
|
+
const setEdlStrength = useViewerStore((s) => s.setPointCloudEdlStrength);
|
|
48
|
+
|
|
49
|
+
if (assetCount <= 0) return null;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div className="absolute bottom-4 left-4 z-10 pointer-events-auto bg-background/90 backdrop-blur-sm rounded-lg border shadow-lg p-2 flex flex-col gap-2 min-w-[200px]">
|
|
53
|
+
<div className="flex items-center justify-between gap-2">
|
|
54
|
+
<span className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
55
|
+
Point Cloud
|
|
56
|
+
</span>
|
|
57
|
+
<span className="text-[10px] text-muted-foreground">
|
|
58
|
+
{assetCount} asset{assetCount === 1 ? '' : 's'}
|
|
59
|
+
</span>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
{/* Color mode */}
|
|
63
|
+
<div className="flex flex-col gap-0.5">
|
|
64
|
+
<span className="text-[9px] uppercase text-muted-foreground tracking-wider">Colour</span>
|
|
65
|
+
{COLOR_MODES.map((mode) => {
|
|
66
|
+
const active = colorMode === mode.value;
|
|
67
|
+
return (
|
|
68
|
+
<button
|
|
69
|
+
key={mode.value}
|
|
70
|
+
aria-pressed={active}
|
|
71
|
+
onClick={() => setColorMode(mode.value)}
|
|
72
|
+
title={mode.hint}
|
|
73
|
+
className={cn(
|
|
74
|
+
'flex items-center gap-2 px-2 py-1 rounded text-xs transition-colors text-left',
|
|
75
|
+
active
|
|
76
|
+
? 'bg-teal-600 text-white'
|
|
77
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground',
|
|
78
|
+
)}
|
|
79
|
+
>
|
|
80
|
+
{mode.label}
|
|
81
|
+
</button>
|
|
82
|
+
);
|
|
83
|
+
})}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{/* Size mode */}
|
|
87
|
+
<div className="flex flex-col gap-0.5">
|
|
88
|
+
<span className="text-[9px] uppercase text-muted-foreground tracking-wider">Size</span>
|
|
89
|
+
<div className="grid grid-cols-3 gap-0.5">
|
|
90
|
+
{SIZE_MODES.map((mode) => {
|
|
91
|
+
const active = sizeMode === mode.value;
|
|
92
|
+
return (
|
|
93
|
+
<button
|
|
94
|
+
key={mode.value}
|
|
95
|
+
aria-pressed={active}
|
|
96
|
+
onClick={() => setSizeMode(mode.value)}
|
|
97
|
+
title={mode.hint}
|
|
98
|
+
className={cn(
|
|
99
|
+
'px-1.5 py-1 rounded text-[11px] transition-colors',
|
|
100
|
+
active
|
|
101
|
+
? 'bg-teal-600 text-white'
|
|
102
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground',
|
|
103
|
+
)}
|
|
104
|
+
>
|
|
105
|
+
{mode.label}
|
|
106
|
+
</button>
|
|
107
|
+
);
|
|
108
|
+
})}
|
|
109
|
+
</div>
|
|
110
|
+
<label className="flex items-center gap-2 mt-1">
|
|
111
|
+
<span className="text-[10px] text-muted-foreground w-8 shrink-0">{pointSize.toFixed(0)}px</span>
|
|
112
|
+
<input
|
|
113
|
+
type="range"
|
|
114
|
+
min={1}
|
|
115
|
+
max={20}
|
|
116
|
+
step={1}
|
|
117
|
+
value={pointSize}
|
|
118
|
+
onChange={(e) => setPointSize(Number(e.target.value))}
|
|
119
|
+
className="flex-1 h-1 accent-teal-600 cursor-pointer"
|
|
120
|
+
title="Splat size in pixels (or upper cap in Auto mode)"
|
|
121
|
+
/>
|
|
122
|
+
</label>
|
|
123
|
+
{sizeMode !== 'fixed-px' && (
|
|
124
|
+
<label className="flex items-center gap-2">
|
|
125
|
+
<span className="text-[10px] text-muted-foreground w-8 shrink-0">
|
|
126
|
+
{(worldRadius * 1000).toFixed(0)}mm
|
|
127
|
+
</span>
|
|
128
|
+
<input
|
|
129
|
+
type="range"
|
|
130
|
+
min={1}
|
|
131
|
+
max={100}
|
|
132
|
+
step={1}
|
|
133
|
+
value={Math.round(worldRadius * 1000)}
|
|
134
|
+
onChange={(e) => setWorldRadius(Number(e.target.value) / 1000)}
|
|
135
|
+
className="flex-1 h-1 accent-teal-600 cursor-pointer"
|
|
136
|
+
title="World-space splat radius in millimetres"
|
|
137
|
+
/>
|
|
138
|
+
</label>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
{/* EDL */}
|
|
143
|
+
<div className="flex flex-col gap-0.5">
|
|
144
|
+
<label className="flex items-center justify-between gap-2 cursor-pointer">
|
|
145
|
+
<span className="text-[9px] uppercase text-muted-foreground tracking-wider">EDL</span>
|
|
146
|
+
<input
|
|
147
|
+
type="checkbox"
|
|
148
|
+
checked={edlEnabled}
|
|
149
|
+
onChange={(e) => setEdlEnabled(e.target.checked)}
|
|
150
|
+
className="accent-teal-600"
|
|
151
|
+
title="Eye-Dome Lighting — adds depth perception via screen-space depth gradient"
|
|
152
|
+
/>
|
|
153
|
+
</label>
|
|
154
|
+
{edlEnabled && (
|
|
155
|
+
<label className="flex items-center gap-2">
|
|
156
|
+
<span className="text-[10px] text-muted-foreground w-8 shrink-0">
|
|
157
|
+
{edlStrength.toFixed(1)}
|
|
158
|
+
</span>
|
|
159
|
+
<input
|
|
160
|
+
type="range"
|
|
161
|
+
min={0}
|
|
162
|
+
max={3}
|
|
163
|
+
step={0.1}
|
|
164
|
+
value={edlStrength}
|
|
165
|
+
onChange={(e) => setEdlStrength(Number(e.target.value))}
|
|
166
|
+
className="flex-1 h-1 accent-teal-600 cursor-pointer"
|
|
167
|
+
title="EDL strength multiplier"
|
|
168
|
+
/>
|
|
169
|
+
</label>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
}
|