@ifc-lite/viewer 1.27.0 → 1.28.1
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 +35 -42
- package/CHANGELOG.md +74 -0
- package/dist/assets/{basketViewActivator-B3CdrLsb.js → basketViewActivator-Ce38DhXd.js} +8 -8
- package/dist/assets/{bcf-QeHK_Aud.js → bcf-Cv_O3JfD.js} +56 -56
- package/dist/assets/{decode-worker-CgM1iNSK.js → decode-worker-Cjign7Zh.js} +1 -1
- package/dist/assets/{deflate-B-d0SYQM.js → deflate-HbyMq59o.js} +1 -1
- package/dist/assets/drawing-2d-DW98umlt.js +257 -0
- package/dist/assets/e57-source-2wI9jkCA.js +1 -0
- package/dist/assets/{exporters-B4LbZFeT.js → exporters-BuD3XRzB.js} +1309 -1153
- package/dist/assets/geometry.worker-TH3fCCoY.js +1 -0
- package/dist/assets/{geotiff-CrVtDRFq.js → geotiff-B2HA8Bwm.js} +10 -10
- package/dist/assets/{ids-DjsGFN10.js → ids-DYUFMd5f.js} +952 -945
- package/dist/assets/{ifc-lite_bg-DsYUIHm3.wasm → ifc-lite_bg-BEA5DLmg.wasm} +0 -0
- package/dist/assets/index-E9wB0zWt.css +1 -0
- package/dist/assets/{index-COYokSKc.js → index-n5O1QJMM.js} +37877 -38126
- package/dist/assets/{index.es-CY202jA3.js → index.es-BKVIpZgL.js} +9 -9
- package/dist/assets/{jpeg-D4wOkf5h.js → jpeg-C7hjKjPX.js} +1 -1
- package/dist/assets/{jspdf.es.min-DIGb9BHN.js → jspdf.es.min-oWlFc42Y.js} +4 -4
- package/dist/assets/lens-C4p1kQ0p.js +1 -0
- package/dist/assets/{lerc-DmW0_tgf.js → lerc-BfIOGhQz.js} +1 -1
- package/dist/assets/{lzw-oWetY-d6.js → lzw-B0jRuuW5.js} +1 -1
- package/dist/assets/{native-bridge-BX8_tHXE.js → native-bridge-DpB-dtEn.js} +6 -3
- package/dist/assets/{packbits-F8Nkp4NY.js → packbits-DVvBTC39.js} +1 -1
- package/dist/assets/parser.worker-BDsWQ6rc.js +182 -0
- package/dist/assets/{pdf-Dsh3HPZB.js → pdf-dVIqI5ac.js} +10 -10
- package/dist/assets/raw-C0ZJYGmN.js +1 -0
- package/dist/assets/{sandbox-BAC3a-eN.js → sandbox-qpJlrNN0.js} +2962 -2554
- package/dist/assets/server-client-DVZ2huNS.js +719 -0
- package/dist/assets/{webimage-BLV1dgmd.js → webimage-B394g0Tw.js} +1 -1
- package/dist/assets/{xlsx-Bc2HTrjC.js → xlsx-D-oHO76J.js} +8 -8
- package/dist/assets/{zstd-C_1HxVrA.js → zstd-Bf38MwV2.js} +1 -1
- package/dist/index.html +9 -9
- package/package.json +24 -23
- package/src/App.tsx +1 -3
- package/src/components/mcp/playground-dispatcher.ts +3 -0
- package/src/components/mcp/playground-files.ts +33 -1
- package/src/components/viewer/BCFPanel.tsx +1 -16
- package/src/components/viewer/ChatPanel.tsx +11 -46
- package/src/components/viewer/CommandPalette.tsx +6 -1
- package/src/components/viewer/ComparePanel.tsx +420 -0
- package/src/components/viewer/HierarchyPanel.tsx +48 -183
- package/src/components/viewer/IDSPanel.tsx +1 -26
- package/src/components/viewer/MainToolbar.tsx +94 -187
- package/src/components/viewer/MobileToolbar.tsx +1 -9
- package/src/components/viewer/PropertiesPanel.tsx +98 -127
- package/src/components/viewer/ScriptPanel.tsx +8 -34
- package/src/components/viewer/Section2DPanel.tsx +32 -1
- package/src/components/viewer/ViewerLayout.tsx +5 -2
- package/src/components/viewer/Viewport.tsx +3 -0
- package/src/components/viewer/ViewportContainer.tsx +24 -42
- package/src/components/viewer/ViewportOverlays.tsx +1 -4
- package/src/components/viewer/hierarchy/HierarchyNode.tsx +3 -3
- package/src/components/viewer/hierarchy/ifc-icons.ts +9 -0
- package/src/components/viewer/hierarchy/treeDataBuilder.ts +87 -0
- package/src/components/viewer/hierarchy/types.ts +1 -0
- package/src/components/viewer/hierarchy/useHierarchyTree.ts +6 -2
- package/src/components/viewer/properties/MaterialTotalsPanel.tsx +283 -0
- package/src/components/viewer/useGeometryStreaming.ts +0 -2
- package/src/hooks/federationLoadGate.test.ts +12 -2
- package/src/hooks/federationLoadGate.ts +9 -2
- package/src/hooks/ingest/federationAlign.ts +488 -0
- package/src/hooks/ingest/viewerModelIngest.ts +3 -212
- package/src/hooks/useCompare.ts +0 -0
- package/src/hooks/useCompareOverlay.ts +119 -0
- package/src/hooks/useDrawingGeneration.ts +234 -14
- package/src/hooks/useIfc.ts +1 -1
- package/src/hooks/useIfcCache.ts +100 -24
- package/src/hooks/useIfcFederation.ts +42 -811
- package/src/hooks/useIfcLoader.ts +349 -1517
- package/src/hooks/useIfcServer.ts +3 -0
- package/src/hooks/useLens.ts +5 -1
- package/src/hooks/useSymbolicAnnotations.ts +70 -38
- package/src/lib/compare/buildFingerprints.ts +173 -0
- package/src/lib/compare/describeChange.ts +0 -0
- package/src/lib/compare/geometricData.test.ts +54 -0
- package/src/lib/compare/geometricData.ts +37 -0
- package/src/lib/compare/overlay.test.ts +99 -0
- package/src/lib/compare/overlay.ts +91 -0
- package/src/lib/geo/cesium-placement.ts +1 -1
- package/src/lib/geo/reproject.ts +4 -1
- package/src/lib/llm/script-edit-ops.ts +23 -0
- package/src/lib/llm/stream-client.ts +8 -1
- package/src/lib/search/result-export.ts +7 -1
- package/src/sdk/adapters/export-adapter.ts +6 -1
- package/src/services/cacheService.ts +9 -25
- package/src/services/desktop-export.ts +2 -59
- package/src/services/file-dialog.ts +8 -142
- package/src/store/constants.ts +23 -0
- package/src/store/globalId.ts +15 -13
- package/src/store/index.ts +19 -6
- package/src/store/slices/cesiumSlice.ts +8 -1
- package/src/store/slices/compareSlice.ts +96 -0
- package/src/store/slices/drawing2DSlice.ts +8 -0
- package/src/store/slices/lensSlice.ts +8 -0
- package/src/store/slices/visibilitySlice.ts +22 -1
- package/src/store/types.ts +1 -71
- package/src/utils/acquireFileBuffer.test.ts +12 -4
- package/src/utils/ifcConfig.ts +0 -12
- package/src/utils/loadingUtils.ts +32 -0
- package/src/utils/spatialHierarchy.test.ts +53 -1
- package/src/utils/spatialHierarchy.ts +42 -2
- package/src/vite-env.d.ts +2 -0
- package/vite.config.ts +6 -3
- package/DESKTOP_CONTRACT_VERSION +0 -1
- package/dist/assets/drawing-2d-C71b8Ugx.js +0 -257
- package/dist/assets/e57-source-CQHxE8n3.js +0 -1
- package/dist/assets/event-B0kAzHa-.js +0 -1
- package/dist/assets/geometry.worker-BdH-E6NB.js +0 -1
- package/dist/assets/index-ajK6D32J.css +0 -1
- package/dist/assets/lens-PYsLu_MA.js +0 -1
- package/dist/assets/parser.worker-D591Zu_-.js +0 -182
- package/dist/assets/raw-D9iw0tmc.js +0 -1
- package/dist/assets/server-client-Cjwnm7il.js +0 -706
- package/dist/assets/tauri-core-stub-D8Fa-u43.js +0 -1
- package/dist/assets/tauri-dialog-stub-r7Wksg7o.js +0 -1
- package/dist/assets/tauri-fs-stub-BdeRC7aK.js +0 -1
- package/src/components/viewer/DesktopEntitlementBanner.tsx +0 -74
- package/src/components/viewer/SettingsPage.tsx +0 -581
- package/src/hooks/ingest/resolveDataStoreOrAbort.test.ts +0 -61
- package/src/hooks/ingest/resolveDataStoreOrAbort.ts +0 -28
- package/src/hooks/ingest/watchedGeometryStream.test.ts +0 -78
- package/src/hooks/ingest/watchedGeometryStream.ts +0 -76
- package/src/lib/desktop/desktopEntitlementEvents.ts +0 -39
- package/src/lib/desktop-entitlement.ts +0 -43
- package/src/lib/desktop-product.ts +0 -130
- package/src/lib/platform.ts +0 -23
- package/src/services/desktop-cache.ts +0 -186
- package/src/services/desktop-harness.ts +0 -196
- package/src/services/desktop-logger.ts +0 -20
- package/src/services/desktop-native-metadata.ts +0 -230
- package/src/services/desktop-panel-actions.ts +0 -43
- package/src/services/desktop-preferences.ts +0 -44
- package/src/services/fs-cache.ts +0 -212
- package/src/services/tauri-core-stub.ts +0 -7
- package/src/services/tauri-dialog-stub.ts +0 -7
- package/src/services/tauri-fs-stub.ts +0 -7
- package/src/services/tauri-modules.d.ts +0 -50
- package/src/store/slices/desktopEntitlementSlice.ts +0 -86
- package/src/utils/desktopModelSnapshot.ts +0 -358
- package/src/utils/nativeSpatialDataStore.ts +0 -277
- package/src-tauri/Cargo.toml +0 -29
- package/src-tauri/build.rs +0 -7
- package/src-tauri/capabilities/default.json +0 -18
- package/src-tauri/icons/128x128.png +0 -0
- package/src-tauri/icons/128x128@2x.png +0 -0
- package/src-tauri/icons/32x32.png +0 -0
- package/src-tauri/icons/Square107x107Logo.png +0 -0
- package/src-tauri/icons/Square142x142Logo.png +0 -0
- package/src-tauri/icons/Square150x150Logo.png +0 -0
- package/src-tauri/icons/Square284x284Logo.png +0 -0
- package/src-tauri/icons/Square30x30Logo.png +0 -0
- package/src-tauri/icons/Square310x310Logo.png +0 -0
- package/src-tauri/icons/Square44x44Logo.png +0 -0
- package/src-tauri/icons/Square71x71Logo.png +0 -0
- package/src-tauri/icons/Square89x89Logo.png +0 -0
- package/src-tauri/icons/StoreLogo.png +0 -0
- package/src-tauri/icons/icon.icns +0 -0
- package/src-tauri/icons/icon.ico +0 -0
- package/src-tauri/icons/icon.png +0 -0
- package/src-tauri/src/lib.rs +0 -21
- package/src-tauri/src/main.rs +0 -10
- package/src-tauri/tauri.conf.json +0 -39
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
EyeOff,
|
|
16
16
|
Equal,
|
|
17
17
|
Crosshair,
|
|
18
|
+
GitCompareArrows,
|
|
18
19
|
Home,
|
|
19
20
|
Maximize2,
|
|
20
21
|
Grid3x3,
|
|
@@ -43,13 +44,14 @@ import {
|
|
|
43
44
|
CalendarClock,
|
|
44
45
|
Globe2,
|
|
45
46
|
Move,
|
|
46
|
-
Settings,
|
|
47
47
|
PenLine,
|
|
48
48
|
Layers3,
|
|
49
49
|
SquareStack,
|
|
50
50
|
ChevronsUpDown,
|
|
51
51
|
Undo2,
|
|
52
52
|
Redo2,
|
|
53
|
+
Boxes,
|
|
54
|
+
Shapes,
|
|
53
55
|
} from 'lucide-react';
|
|
54
56
|
import { Button } from '@/components/ui/button';
|
|
55
57
|
import { Switch } from '@/components/ui/switch';
|
|
@@ -82,16 +84,10 @@ import { DataConnector } from './DataConnector';
|
|
|
82
84
|
import { ExportChangesButton } from './ExportChangesButton';
|
|
83
85
|
import { SearchInline } from './SearchInline';
|
|
84
86
|
import { useFloorplanView } from '@/hooks/useFloorplanView';
|
|
85
|
-
import { buildDesktopUpgradeUrl, hasDesktopFeatureAccess, type DesktopFeature } from '@/lib/desktop-product';
|
|
86
87
|
import { recordRecentFiles, cacheFileBlobs } from '@/lib/recent-files';
|
|
87
88
|
import { ThemeSwitch } from './ThemeSwitch';
|
|
88
89
|
import { ExtensionToolbarSlot } from '@/components/extensions/ExtensionToolbarSlot';
|
|
89
90
|
import { toast } from '@/components/ui/toast';
|
|
90
|
-
import { navigateToPath } from '@/services/app-navigation';
|
|
91
|
-
import { getStartupHarnessRequest, setActiveHarnessRequest, tryClaimStartupHarnessRequest } from '@/services/desktop-harness';
|
|
92
|
-
import { logToDesktopTerminal } from '@/services/desktop-logger';
|
|
93
|
-
import { openIfcFileDialog, type NativeFileHandle } from '@/services/file-dialog';
|
|
94
|
-
import { isTauri } from '@/lib/platform';
|
|
95
91
|
import {
|
|
96
92
|
closeActiveAnalysisExtension,
|
|
97
93
|
getAnalysisExtensionsSnapshot,
|
|
@@ -102,10 +98,6 @@ import {
|
|
|
102
98
|
type Tool = 'select' | 'walk' | 'measure' | 'section' | 'annotate' | 'addElement' | 'split';
|
|
103
99
|
type WorkspacePanel = 'script' | 'list' | 'bcf' | 'ids' | 'lens' | 'addElement' | string;
|
|
104
100
|
|
|
105
|
-
function isNativeFileHandle(file: File | NativeFileHandle): file is NativeFileHandle {
|
|
106
|
-
return typeof (file as NativeFileHandle).path === 'string';
|
|
107
|
-
}
|
|
108
|
-
|
|
109
101
|
// #region FIX: Move ToolButton OUTSIDE MainToolbar to prevent recreation on every render
|
|
110
102
|
// This fixes Radix UI Tooltip's asChild prop becoming stale during re-renders
|
|
111
103
|
interface ToolButtonProps {
|
|
@@ -437,11 +429,9 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
437
429
|
// Listen for programmatic file-load requests (from command palette recent files)
|
|
438
430
|
useEffect(() => {
|
|
439
431
|
const handler = (e: Event) => {
|
|
440
|
-
const file = (e as CustomEvent<File
|
|
432
|
+
const file = (e as CustomEvent<File>).detail;
|
|
441
433
|
if (file) {
|
|
442
|
-
recordRecentFiles([
|
|
443
|
-
? { name: file.name, size: file.size, path: file.path, modifiedMs: file.modifiedMs ?? null }
|
|
444
|
-
: { name: file.name, size: file.size }]);
|
|
434
|
+
recordRecentFiles([{ name: file.name, size: file.size }]);
|
|
445
435
|
void loadFile(file);
|
|
446
436
|
}
|
|
447
437
|
};
|
|
@@ -449,78 +439,6 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
449
439
|
return () => window.removeEventListener('ifc-lite:load-file', handler);
|
|
450
440
|
}, [loadFile]);
|
|
451
441
|
|
|
452
|
-
useEffect(() => {
|
|
453
|
-
let cancelled = false;
|
|
454
|
-
const sleep = (ms: number) => new Promise((resolve) => globalThis.setTimeout(resolve, ms));
|
|
455
|
-
|
|
456
|
-
const waitForViewerToSettle = async (label: string) => {
|
|
457
|
-
const timeoutMs = 120_000;
|
|
458
|
-
const pollMs = 100;
|
|
459
|
-
const start = performance.now();
|
|
460
|
-
while (!cancelled) {
|
|
461
|
-
const state = useViewerStore.getState();
|
|
462
|
-
const meshCount = state.geometryResult?.meshes.length ?? 0;
|
|
463
|
-
if (!state.loading && meshCount > 0) {
|
|
464
|
-
void logToDesktopTerminal(
|
|
465
|
-
'info',
|
|
466
|
-
`[DesktopHarness] ${label} settled: loading=${state.loading} meshes=${meshCount} progress=${state.progress?.phase ?? 'none'}`
|
|
467
|
-
);
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
if (performance.now() - start >= timeoutMs) {
|
|
471
|
-
throw new Error(`[DesktopHarness] Timed out waiting for ${label} to settle`);
|
|
472
|
-
}
|
|
473
|
-
await sleep(pollMs);
|
|
474
|
-
}
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
void (async () => {
|
|
478
|
-
void logToDesktopTerminal('info', '[DesktopHarness] MainToolbar startup harness effect running');
|
|
479
|
-
const request = await getStartupHarnessRequest();
|
|
480
|
-
if (!request || cancelled) {
|
|
481
|
-
void logToDesktopTerminal(
|
|
482
|
-
'info',
|
|
483
|
-
`[DesktopHarness] No startup harness request available (cancelled=${cancelled})`
|
|
484
|
-
);
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
if (!tryClaimStartupHarnessRequest(request)) {
|
|
488
|
-
void logToDesktopTerminal('info', `[DesktopHarness] Startup harness request already claimed for ${request.file.path}`);
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
void logToDesktopTerminal('info', `[DesktopHarness] Claimed startup harness request for ${request.file.path}`);
|
|
492
|
-
console.log(`[DesktopHarness] Auto-loading startup file: ${request.file.path}`);
|
|
493
|
-
if (!request.replaceFile) {
|
|
494
|
-
void logToDesktopTerminal('info', `[DesktopHarness] Calling loadFile for ${request.file.path}`);
|
|
495
|
-
await loadFile(request.file);
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
void logToDesktopTerminal(
|
|
500
|
-
'info',
|
|
501
|
-
`[DesktopHarness] Running replacement sequence first=${request.file.path} second=${request.replaceFile.path}`
|
|
502
|
-
);
|
|
503
|
-
setActiveHarnessRequest(null);
|
|
504
|
-
await loadFile(request.file);
|
|
505
|
-
await waitForViewerToSettle(`first load ${request.file.name}`);
|
|
506
|
-
if (cancelled) {
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
setActiveHarnessRequest({
|
|
511
|
-
...request,
|
|
512
|
-
file: request.replaceFile,
|
|
513
|
-
replaceFile: undefined,
|
|
514
|
-
});
|
|
515
|
-
void logToDesktopTerminal('info', `[DesktopHarness] Calling replacement loadFile for ${request.replaceFile.path}`);
|
|
516
|
-
await loadFile(request.replaceFile);
|
|
517
|
-
})();
|
|
518
|
-
|
|
519
|
-
return () => {
|
|
520
|
-
cancelled = true;
|
|
521
|
-
};
|
|
522
|
-
}, [loadFile]);
|
|
523
|
-
|
|
524
442
|
// Floorplan view
|
|
525
443
|
const { availableStoreys, activateFloorplan } = useFloorplanView();
|
|
526
444
|
|
|
@@ -540,6 +458,10 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
540
458
|
const typeVisibility = useViewerStore((state) => state.typeVisibility);
|
|
541
459
|
const toggleTypeVisibility = useViewerStore((state) => state.toggleTypeVisibility);
|
|
542
460
|
const resetTypeVisibility = useViewerStore((state) => state.resetTypeVisibility);
|
|
461
|
+
// #957 follow-up: Model/Types 3D view switch — 'model' shows placed
|
|
462
|
+
// occurrences (default), 'types' shows the type-library shapes.
|
|
463
|
+
const typeViewMode = useViewerStore((state) => state.typeViewMode);
|
|
464
|
+
const setTypeViewMode = useViewerStore((state) => state.setTypeViewMode);
|
|
543
465
|
// How many of the five class toggles are on — surfaced in the menu
|
|
544
466
|
// header so the user sees scene state at a glance.
|
|
545
467
|
const visibleClassCount = [
|
|
@@ -562,6 +484,8 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
562
484
|
const setIdsPanelVisible = useViewerStore((state) => state.setIdsPanelVisible);
|
|
563
485
|
const clashPanelVisible = useViewerStore((state) => state.clashPanelVisible);
|
|
564
486
|
const setClashPanelVisible = useViewerStore((state) => state.setClashPanelVisible);
|
|
487
|
+
const comparePanelVisible = useViewerStore((state) => state.comparePanelVisible);
|
|
488
|
+
const setComparePanelVisible = useViewerStore((state) => state.setComparePanelVisible);
|
|
565
489
|
const listPanelVisible = useViewerStore((state) => state.listPanelVisible);
|
|
566
490
|
const setListPanelVisible = useViewerStore((state) => state.setListPanelVisible);
|
|
567
491
|
const setRightPanelCollapsed = useViewerStore((state) => state.setRightPanelCollapsed);
|
|
@@ -588,7 +512,6 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
588
512
|
const cesiumPlacementEditMode = useViewerStore((state) => state.cesiumPlacementEditMode);
|
|
589
513
|
const setCesiumPlacementEditMode = useViewerStore((state) => state.setCesiumPlacementEditMode);
|
|
590
514
|
const storeModels = useViewerStore((state) => state.models);
|
|
591
|
-
const desktopEntitlement = useViewerStore((state) => state.desktopEntitlement);
|
|
592
515
|
const analysisExtensionState = useSyncExternalStore(
|
|
593
516
|
subscribeAnalysisExtensions,
|
|
594
517
|
getAnalysisExtensionsSnapshot,
|
|
@@ -606,7 +529,6 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
606
529
|
() => analysisExtensionState.extensions.filter((extension) => (extension.placement ?? 'right') === 'bottom'),
|
|
607
530
|
[analysisExtensionState.extensions],
|
|
608
531
|
);
|
|
609
|
-
const desktopShell = isTauri();
|
|
610
532
|
|
|
611
533
|
// NOTE: The Class Visibility dropdown used to gate each toggle on whether
|
|
612
534
|
// the loaded model actually contained that class (scanning meshes for
|
|
@@ -722,19 +644,6 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
722
644
|
goHomeFromStore();
|
|
723
645
|
}, []);
|
|
724
646
|
|
|
725
|
-
const promptDesktopUpgrade = useCallback((featureLabel: string) => {
|
|
726
|
-
toast.info(`${featureLabel} is available with Desktop Pro`);
|
|
727
|
-
navigateToPath(buildDesktopUpgradeUrl());
|
|
728
|
-
}, []);
|
|
729
|
-
|
|
730
|
-
const requireDesktopFeature = useCallback((feature: DesktopFeature, label: string) => {
|
|
731
|
-
if (hasDesktopFeatureAccess(desktopEntitlement, feature)) {
|
|
732
|
-
return true;
|
|
733
|
-
}
|
|
734
|
-
promptDesktopUpgrade(label);
|
|
735
|
-
return false;
|
|
736
|
-
}, [desktopEntitlement, promptDesktopUpgrade]);
|
|
737
|
-
|
|
738
647
|
const handleToggleBottomPanel = useCallback((panel: 'script' | 'list' | 'gantt') => {
|
|
739
648
|
if (activeAnalysisExtension?.placement === 'bottom') {
|
|
740
649
|
closeActiveAnalysisExtension();
|
|
@@ -761,24 +670,16 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
761
670
|
setScriptPanelVisible,
|
|
762
671
|
]);
|
|
763
672
|
|
|
764
|
-
const handleToggleRightPanel = useCallback((panel: 'bcf' | 'ids' | 'lens' | 'clash' | 'addElement' | 'extensions') => {
|
|
673
|
+
const handleToggleRightPanel = useCallback((panel: 'bcf' | 'ids' | 'lens' | 'clash' | 'compare' | 'addElement' | 'extensions') => {
|
|
765
674
|
if (activeAnalysisExtension?.placement !== 'bottom') {
|
|
766
675
|
closeActiveAnalysisExtension();
|
|
767
676
|
}
|
|
768
|
-
if (panel === 'bcf' && !requireDesktopFeature('bcf_issue_management', 'BCF issue management')) {
|
|
769
|
-
return;
|
|
770
|
-
}
|
|
771
|
-
if (panel === 'ids' && !requireDesktopFeature('ids_validation', 'IDS validation')) {
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
774
|
-
if (panel === 'extensions' && !requireDesktopFeature('extensions', 'Extensions')) {
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
677
|
|
|
778
678
|
const nextBcfVisible = panel === 'bcf' ? !bcfPanelVisible : false;
|
|
779
679
|
const nextIdsVisible = panel === 'ids' ? !idsPanelVisible : false;
|
|
780
680
|
const nextLensVisible = panel === 'lens' ? !lensPanelVisible : false;
|
|
781
681
|
const nextClashVisible = panel === 'clash' ? !clashPanelVisible : false;
|
|
682
|
+
const nextCompareVisible = panel === 'compare' ? !comparePanelVisible : false;
|
|
782
683
|
const nextExtensionsVisible = panel === 'extensions' ? !extensionsPanelVisible : false;
|
|
783
684
|
const isAddElementActive = activeTool === 'addElement';
|
|
784
685
|
const nextAddElementActive = panel === 'addElement' ? !isAddElementActive : false;
|
|
@@ -787,6 +688,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
787
688
|
setIdsPanelVisible(nextIdsVisible);
|
|
788
689
|
setLensPanelVisible(nextLensVisible);
|
|
789
690
|
setClashPanelVisible(nextClashVisible);
|
|
691
|
+
setComparePanelVisible(nextCompareVisible);
|
|
790
692
|
setExtensionsPanelVisible(nextExtensionsVisible);
|
|
791
693
|
|
|
792
694
|
if (panel === 'addElement') {
|
|
@@ -795,7 +697,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
795
697
|
setActiveTool('select');
|
|
796
698
|
}
|
|
797
699
|
|
|
798
|
-
if (nextBcfVisible || nextIdsVisible || nextLensVisible || nextClashVisible || nextExtensionsVisible || nextAddElementActive) {
|
|
700
|
+
if (nextBcfVisible || nextIdsVisible || nextLensVisible || nextClashVisible || nextCompareVisible || nextExtensionsVisible || nextAddElementActive) {
|
|
799
701
|
setRightPanelCollapsed(false);
|
|
800
702
|
}
|
|
801
703
|
}, [
|
|
@@ -803,13 +705,14 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
803
705
|
activeTool,
|
|
804
706
|
bcfPanelVisible,
|
|
805
707
|
clashPanelVisible,
|
|
708
|
+
comparePanelVisible,
|
|
806
709
|
extensionsPanelVisible,
|
|
807
710
|
idsPanelVisible,
|
|
808
711
|
lensPanelVisible,
|
|
809
|
-
requireDesktopFeature,
|
|
810
712
|
setActiveTool,
|
|
811
713
|
setBcfPanelVisible,
|
|
812
714
|
setClashPanelVisible,
|
|
715
|
+
setComparePanelVisible,
|
|
813
716
|
setExtensionsPanelVisible,
|
|
814
717
|
setIdsPanelVisible,
|
|
815
718
|
setLensPanelVisible,
|
|
@@ -877,6 +780,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
877
780
|
if (idsPanelVisible) panels.add('ids');
|
|
878
781
|
if (lensPanelVisible) panels.add('lens');
|
|
879
782
|
if (clashPanelVisible) panels.add('clash');
|
|
783
|
+
if (comparePanelVisible) panels.add('compare');
|
|
880
784
|
if (extensionsPanelVisible) panels.add('extensions');
|
|
881
785
|
if (activeTool === 'addElement') panels.add('addElement');
|
|
882
786
|
if (analysisExtensionState.activeId) panels.add(analysisExtensionState.activeId);
|
|
@@ -886,6 +790,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
886
790
|
analysisExtensionState.activeId,
|
|
887
791
|
bcfPanelVisible,
|
|
888
792
|
clashPanelVisible,
|
|
793
|
+
comparePanelVisible,
|
|
889
794
|
extensionsPanelVisible,
|
|
890
795
|
ganttPanelVisible,
|
|
891
796
|
idsPanelVisible,
|
|
@@ -904,13 +809,13 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
904
809
|
if (activeWorkspacePanels.has('ids')) return 'IDS Validation';
|
|
905
810
|
if (activeWorkspacePanels.has('lens')) return 'Lens Rules';
|
|
906
811
|
if (activeWorkspacePanels.has('clash')) return 'Clash Detection';
|
|
812
|
+
if (activeWorkspacePanels.has('compare')) return 'Compare Models';
|
|
907
813
|
if (activeWorkspacePanels.has('extensions')) return 'Extensions';
|
|
908
814
|
if (activeWorkspacePanels.has('addElement')) return 'Add Element';
|
|
909
815
|
return activeAnalysisExtension?.label ?? 'Analysis';
|
|
910
816
|
}, [activeAnalysisExtension?.label, activeWorkspacePanels]);
|
|
911
817
|
|
|
912
818
|
const handleScreenshot = useCallback(() => {
|
|
913
|
-
if (!requireDesktopFeature('exports', 'Exports')) return;
|
|
914
819
|
const canvas = document.querySelector('canvas');
|
|
915
820
|
if (!canvas) return;
|
|
916
821
|
try {
|
|
@@ -924,10 +829,9 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
924
829
|
console.error('Screenshot failed:', err);
|
|
925
830
|
toast.error('Screenshot failed');
|
|
926
831
|
}
|
|
927
|
-
}, [
|
|
832
|
+
}, []);
|
|
928
833
|
|
|
929
834
|
const handleExportCSV = useCallback((type: 'entities' | 'properties' | 'quantities' | 'spatial') => {
|
|
930
|
-
if (!requireDesktopFeature('exports', 'Exports')) return;
|
|
931
835
|
if (!ifcDataStore) return;
|
|
932
836
|
try {
|
|
933
837
|
const exporter = new CSVExporter(ifcDataStore);
|
|
@@ -965,10 +869,9 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
965
869
|
console.error('CSV export failed:', err);
|
|
966
870
|
toast.error(`CSV export failed: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
967
871
|
}
|
|
968
|
-
}, [ifcDataStore
|
|
872
|
+
}, [ifcDataStore]);
|
|
969
873
|
|
|
970
874
|
const handleExportJSON = useCallback(() => {
|
|
971
|
-
if (!requireDesktopFeature('exports', 'Exports')) return;
|
|
972
875
|
if (!ifcDataStore) return;
|
|
973
876
|
try {
|
|
974
877
|
const entities: Record<string, unknown>[] = [];
|
|
@@ -996,7 +899,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
996
899
|
console.error('JSON export failed:', err);
|
|
997
900
|
toast.error(`JSON export failed: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
998
901
|
}
|
|
999
|
-
}, [ifcDataStore
|
|
902
|
+
}, [ifcDataStore]);
|
|
1000
903
|
|
|
1001
904
|
return (
|
|
1002
905
|
<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">
|
|
@@ -1024,25 +927,9 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1024
927
|
<Button
|
|
1025
928
|
variant="ghost"
|
|
1026
929
|
size="icon-sm"
|
|
1027
|
-
onClick={
|
|
930
|
+
onClick={(e) => {
|
|
1028
931
|
// Blur button to close tooltip before opening file dialog
|
|
1029
932
|
(e.currentTarget as HTMLButtonElement).blur();
|
|
1030
|
-
|
|
1031
|
-
void logToDesktopTerminal('info', '[MainToolbar] Open file button clicked');
|
|
1032
|
-
const file = await openIfcFileDialog();
|
|
1033
|
-
if (file) {
|
|
1034
|
-
void logToDesktopTerminal('info', `[MainToolbar] Native dialog selected ${file.path}`);
|
|
1035
|
-
recordRecentFiles([{
|
|
1036
|
-
name: file.name,
|
|
1037
|
-
size: file.size,
|
|
1038
|
-
path: file.path,
|
|
1039
|
-
modifiedMs: file.modifiedMs ?? null,
|
|
1040
|
-
}]);
|
|
1041
|
-
void loadFile(file);
|
|
1042
|
-
return;
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
void logToDesktopTerminal('info', '[MainToolbar] Falling back to browser file input');
|
|
1046
933
|
fileInputRef.current?.click();
|
|
1047
934
|
}}
|
|
1048
935
|
disabled={loading}
|
|
@@ -1085,37 +972,23 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1085
972
|
</Button>
|
|
1086
973
|
</DropdownMenuTrigger>
|
|
1087
974
|
<DropdownMenuContent>
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
<
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
/>
|
|
1097
|
-
) : (
|
|
1098
|
-
<DropdownMenuItem onClick={() => promptDesktopUpgrade('Exports')}>
|
|
1099
|
-
<FileText className="h-4 w-4 mr-2" />
|
|
1100
|
-
Export IFC (with changes)
|
|
1101
|
-
</DropdownMenuItem>
|
|
1102
|
-
)}
|
|
975
|
+
<ExportDialog
|
|
976
|
+
trigger={
|
|
977
|
+
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
|
|
978
|
+
<FileText className="h-4 w-4 mr-2" />
|
|
979
|
+
Export IFC (with changes)
|
|
980
|
+
</DropdownMenuItem>
|
|
981
|
+
}
|
|
982
|
+
/>
|
|
1103
983
|
<DropdownMenuSeparator />
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
<
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
/>
|
|
1113
|
-
) : (
|
|
1114
|
-
<DropdownMenuItem onClick={() => promptDesktopUpgrade('Exports')}>
|
|
1115
|
-
<Download className="h-4 w-4 mr-2" />
|
|
1116
|
-
Export GLB (3D Model)
|
|
1117
|
-
</DropdownMenuItem>
|
|
1118
|
-
)}
|
|
984
|
+
<GLBExportDialog
|
|
985
|
+
trigger={
|
|
986
|
+
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
|
|
987
|
+
<Download className="h-4 w-4 mr-2" />
|
|
988
|
+
Export GLB (3D Model)
|
|
989
|
+
</DropdownMenuItem>
|
|
990
|
+
}
|
|
991
|
+
/>
|
|
1119
992
|
<DropdownMenuSeparator />
|
|
1120
993
|
<DropdownMenuSub>
|
|
1121
994
|
<DropdownMenuSubTrigger disabled={!ifcDataStore}>
|
|
@@ -1187,9 +1060,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1187
1060
|
</DropdownMenu>
|
|
1188
1061
|
|
|
1189
1062
|
{/* Export Changes Button - shows when there are pending mutations */}
|
|
1190
|
-
|
|
1191
|
-
<ExportChangesButton />
|
|
1192
|
-
) : null}
|
|
1063
|
+
<ExportChangesButton />
|
|
1193
1064
|
|
|
1194
1065
|
{/* ── Panels ── */}
|
|
1195
1066
|
<DropdownMenu>
|
|
@@ -1265,6 +1136,13 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1265
1136
|
<Crosshair className="h-4 w-4 mr-2" />
|
|
1266
1137
|
Clash Detection
|
|
1267
1138
|
</DropdownMenuCheckboxItem>
|
|
1139
|
+
<DropdownMenuCheckboxItem
|
|
1140
|
+
checked={activeWorkspacePanels.has('compare')}
|
|
1141
|
+
onCheckedChange={() => handleToggleRightPanel('compare')}
|
|
1142
|
+
>
|
|
1143
|
+
<GitCompareArrows className="h-4 w-4 mr-2" />
|
|
1144
|
+
Compare Models
|
|
1145
|
+
</DropdownMenuCheckboxItem>
|
|
1268
1146
|
<DropdownMenuSeparator />
|
|
1269
1147
|
<DropdownMenuLabel className="text-[10px] uppercase tracking-wide text-muted-foreground">
|
|
1270
1148
|
Author
|
|
@@ -1532,6 +1410,51 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1532
1410
|
model lacks is a no-op.
|
|
1533
1411
|
*/}
|
|
1534
1412
|
<DropdownMenuContent align="start" className="w-[300px] p-1.5">
|
|
1413
|
+
{/* Model / Types 3D view switch (#957 follow-up). A type carries a
|
|
1414
|
+
RepresentationMap whose shape is drawn at its MappingOrigin; "Types"
|
|
1415
|
+
shows that type library, "Model" shows the placed occurrences. The
|
|
1416
|
+
two are mutually exclusive — toggling re-filters the cached mesh set
|
|
1417
|
+
instantly (no reload). */}
|
|
1418
|
+
<div className="px-1.5 pb-1 pt-0.5">
|
|
1419
|
+
<span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
1420
|
+
3D View
|
|
1421
|
+
</span>
|
|
1422
|
+
</div>
|
|
1423
|
+
<div className="flex gap-1 px-1.5 pb-1.5" role="radiogroup" aria-label="3D view mode">
|
|
1424
|
+
<button
|
|
1425
|
+
type="button"
|
|
1426
|
+
role="radio"
|
|
1427
|
+
aria-checked={typeViewMode === 'model'}
|
|
1428
|
+
onClick={() => setTypeViewMode('model')}
|
|
1429
|
+
className={cn(
|
|
1430
|
+
'flex flex-1 items-center justify-center gap-1.5 rounded-md border px-2 py-1.5 text-xs font-medium transition-colors',
|
|
1431
|
+
typeViewMode === 'model'
|
|
1432
|
+
? 'border-primary/40 bg-primary/10 text-foreground'
|
|
1433
|
+
: 'border-transparent text-muted-foreground hover:bg-muted/50',
|
|
1434
|
+
)}
|
|
1435
|
+
>
|
|
1436
|
+
<Boxes className="h-3.5 w-3.5 shrink-0" />
|
|
1437
|
+
Model
|
|
1438
|
+
</button>
|
|
1439
|
+
<button
|
|
1440
|
+
type="button"
|
|
1441
|
+
role="radio"
|
|
1442
|
+
aria-checked={typeViewMode === 'types'}
|
|
1443
|
+
onClick={() => setTypeViewMode('types')}
|
|
1444
|
+
className={cn(
|
|
1445
|
+
'flex flex-1 items-center justify-center gap-1.5 rounded-md border px-2 py-1.5 text-xs font-medium transition-colors',
|
|
1446
|
+
typeViewMode === 'types'
|
|
1447
|
+
? 'border-primary/40 bg-primary/10 text-foreground'
|
|
1448
|
+
: 'border-transparent text-muted-foreground hover:bg-muted/50',
|
|
1449
|
+
)}
|
|
1450
|
+
>
|
|
1451
|
+
<Shapes className="h-3.5 w-3.5 shrink-0" />
|
|
1452
|
+
Types
|
|
1453
|
+
</button>
|
|
1454
|
+
</div>
|
|
1455
|
+
|
|
1456
|
+
<DropdownMenuSeparator className="my-1" />
|
|
1457
|
+
|
|
1535
1458
|
<div className="flex items-center justify-between gap-2 px-1.5 pb-1 pt-0.5">
|
|
1536
1459
|
<span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
1537
1460
|
Visibility
|
|
@@ -1621,7 +1544,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1621
1544
|
appears beside it (its amber tint signals a modal pose whose
|
|
1622
1545
|
exit affordance must stay visible).
|
|
1623
1546
|
*/}
|
|
1624
|
-
{cesiumAvailable &&
|
|
1547
|
+
{cesiumAvailable && (
|
|
1625
1548
|
<>
|
|
1626
1549
|
<Tooltip>
|
|
1627
1550
|
<TooltipTrigger asChild>
|
|
@@ -1783,22 +1706,6 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1783
1706
|
the toolbar's meta cluster stays focused on shell chrome
|
|
1784
1707
|
(Settings · Theme · Help). */}
|
|
1785
1708
|
<div className="flex items-center gap-2 ml-2 pl-2 border-l border-zinc-200 dark:border-zinc-700/60">
|
|
1786
|
-
{desktopShell ? (
|
|
1787
|
-
<Tooltip>
|
|
1788
|
-
<TooltipTrigger asChild>
|
|
1789
|
-
<Button
|
|
1790
|
-
variant="ghost"
|
|
1791
|
-
size="icon"
|
|
1792
|
-
className="rounded-full"
|
|
1793
|
-
onClick={() => navigateToPath('/settings')}
|
|
1794
|
-
>
|
|
1795
|
-
<Settings className="!h-[20px] !w-[20px]" />
|
|
1796
|
-
</Button>
|
|
1797
|
-
</TooltipTrigger>
|
|
1798
|
-
<TooltipContent>Settings</TooltipContent>
|
|
1799
|
-
</Tooltip>
|
|
1800
|
-
) : null}
|
|
1801
|
-
|
|
1802
1709
|
<Tooltip>
|
|
1803
1710
|
<TooltipTrigger asChild>
|
|
1804
1711
|
<div>
|
|
@@ -44,8 +44,6 @@ import { executeBasketIsolate } from '@/store/basket/basketCommands';
|
|
|
44
44
|
import { useIfc } from '@/hooks/useIfc';
|
|
45
45
|
import { cn } from '@/lib/utils';
|
|
46
46
|
import { GLTFExporter } from '@ifc-lite/export';
|
|
47
|
-
import { openIfcFileDialog } from '@/services/file-dialog';
|
|
48
|
-
import { logToDesktopTerminal } from '@/services/desktop-logger';
|
|
49
47
|
import { recordRecentFiles, cacheFileBlobs } from '@/lib/recent-files';
|
|
50
48
|
import { toast } from '@/components/ui/toast';
|
|
51
49
|
|
|
@@ -181,13 +179,7 @@ export function MobileToolbar() {
|
|
|
181
179
|
variant="ghost"
|
|
182
180
|
size="icon-sm"
|
|
183
181
|
className="h-9 w-9 flex-shrink-0"
|
|
184
|
-
onClick={
|
|
185
|
-
const file = await openIfcFileDialog();
|
|
186
|
-
if (file) {
|
|
187
|
-
recordRecentFiles([{ name: file.name, size: file.size, path: file.path, modifiedMs: file.modifiedMs ?? null }]);
|
|
188
|
-
void loadFile(file);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
182
|
+
onClick={() => {
|
|
191
183
|
fileInputRef.current?.click();
|
|
192
184
|
}}
|
|
193
185
|
disabled={loading}
|