@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
|
@@ -32,11 +32,10 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
|
|
|
32
32
|
import { useViewerStore } from '@/store';
|
|
33
33
|
import { toGlobalIdFromModels } from '@/store/globalId';
|
|
34
34
|
import { useIfc } from '@/hooks/useIfc';
|
|
35
|
-
import { getNativeEntityDetails } from '@/services/desktop-native-metadata';
|
|
36
35
|
import { configureMutationView } from '@/utils/configureMutationView';
|
|
37
36
|
import { IfcQuery } from '@ifc-lite/query';
|
|
38
37
|
import { MutablePropertyView } from '@ifc-lite/mutations';
|
|
39
|
-
import { extractClassificationsOnDemand, extractMaterialsOnDemand, extractTypePropertiesOnDemand, extractTypeEntityOwnProperties, extractDocumentsOnDemand, extractRelationshipsOnDemand, extractGeoreferencingOnDemand, extractLengthUnitScale, getAttributeNames, type IfcDataStore } from '@ifc-lite/parser';
|
|
38
|
+
import { extractClassificationsOnDemand, extractMaterialsOnDemand, extractMaterialPropertiesOnDemand, extractTypePropertiesOnDemand, extractTypeEntityOwnProperties, extractDocumentsOnDemand, extractRelationshipsOnDemand, extractGeoreferencingOnDemand, extractLengthUnitScale, getAttributeNames, type IfcDataStore, type MaterialPsetGroup } from '@ifc-lite/parser';
|
|
40
39
|
import type { NewEntity } from '@ifc-lite/mutations';
|
|
41
40
|
import { EntityFlags, RelationshipType, isSpatialStructureTypeName, isStoreyLikeSpatialTypeName } from '@ifc-lite/data';
|
|
42
41
|
import type { EntityRef, FederatedModel } from '@/store/types';
|
|
@@ -47,6 +46,7 @@ import { QuantitySetCard } from './properties/QuantitySetCard';
|
|
|
47
46
|
import { ModelMetadataPanel } from './properties/ModelMetadataPanel';
|
|
48
47
|
import { ClassificationCard } from './properties/ClassificationCard';
|
|
49
48
|
import { MaterialCard } from './properties/MaterialCard';
|
|
49
|
+
import { MaterialTotalsPanel } from './properties/MaterialTotalsPanel';
|
|
50
50
|
import { ScheduleCard } from './properties/ScheduleCard';
|
|
51
51
|
import { TaskEditCard } from './properties/TaskEditCard';
|
|
52
52
|
import { DocumentCard } from './properties/DocumentCard';
|
|
@@ -56,6 +56,17 @@ import { BsddCard } from './properties/BsddCard';
|
|
|
56
56
|
import { GeoreferencingPanel } from './properties/GeoreferencingPanel';
|
|
57
57
|
import { RawStepCard } from './properties/RawStepCard';
|
|
58
58
|
|
|
59
|
+
/** IFC material *definition* classes selectable from the Materials tab. */
|
|
60
|
+
const MATERIAL_DEF_TYPES = new Set([
|
|
61
|
+
'IFCMATERIAL',
|
|
62
|
+
'IFCMATERIALLAYERSET',
|
|
63
|
+
'IFCMATERIALLAYERSETUSAGE',
|
|
64
|
+
'IFCMATERIALPROFILESET',
|
|
65
|
+
'IFCMATERIALPROFILESETUSAGE',
|
|
66
|
+
'IFCMATERIALCONSTITUENTSET',
|
|
67
|
+
'IFCMATERIALLIST',
|
|
68
|
+
]);
|
|
69
|
+
|
|
59
70
|
type DisplayProperty = { name: string; value: unknown; isMutated: boolean };
|
|
60
71
|
type DisplayPropertySet = {
|
|
61
72
|
name: string;
|
|
@@ -150,7 +161,7 @@ export function PropertiesPanel() {
|
|
|
150
161
|
const m = models.get(selectedEntity.modelId);
|
|
151
162
|
if (m) {
|
|
152
163
|
return {
|
|
153
|
-
modelQuery: m.
|
|
164
|
+
modelQuery: m.ifcDataStore ? new IfcQuery(m.ifcDataStore) : null,
|
|
154
165
|
model: m,
|
|
155
166
|
};
|
|
156
167
|
}
|
|
@@ -189,8 +200,6 @@ export function PropertiesPanel() {
|
|
|
189
200
|
const [copied, setCopied] = useState(false);
|
|
190
201
|
const [coordCopied, setCoordCopied] = useState<string | null>(null);
|
|
191
202
|
const [coordOpen, setCoordOpen] = useState(false);
|
|
192
|
-
const [nativeDetails, setNativeDetails] = useState<import('@/store/types').NativeMetadataEntityDetails | null>(null);
|
|
193
|
-
const [nativeDetailsState, setNativeDetailsState] = useState<'idle' | 'loading' | 'error'>('idle');
|
|
194
203
|
|
|
195
204
|
// Inline property editing is gated by the global edit-mode pill in
|
|
196
205
|
// the main toolbar (see `uiSlice.editEnabled`). Reading it from the
|
|
@@ -199,32 +208,6 @@ export function PropertiesPanel() {
|
|
|
199
208
|
// tools — behind a single switch.
|
|
200
209
|
const editMode = useViewerStore((s) => s.editEnabled);
|
|
201
210
|
|
|
202
|
-
useEffect(() => {
|
|
203
|
-
if (!selectedEntity || !model?.nativeMetadata) {
|
|
204
|
-
setNativeDetails(null);
|
|
205
|
-
setNativeDetailsState('idle');
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
let cancelled = false;
|
|
209
|
-
setNativeDetailsState('loading');
|
|
210
|
-
void getNativeEntityDetails(model.nativeMetadata.cacheKey, selectedEntity.expressId)
|
|
211
|
-
.then((details) => {
|
|
212
|
-
if (!cancelled) {
|
|
213
|
-
setNativeDetails(details);
|
|
214
|
-
setNativeDetailsState('idle');
|
|
215
|
-
}
|
|
216
|
-
})
|
|
217
|
-
.catch(() => {
|
|
218
|
-
if (!cancelled) {
|
|
219
|
-
setNativeDetails(null);
|
|
220
|
-
setNativeDetailsState('error');
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
return () => {
|
|
224
|
-
cancelled = true;
|
|
225
|
-
};
|
|
226
|
-
}, [selectedEntity, model?.nativeMetadata]);
|
|
227
|
-
|
|
228
211
|
const copyToClipboard = useCallback((text: string) => {
|
|
229
212
|
navigator.clipboard.writeText(text);
|
|
230
213
|
setCopied(true);
|
|
@@ -450,6 +433,16 @@ export function PropertiesPanel() {
|
|
|
450
433
|
return typeName.endsWith('Type');
|
|
451
434
|
}, [selectedEntity, model, ifcDataStore]);
|
|
452
435
|
|
|
436
|
+
// Detect a material definition selected from the "Materials" hierarchy tab.
|
|
437
|
+
// Materials aren't products, so the EntityTable's getTypeName doesn't cover
|
|
438
|
+
// them — read the raw class from the entity index instead.
|
|
439
|
+
const selectedMaterialId = useMemo(() => {
|
|
440
|
+
if (!selectedEntity) return null;
|
|
441
|
+
const dataStore = model?.ifcDataStore ?? ifcDataStore;
|
|
442
|
+
const rawType = (dataStore as IfcDataStore | null)?.entityIndex?.byId?.get(selectedEntity.expressId)?.type;
|
|
443
|
+
return rawType && MATERIAL_DEF_TYPES.has(rawType.toUpperCase()) ? selectedEntity.expressId : null;
|
|
444
|
+
}, [selectedEntity, model, ifcDataStore]);
|
|
445
|
+
|
|
453
446
|
// Unified property/quantity access - EntityNode handles on-demand extraction automatically
|
|
454
447
|
// These hooks must be called before any early return to maintain hook order
|
|
455
448
|
// Use MutablePropertyView as primary source when available (it handles base + mutations)
|
|
@@ -627,6 +620,17 @@ export function PropertiesPanel() {
|
|
|
627
620
|
return extractMaterialsOnDemand(dataStore as IfcDataStore, lookupExpressId);
|
|
628
621
|
}, [selectedEntity, lookupExpressId, model, ifcDataStore]);
|
|
629
622
|
|
|
623
|
+
// Property sets attached to the selected entity's material(s) via
|
|
624
|
+
// IfcMaterialProperties (e.g. Pset_MaterialConcrete). These live on the
|
|
625
|
+
// IfcMaterial — not on the object — so they never surface through the
|
|
626
|
+
// occurrence/type pset paths; resolve them through the material association.
|
|
627
|
+
const materialProperties: MaterialPsetGroup[] = useMemo(() => {
|
|
628
|
+
if (!selectedEntity || lookupExpressId === null) return [];
|
|
629
|
+
const dataStore = model?.ifcDataStore ?? ifcDataStore;
|
|
630
|
+
if (!dataStore) return [];
|
|
631
|
+
return extractMaterialPropertiesOnDemand(dataStore as IfcDataStore, lookupExpressId);
|
|
632
|
+
}, [selectedEntity, lookupExpressId, model, ifcDataStore]);
|
|
633
|
+
|
|
630
634
|
// Extract documents for the selected entity from the IFC data store
|
|
631
635
|
const documents = useMemo(() => {
|
|
632
636
|
if (!selectedEntity || lookupExpressId === null) return [];
|
|
@@ -925,51 +929,6 @@ export function PropertiesPanel() {
|
|
|
925
929
|
return names;
|
|
926
930
|
}, [attributes]);
|
|
927
931
|
|
|
928
|
-
const isNativeLazySelection = Boolean(selectedEntity && model?.nativeMetadata);
|
|
929
|
-
|
|
930
|
-
// Native-lazy entities (server-streamed without full STEP data) can
|
|
931
|
-
// never be edited; the per-row editors below self-guard via
|
|
932
|
-
// `enableEditing={editMode && !isNativeLazySelection}`. The old
|
|
933
|
-
// panel-local `editMode` would flip itself off when a native-lazy
|
|
934
|
-
// entity was selected; now that the flag is global it stays on, and
|
|
935
|
-
// the field-level guards do the work.
|
|
936
|
-
|
|
937
|
-
const nativeSpatialInfo = useMemo(() => {
|
|
938
|
-
if (!nativeDetails?.spatial?.storeyName) return null;
|
|
939
|
-
return {
|
|
940
|
-
storeyId: nativeDetails.spatial.storeyId ?? undefined,
|
|
941
|
-
storeyName: nativeDetails.spatial.storeyName,
|
|
942
|
-
elevation: nativeDetails.spatial.elevation ?? undefined,
|
|
943
|
-
height: nativeDetails.spatial.height ?? undefined,
|
|
944
|
-
};
|
|
945
|
-
}, [nativeDetails]);
|
|
946
|
-
|
|
947
|
-
const nativeOccurrenceProperties = useMemo<PropertySet[]>(() => {
|
|
948
|
-
if (!nativeDetails) return [];
|
|
949
|
-
return nativeDetails.properties.map((pset) => ({
|
|
950
|
-
name: pset.name,
|
|
951
|
-
properties: pset.properties.map((property) => ({
|
|
952
|
-
name: property.name,
|
|
953
|
-
value: property.value,
|
|
954
|
-
isMutated: false,
|
|
955
|
-
})),
|
|
956
|
-
isNewPset: false,
|
|
957
|
-
source: 'instance' as const,
|
|
958
|
-
}));
|
|
959
|
-
}, [nativeDetails]);
|
|
960
|
-
|
|
961
|
-
const nativeQuantities = useMemo<QuantitySet[]>(() => {
|
|
962
|
-
if (!nativeDetails) return [];
|
|
963
|
-
return nativeDetails.quantities.map((qset) => ({
|
|
964
|
-
name: qset.name,
|
|
965
|
-
quantities: qset.quantities.map((quantity) => ({
|
|
966
|
-
name: quantity.name,
|
|
967
|
-
value: quantity.value,
|
|
968
|
-
type: quantity.type ?? 0,
|
|
969
|
-
})),
|
|
970
|
-
}));
|
|
971
|
-
}, [nativeDetails]);
|
|
972
|
-
|
|
973
932
|
// Overlay (authored) entities — split halves, duplicates, scripted
|
|
974
933
|
// adds — live only in the StoreEditor overlay, NOT the parsed store.
|
|
975
934
|
// `modelQuery.entity()` always returns a node, and its getters fall
|
|
@@ -977,48 +936,27 @@ export function PropertiesPanel() {
|
|
|
977
936
|
// table (entity-table.ts#getTypeName). Those non-null sentinels would
|
|
978
937
|
// shadow the overlay record in an `entityNode ?? overlay` chain, so
|
|
979
938
|
// when an overlay record exists it MUST take precedence.
|
|
980
|
-
const renderedEntityType =
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
const
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
const
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
const
|
|
996
|
-
const
|
|
997
|
-
const
|
|
998
|
-
const
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
const renderedQuantities = isNativeLazySelection ? nativeQuantities : quantities;
|
|
1002
|
-
const renderedAttributes = isNativeLazySelection ? [] : attributes;
|
|
1003
|
-
const renderedClassifications = isNativeLazySelection ? [] : classifications;
|
|
1004
|
-
const renderedMaterialInfo = isNativeLazySelection ? null : materialInfo;
|
|
1005
|
-
const renderedDocuments = isNativeLazySelection ? [] : documents;
|
|
1006
|
-
const renderedEntityRelationships = isNativeLazySelection ? null : entityRelationships;
|
|
1007
|
-
const renderedGeoref = isNativeLazySelection ? null : georef;
|
|
1008
|
-
const renderedSpatialContainment = isNativeLazySelection ? null : spatialContainment;
|
|
1009
|
-
const renderedTypeProperties = isNativeLazySelection
|
|
1010
|
-
? (nativeDetails?.typeSummary
|
|
1011
|
-
? {
|
|
1012
|
-
typeName: nativeDetails.typeSummary.name,
|
|
1013
|
-
typeId: nativeDetails.typeSummary.expressId,
|
|
1014
|
-
psets: [] as PropertySet[],
|
|
1015
|
-
}
|
|
1016
|
-
: null)
|
|
1017
|
-
: typeProperties;
|
|
1018
|
-
const renderedTypeEditImpact = isNativeLazySelection ? null : typeEditImpact;
|
|
1019
|
-
const renderedIsTypeEntity = isNativeLazySelection
|
|
1020
|
-
? ((nativeDetails?.summary.type ?? '').endsWith('Type'))
|
|
1021
|
-
: isTypeEntity;
|
|
939
|
+
const renderedEntityType = overlayEntity?.type ?? entityNode?.type ?? 'Unknown';
|
|
940
|
+
const renderedEntityName = overlayAttr(2) ?? entityNode?.name ?? undefined;
|
|
941
|
+
const renderedEntityGlobalId = overlayAttr(0) ?? entityNode?.globalId;
|
|
942
|
+
const renderedEntityDescription = overlayAttr(3) ?? entityNode?.description ?? undefined;
|
|
943
|
+
const renderedEntityObjectType = overlayAttr(4) ?? entityNode?.objectType ?? undefined;
|
|
944
|
+
const renderedSpatialInfo = spatialInfo;
|
|
945
|
+
const renderedOccurrenceProperties = occurrenceProperties;
|
|
946
|
+
const renderedInheritedTypeProperties = inheritedTypeProperties;
|
|
947
|
+
const renderedMergedProperties = mergedProperties;
|
|
948
|
+
const renderedQuantities = quantities;
|
|
949
|
+
const renderedAttributes = attributes;
|
|
950
|
+
const renderedClassifications = classifications;
|
|
951
|
+
const renderedMaterialInfo = materialInfo;
|
|
952
|
+
const renderedMaterialProperties = materialProperties;
|
|
953
|
+
const renderedDocuments = documents;
|
|
954
|
+
const renderedEntityRelationships = entityRelationships;
|
|
955
|
+
const renderedGeoref = georef;
|
|
956
|
+
const renderedSpatialContainment = spatialContainment;
|
|
957
|
+
const renderedTypeProperties = typeProperties;
|
|
958
|
+
const renderedTypeEditImpact = typeEditImpact;
|
|
959
|
+
const renderedIsTypeEntity = isTypeEntity;
|
|
1022
960
|
const renderedExistingProps = useMemo(() => {
|
|
1023
961
|
const keys = new Set<string>();
|
|
1024
962
|
for (const pset of renderedMergedProperties) {
|
|
@@ -1053,6 +991,12 @@ export function PropertiesPanel() {
|
|
|
1053
991
|
}
|
|
1054
992
|
}
|
|
1055
993
|
|
|
994
|
+
// Material selected from the "Materials" hierarchy tab — show the material's
|
|
995
|
+
// own property sets plus quantities aggregated across all using elements.
|
|
996
|
+
if (selectedMaterialId !== null && selectedEntity) {
|
|
997
|
+
return <MaterialTotalsPanel materialId={selectedMaterialId} modelId={selectedEntity.modelId} />;
|
|
998
|
+
}
|
|
999
|
+
|
|
1056
1000
|
// Multi-entity selection (unified storeys) - render combined view
|
|
1057
1001
|
if (selectedEntities.length > 1) {
|
|
1058
1002
|
return (
|
|
@@ -1069,7 +1013,7 @@ export function PropertiesPanel() {
|
|
|
1069
1013
|
// `overlayEntity` when `entityNode` is empty. Without including
|
|
1070
1014
|
// `overlayEntity` here the panel collapses to the model-metadata
|
|
1071
1015
|
// view the moment a fresh add lands.
|
|
1072
|
-
if (!selectedEntityId ||
|
|
1016
|
+
if (!selectedEntityId || !modelQuery || (!entityNode && !overlayEntity)) {
|
|
1073
1017
|
// Show model metadata when a single model is loaded and nothing selected.
|
|
1074
1018
|
// Handles both federated models (models.size >= 1) and legacy single-model path (models.size === 0).
|
|
1075
1019
|
if (models.size === 1) {
|
|
@@ -1461,7 +1405,7 @@ export function PropertiesPanel() {
|
|
|
1461
1405
|
</div>
|
|
1462
1406
|
)}
|
|
1463
1407
|
{/* Edit toolbar - only shown when edit mode is active */}
|
|
1464
|
-
{editMode && selectedEntity &&
|
|
1408
|
+
{editMode && selectedEntity && (
|
|
1465
1409
|
<>
|
|
1466
1410
|
<GeometryEditCard
|
|
1467
1411
|
modelId={selectedEntity.modelId}
|
|
@@ -1481,6 +1425,7 @@ export function PropertiesPanel() {
|
|
|
1481
1425
|
{renderedMergedProperties.length === 0
|
|
1482
1426
|
&& renderedClassifications.length === 0
|
|
1483
1427
|
&& !renderedMaterialInfo
|
|
1428
|
+
&& renderedMaterialProperties.length === 0
|
|
1484
1429
|
&& renderedDocuments.length === 0
|
|
1485
1430
|
&& !renderedEntityRelationships
|
|
1486
1431
|
&& !hasScheduleForSelection ? (
|
|
@@ -1506,7 +1451,7 @@ export function PropertiesPanel() {
|
|
|
1506
1451
|
pset={pset}
|
|
1507
1452
|
modelId={selectedEntity?.modelId}
|
|
1508
1453
|
entityId={selectedEntity?.expressId}
|
|
1509
|
-
enableEditing={editMode
|
|
1454
|
+
enableEditing={editMode}
|
|
1510
1455
|
isTypeProperty={renderedIsTypeEntity}
|
|
1511
1456
|
typeEditScope={renderedIsTypeEntity ? renderedTypeEditImpact ?? undefined : undefined}
|
|
1512
1457
|
/>
|
|
@@ -1530,7 +1475,7 @@ export function PropertiesPanel() {
|
|
|
1530
1475
|
pset={pset}
|
|
1531
1476
|
modelId={selectedEntity?.modelId}
|
|
1532
1477
|
entityId={renderedTypeProperties.typeId}
|
|
1533
|
-
enableEditing={editMode
|
|
1478
|
+
enableEditing={editMode}
|
|
1534
1479
|
isTypeProperty
|
|
1535
1480
|
typeEditScope={renderedTypeEditImpact?.mode === 'inherited' ? renderedTypeEditImpact : undefined}
|
|
1536
1481
|
/>
|
|
@@ -1560,10 +1505,38 @@ export function PropertiesPanel() {
|
|
|
1560
1505
|
</>
|
|
1561
1506
|
)}
|
|
1562
1507
|
|
|
1508
|
+
{/* Material Property Sets (Pset_Material* attached to the
|
|
1509
|
+
IfcMaterial via IfcMaterialProperties). Grouped per material,
|
|
1510
|
+
mirroring the Type Properties block. */}
|
|
1511
|
+
{renderedMaterialProperties.length > 0 && (
|
|
1512
|
+
<>
|
|
1513
|
+
{(renderedMergedProperties.length > 0 || renderedClassifications.length > 0 || renderedMaterialInfo) && (
|
|
1514
|
+
<div className="border-t border-amber-200 dark:border-amber-800/50 pt-2 mt-2" />
|
|
1515
|
+
)}
|
|
1516
|
+
{renderedMaterialProperties.map((group) => (
|
|
1517
|
+
<div key={`matpset-${group.materialId}`} className="space-y-3">
|
|
1518
|
+
<div className="flex items-center gap-2 px-1 pb-0.5 text-[11px] text-amber-600/70 dark:text-amber-400/60 uppercase tracking-wider font-semibold">
|
|
1519
|
+
<Layers className="h-3 w-3 shrink-0" />
|
|
1520
|
+
<span className="truncate">Material Properties ({group.materialName})</span>
|
|
1521
|
+
</div>
|
|
1522
|
+
{group.psets.map((pset) => (
|
|
1523
|
+
<PropertySetCard
|
|
1524
|
+
key={`matpset-${group.materialId}-${pset.name}`}
|
|
1525
|
+
pset={{
|
|
1526
|
+
name: pset.name,
|
|
1527
|
+
properties: pset.properties.map((p) => ({ name: p.name, value: p.value, isMutated: false })),
|
|
1528
|
+
}}
|
|
1529
|
+
/>
|
|
1530
|
+
))}
|
|
1531
|
+
</div>
|
|
1532
|
+
))}
|
|
1533
|
+
</>
|
|
1534
|
+
)}
|
|
1535
|
+
|
|
1563
1536
|
{/* Documents */}
|
|
1564
1537
|
{renderedDocuments.length > 0 && (
|
|
1565
1538
|
<>
|
|
1566
|
-
{(renderedMergedProperties.length > 0 || renderedClassifications.length > 0 || renderedMaterialInfo) && (
|
|
1539
|
+
{(renderedMergedProperties.length > 0 || renderedClassifications.length > 0 || renderedMaterialInfo || renderedMaterialProperties.length > 0) && (
|
|
1567
1540
|
<div className="border-t border-zinc-200 dark:border-zinc-800 pt-2 mt-2" />
|
|
1568
1541
|
)}
|
|
1569
1542
|
{renderedDocuments.map((doc, i) => (
|
|
@@ -1626,7 +1599,7 @@ export function PropertiesPanel() {
|
|
|
1626
1599
|
</TabsContent>
|
|
1627
1600
|
|
|
1628
1601
|
<TabsContent value="raw-step" className="m-0 p-3 overflow-hidden">
|
|
1629
|
-
{selectedEntity
|
|
1602
|
+
{selectedEntity ? (
|
|
1630
1603
|
<RawStepCard
|
|
1631
1604
|
modelId={selectedEntity.modelId === 'legacy' ? '__legacy__' : selectedEntity.modelId}
|
|
1632
1605
|
entityId={selectedEntity.expressId}
|
|
@@ -1636,9 +1609,7 @@ export function PropertiesPanel() {
|
|
|
1636
1609
|
/>
|
|
1637
1610
|
) : (
|
|
1638
1611
|
<p className="text-sm text-zinc-500 dark:text-zinc-500 text-center py-8 font-mono">
|
|
1639
|
-
|
|
1640
|
-
? 'Raw STEP is not available for native-metadata selections'
|
|
1641
|
-
: 'Select an entity to inspect raw STEP arguments'}
|
|
1612
|
+
Select an entity to inspect raw STEP arguments
|
|
1642
1613
|
</p>
|
|
1643
1614
|
)}
|
|
1644
1615
|
</TabsContent>
|
|
@@ -49,13 +49,10 @@ import {
|
|
|
49
49
|
DialogDescription,
|
|
50
50
|
} from '@/components/ui/dialog';
|
|
51
51
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
52
|
-
import { toast } from '@/components/ui/toast';
|
|
53
|
-
import { buildDesktopUpgradeUrl, hasDesktopFeatureAccess } from '@/lib/desktop-product';
|
|
54
52
|
import { cn, formatDuration } from '@/lib/utils';
|
|
55
53
|
import { useViewerStore } from '@/store';
|
|
56
54
|
import { useSandbox } from '@/hooks/useSandbox';
|
|
57
55
|
import { SCRIPT_TEMPLATES } from '@/lib/scripts/templates';
|
|
58
|
-
import { navigateToPath } from '@/services/app-navigation';
|
|
59
56
|
import { CodeEditor } from './CodeEditor';
|
|
60
57
|
import { ChatPanel } from './ChatPanel';
|
|
61
58
|
import { PromoteToolDialog } from '@/components/extensions/PromoteToolDialog';
|
|
@@ -154,8 +151,6 @@ export function ScriptPanel({ onClose }: ScriptPanelProps) {
|
|
|
154
151
|
const [outputCollapsed, setOutputCollapsed] = useState(false);
|
|
155
152
|
const chatPanelVisible = useViewerStore((s) => s.chatPanelVisible);
|
|
156
153
|
const setChatPanelVisible = useViewerStore((s) => s.setChatPanelVisible);
|
|
157
|
-
const desktopEntitlement = useViewerStore((s) => s.desktopEntitlement);
|
|
158
|
-
const canUseAiAssistant = hasDesktopFeatureAccess(desktopEntitlement, 'ai_assistant');
|
|
159
154
|
|
|
160
155
|
// Chat panel width (px) — resizable via drag handle
|
|
161
156
|
const [chatWidth, setChatWidth] = useState(380);
|
|
@@ -165,27 +160,14 @@ export function ScriptPanel({ onClose }: ScriptPanelProps) {
|
|
|
165
160
|
// Open chat by default when script panel mounts
|
|
166
161
|
useEffect(() => {
|
|
167
162
|
try {
|
|
168
|
-
if (
|
|
163
|
+
if (localStorage.getItem('ifc-lite-chat-panel-visible') === null) {
|
|
169
164
|
setChatPanelVisible(true);
|
|
170
165
|
}
|
|
171
166
|
} catch {
|
|
172
|
-
|
|
173
|
-
setChatPanelVisible(true);
|
|
174
|
-
}
|
|
167
|
+
setChatPanelVisible(true);
|
|
175
168
|
}
|
|
176
169
|
return () => { cleanupChatDragRef.current?.(); };
|
|
177
|
-
}, [
|
|
178
|
-
|
|
179
|
-
useEffect(() => {
|
|
180
|
-
if (!canUseAiAssistant && chatPanelVisible) {
|
|
181
|
-
setChatPanelVisible(false);
|
|
182
|
-
}
|
|
183
|
-
}, [canUseAiAssistant, chatPanelVisible, setChatPanelVisible]);
|
|
184
|
-
|
|
185
|
-
const promptAiUpgrade = useCallback(() => {
|
|
186
|
-
toast.info('AI assistant is available with Desktop Pro');
|
|
187
|
-
navigateToPath(buildDesktopUpgradeUrl());
|
|
188
|
-
}, []);
|
|
170
|
+
}, [setChatPanelVisible]);
|
|
189
171
|
|
|
190
172
|
const handleChatResizeStart = useCallback((e: React.MouseEvent) => {
|
|
191
173
|
e.preventDefault();
|
|
@@ -259,10 +241,6 @@ export function ScriptPanel({ onClose }: ScriptPanelProps) {
|
|
|
259
241
|
|
|
260
242
|
const handleFixWithLlm = useCallback(() => {
|
|
261
243
|
if (!lastError) return;
|
|
262
|
-
if (!canUseAiAssistant) {
|
|
263
|
-
promptAiUpgrade();
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
244
|
setChatPanelVisible(true);
|
|
267
245
|
const state = useViewerStore.getState();
|
|
268
246
|
queueChatRepairRequest({
|
|
@@ -270,15 +248,11 @@ export function ScriptPanel({ onClose }: ScriptPanelProps) {
|
|
|
270
248
|
diagnostics: state.scriptLastDiagnostics,
|
|
271
249
|
reason: lastError.startsWith('Preflight validation failed:') ? 'preflight' : 'runtime',
|
|
272
250
|
});
|
|
273
|
-
}, [
|
|
251
|
+
}, [lastError, queueChatRepairRequest, setChatPanelVisible]);
|
|
274
252
|
|
|
275
253
|
const toggleChat = useCallback(() => {
|
|
276
|
-
if (!canUseAiAssistant) {
|
|
277
|
-
promptAiUpgrade();
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
254
|
setChatPanelVisible(!chatPanelVisible);
|
|
281
|
-
}, [
|
|
255
|
+
}, [chatPanelVisible, setChatPanelVisible]);
|
|
282
256
|
|
|
283
257
|
return (
|
|
284
258
|
<div className="h-full flex bg-background">
|
|
@@ -333,12 +307,12 @@ export function ScriptPanel({ onClose }: ScriptPanelProps) {
|
|
|
333
307
|
variant={chatPanelVisible ? 'default' : 'ghost'}
|
|
334
308
|
size="icon-xs"
|
|
335
309
|
onClick={toggleChat}
|
|
336
|
-
className={cn(
|
|
310
|
+
className={cn(chatPanelVisible && 'bg-blue-500 hover:bg-blue-600 text-white')}
|
|
337
311
|
>
|
|
338
312
|
<Bot className="h-3.5 w-3.5" />
|
|
339
313
|
</Button>
|
|
340
314
|
</TooltipTrigger>
|
|
341
|
-
<TooltipContent>{
|
|
315
|
+
<TooltipContent>{chatPanelVisible ? 'Hide AI Chat' : 'Show AI Chat'}</TooltipContent>
|
|
342
316
|
</Tooltip>
|
|
343
317
|
|
|
344
318
|
{onClose && (
|
|
@@ -578,7 +552,7 @@ export function ScriptPanel({ onClose }: ScriptPanelProps) {
|
|
|
578
552
|
className="h-6 px-2 text-xs border-destructive/40 text-destructive bg-transparent hover:bg-destructive/10"
|
|
579
553
|
onClick={handleFixWithLlm}
|
|
580
554
|
>
|
|
581
|
-
|
|
555
|
+
Fix with LLM
|
|
582
556
|
</Button>
|
|
583
557
|
</div>
|
|
584
558
|
</div>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import React, { useCallback, useRef, useState, useEffect, useMemo } from 'react';
|
|
15
|
-
import { X, Download, Eye, EyeOff, Maximize2, ZoomIn, ZoomOut, Loader2, Printer, GripVertical, MoreHorizontal, RefreshCw, Pin, PinOff, Palette, Ruler, Trash2, FileText, Shapes, Box, PenTool, Hexagon, Type, Cloud, MousePointer2, Tag } from 'lucide-react';
|
|
15
|
+
import { X, Download, Eye, EyeOff, Maximize2, ZoomIn, ZoomOut, Loader2, Printer, GripVertical, MoreHorizontal, RefreshCw, Pin, PinOff, Palette, Ruler, Trash2, FileText, Shapes, Box, BoxSelect, PenTool, Hexagon, Type, Cloud, MousePointer2, Tag } from 'lucide-react';
|
|
16
16
|
import { Button } from '@/components/ui/button';
|
|
17
17
|
import {
|
|
18
18
|
DropdownMenu,
|
|
@@ -321,6 +321,15 @@ export function Section2DPanel({
|
|
|
321
321
|
updateDisplayOptions({ showIfcAnnotations: !displayOptions.showIfcAnnotations });
|
|
322
322
|
}, [displayOptions.showIfcAnnotations, updateDisplayOptions]);
|
|
323
323
|
|
|
324
|
+
// Construction projection (issue #979): toggling changes which geometry the
|
|
325
|
+
// generator emits, so clear the current drawing to force a regenerate —
|
|
326
|
+
// same pattern as the symbolic/section-cut toggle.
|
|
327
|
+
const toggleConstructionProjection = useCallback(() => {
|
|
328
|
+
updateDisplayOptions({ showConstructionProjection: !displayOptions.showConstructionProjection });
|
|
329
|
+
setDrawing(null);
|
|
330
|
+
setDrawingStatus('idle');
|
|
331
|
+
}, [displayOptions.showConstructionProjection, updateDisplayOptions, setDrawing, setDrawingStatus]);
|
|
332
|
+
|
|
324
333
|
const annotationHandlers = useAnnotation2D({
|
|
325
334
|
drawing, viewTransform, sectionAxis: sectionPlane.axis, containerRef,
|
|
326
335
|
activeTool: annotation2DActiveTool, setActiveTool: setAnnotation2DActiveTool,
|
|
@@ -589,6 +598,21 @@ export function Section2DPanel({
|
|
|
589
598
|
<Tag className="h-4 w-4" />
|
|
590
599
|
</Button>
|
|
591
600
|
|
|
601
|
+
{/* Construction projection toggle (issue #979) — plan only */}
|
|
602
|
+
<Button
|
|
603
|
+
variant={displayOptions.showConstructionProjection ? 'default' : 'ghost'}
|
|
604
|
+
size="icon-sm"
|
|
605
|
+
onClick={toggleConstructionProjection}
|
|
606
|
+
title={
|
|
607
|
+
displayOptions.showConstructionProjection
|
|
608
|
+
? 'Hide construction projection (overhead & visible reference lines)'
|
|
609
|
+
: 'Show construction projection (overhead & visible reference lines)'
|
|
610
|
+
}
|
|
611
|
+
disabled={sectionPlane.axis !== 'down' || sectionPlane.custom !== undefined}
|
|
612
|
+
>
|
|
613
|
+
<BoxSelect className="h-4 w-4" />
|
|
614
|
+
</Button>
|
|
615
|
+
|
|
592
616
|
{/* Annotation Tools Dropdown */}
|
|
593
617
|
<DropdownMenu>
|
|
594
618
|
<DropdownMenuTrigger asChild>
|
|
@@ -760,6 +784,13 @@ export function Section2DPanel({
|
|
|
760
784
|
<Tag className="h-4 w-4 mr-2" />
|
|
761
785
|
IFC Annotations {displayOptions.showIfcAnnotations ? 'On' : 'Off'}
|
|
762
786
|
</DropdownMenuItem>
|
|
787
|
+
<DropdownMenuItem
|
|
788
|
+
onClick={toggleConstructionProjection}
|
|
789
|
+
disabled={sectionPlane.axis !== 'down' || sectionPlane.custom !== undefined}
|
|
790
|
+
>
|
|
791
|
+
<BoxSelect className="h-4 w-4 mr-2" />
|
|
792
|
+
Construction Projection {displayOptions.showConstructionProjection ? 'On' : 'Off'}
|
|
793
|
+
</DropdownMenuItem>
|
|
763
794
|
<DropdownMenuItem onClick={() => setAnnotation2DActiveTool('none')}>
|
|
764
795
|
<MousePointer2 className="h-4 w-4 mr-2" />
|
|
765
796
|
Select / Pan {annotation2DActiveTool === 'none' ? '(On)' : ''}
|
|
@@ -29,13 +29,13 @@ import { BCFPanel } from './BCFPanel';
|
|
|
29
29
|
import { IDSPanel } from './IDSPanel';
|
|
30
30
|
import { LensPanel } from './LensPanel';
|
|
31
31
|
import { ClashPanel } from './ClashPanel';
|
|
32
|
+
import { ComparePanel } from './ComparePanel';
|
|
32
33
|
import { ListPanel } from './lists/ListPanel';
|
|
33
34
|
import { ScriptPanel } from './ScriptPanel';
|
|
34
35
|
import { GanttPanel } from './schedule/GanttPanel';
|
|
35
36
|
import { ExtensionsPanel } from '@/components/extensions/ExtensionsPanel';
|
|
36
37
|
import { CommandPalette } from './CommandPalette';
|
|
37
38
|
import { SearchModal } from './SearchModal';
|
|
38
|
-
import { DesktopEntitlementBanner } from './DesktopEntitlementBanner';
|
|
39
39
|
import {
|
|
40
40
|
closeActiveAnalysisExtension,
|
|
41
41
|
getAnalysisExtensionById,
|
|
@@ -135,6 +135,8 @@ export function ViewerLayout() {
|
|
|
135
135
|
const setLensPanelVisible = useViewerStore((s) => s.setLensPanelVisible);
|
|
136
136
|
const clashPanelVisible = useViewerStore((s) => s.clashPanelVisible);
|
|
137
137
|
const setClashPanelVisible = useViewerStore((s) => s.setClashPanelVisible);
|
|
138
|
+
const comparePanelVisible = useViewerStore((s) => s.comparePanelVisible);
|
|
139
|
+
const setComparePanelVisible = useViewerStore((s) => s.setComparePanelVisible);
|
|
138
140
|
const scriptPanelVisible = useViewerStore((s) => s.scriptPanelVisible);
|
|
139
141
|
const setScriptPanelVisible = useViewerStore((s) => s.setScriptPanelVisible);
|
|
140
142
|
const ganttPanelVisible = useViewerStore((s) => s.ganttPanelVisible);
|
|
@@ -283,7 +285,6 @@ export function ViewerLayout() {
|
|
|
283
285
|
|
|
284
286
|
{/* Main Toolbar — use compact MobileToolbar on mobile */}
|
|
285
287
|
{isMobile ? <MobileToolbar /> : <MainToolbar onShowShortcuts={shortcutsDialog.toggle} />}
|
|
286
|
-
{!isMobile && <DesktopEntitlementBanner />}
|
|
287
288
|
|
|
288
289
|
{/* Main Content Area - Desktop Layout */}
|
|
289
290
|
{!isMobile && (
|
|
@@ -347,6 +348,8 @@ export function ViewerLayout() {
|
|
|
347
348
|
<LensPanel onClose={() => setLensPanelVisible(false)} />
|
|
348
349
|
) : clashPanelVisible ? (
|
|
349
350
|
<ClashPanel onClose={() => setClashPanelVisible(false)} />
|
|
351
|
+
) : comparePanelVisible ? (
|
|
352
|
+
<ComparePanel onClose={() => setComparePanelVisible(false)} />
|
|
350
353
|
) : idsPanelVisible ? (
|
|
351
354
|
<IDSPanel onClose={() => setIdsPanelVisible(false)} />
|
|
352
355
|
) : bcfPanelVisible ? (
|
|
@@ -808,6 +808,9 @@ export function Viewport({
|
|
|
808
808
|
}
|
|
809
809
|
setIsInitialized(false);
|
|
810
810
|
rendererRef.current = null;
|
|
811
|
+
// Free all WebGPU resources held by this renderer instance.
|
|
812
|
+
// destroy() is idempotent, so this is safe even if init() rejected.
|
|
813
|
+
renderer.destroy();
|
|
811
814
|
// Clear BCF global refs to prevent memory leaks
|
|
812
815
|
clearGlobalRefs();
|
|
813
816
|
};
|