@ifc-lite/viewer 1.28.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 +34 -41
- package/CHANGELOG.md +10 -0
- package/dist/assets/{basketViewActivator-BNRDNuUJ.js → basketViewActivator-Ce38DhXd.js} +7 -7
- package/dist/assets/{bcf-DCwCuP7n.js → bcf-Cv_O3JfD.js} +1 -1
- package/dist/assets/{deflate-DNGgs8Ur.js → deflate-HbyMq59o.js} +1 -1
- package/dist/assets/drawing-2d-DW98umlt.js +257 -0
- package/dist/assets/{exporters-B9v81gi9.js → exporters-BuD3XRzB.js} +463 -416
- package/dist/assets/geometry.worker-TH3fCCoY.js +1 -0
- package/dist/assets/{geotiff-D-YCLS4g.js → geotiff-B2HA8Bwm.js} +10 -10
- package/dist/assets/{ids-CCpq-5d3.js → ids-DYUFMd5f.js} +4 -4
- package/dist/assets/{ifc-lite_bg-DbgS5EUA.wasm → ifc-lite_bg-BEA5DLmg.wasm} +0 -0
- package/dist/assets/index-E9wB0zWt.css +1 -0
- package/dist/assets/{index-Bgb3_Pu_.js → index-n5O1QJMM.js} +36808 -39415
- package/dist/assets/{index.es-CWfqZyyr.js → index.es-BKVIpZgL.js} +8 -8
- package/dist/assets/{jpeg-DGOAeUqU.js → jpeg-C7hjKjPX.js} +1 -1
- package/dist/assets/{jspdf.es.min-XPLU2Wkq.js → jspdf.es.min-oWlFc42Y.js} +4 -4
- package/dist/assets/{lerc-1PMSCHwX.js → lerc-BfIOGhQz.js} +1 -1
- package/dist/assets/{lzw-C65U9lNM.js → lzw-B0jRuuW5.js} +1 -1
- package/dist/assets/{native-bridge-XxXos6yI.js → native-bridge-DpB-dtEn.js} +5 -2
- package/dist/assets/{packbits-BdMWXC3m.js → packbits-DVvBTC39.js} +1 -1
- package/dist/assets/{parser.worker-Ddwo3_06.js → parser.worker-BDsWQ6rc.js} +1 -1
- package/dist/assets/{pdf-CRwaZf3s.js → pdf-dVIqI5ac.js} +9 -9
- package/dist/assets/raw-C0ZJYGmN.js +1 -0
- package/dist/assets/{sandbox-0sDo3g3m.js → sandbox-qpJlrNN0.js} +8 -8
- package/dist/assets/{server-client-cTCJ-853.js → server-client-DVZ2huNS.js} +1 -1
- package/dist/assets/{webimage-BtakWX7W.js → webimage-B394g0Tw.js} +1 -1
- package/dist/assets/{xlsx-B1YOg2QB.js → xlsx-D-oHO76J.js} +7 -7
- package/dist/assets/{zstd-CmwsbxmM.js → zstd-Bf38MwV2.js} +1 -1
- package/dist/index.html +8 -8
- package/package.json +5 -5
- package/src/App.tsx +1 -3
- package/src/components/viewer/BCFPanel.tsx +1 -16
- package/src/components/viewer/ChatPanel.tsx +11 -46
- package/src/components/viewer/HierarchyPanel.tsx +2 -176
- package/src/components/viewer/IDSPanel.tsx +1 -26
- package/src/components/viewer/MainToolbar.tsx +75 -185
- package/src/components/viewer/MobileToolbar.tsx +1 -9
- package/src/components/viewer/PropertiesPanel.tsx +28 -126
- package/src/components/viewer/ScriptPanel.tsx +8 -34
- package/src/components/viewer/Section2DPanel.tsx +32 -1
- package/src/components/viewer/ViewerLayout.tsx +0 -2
- package/src/components/viewer/ViewportContainer.tsx +24 -42
- package/src/components/viewer/ViewportOverlays.tsx +1 -4
- package/src/components/viewer/useGeometryStreaming.ts +0 -2
- package/src/hooks/ingest/federationAlign.ts +7 -0
- package/src/hooks/useDrawingGeneration.ts +211 -13
- package/src/hooks/useIfcCache.ts +94 -41
- package/src/hooks/useIfcFederation.ts +2 -3
- package/src/hooks/useIfcLoader.ts +10 -1051
- 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/index.ts +3 -5
- package/src/store/slices/drawing2DSlice.ts +8 -0
- package/src/store/slices/visibilitySlice.ts +22 -1
- package/src/store/types.ts +1 -71
- package/src/utils/ifcConfig.ts +0 -12
- package/vite.config.ts +6 -3
- package/DESKTOP_CONTRACT_VERSION +0 -1
- package/dist/assets/drawing-2d-D0dDf6Lh.js +0 -257
- package/dist/assets/event-B0kAzHa-.js +0 -1
- package/dist/assets/geometry.worker-Bpa3115V.js +0 -1
- package/dist/assets/index-BtbXFKsX.css +0 -1
- package/dist/assets/raw-CJgQdyuZ.js +0 -1
- 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/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 -359
- 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,7 +32,6 @@ 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';
|
|
@@ -162,7 +161,7 @@ export function PropertiesPanel() {
|
|
|
162
161
|
const m = models.get(selectedEntity.modelId);
|
|
163
162
|
if (m) {
|
|
164
163
|
return {
|
|
165
|
-
modelQuery: m.
|
|
164
|
+
modelQuery: m.ifcDataStore ? new IfcQuery(m.ifcDataStore) : null,
|
|
166
165
|
model: m,
|
|
167
166
|
};
|
|
168
167
|
}
|
|
@@ -201,8 +200,6 @@ export function PropertiesPanel() {
|
|
|
201
200
|
const [copied, setCopied] = useState(false);
|
|
202
201
|
const [coordCopied, setCoordCopied] = useState<string | null>(null);
|
|
203
202
|
const [coordOpen, setCoordOpen] = useState(false);
|
|
204
|
-
const [nativeDetails, setNativeDetails] = useState<import('@/store/types').NativeMetadataEntityDetails | null>(null);
|
|
205
|
-
const [nativeDetailsState, setNativeDetailsState] = useState<'idle' | 'loading' | 'error'>('idle');
|
|
206
203
|
|
|
207
204
|
// Inline property editing is gated by the global edit-mode pill in
|
|
208
205
|
// the main toolbar (see `uiSlice.editEnabled`). Reading it from the
|
|
@@ -211,32 +208,6 @@ export function PropertiesPanel() {
|
|
|
211
208
|
// tools — behind a single switch.
|
|
212
209
|
const editMode = useViewerStore((s) => s.editEnabled);
|
|
213
210
|
|
|
214
|
-
useEffect(() => {
|
|
215
|
-
if (!selectedEntity || !model?.nativeMetadata) {
|
|
216
|
-
setNativeDetails(null);
|
|
217
|
-
setNativeDetailsState('idle');
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
let cancelled = false;
|
|
221
|
-
setNativeDetailsState('loading');
|
|
222
|
-
void getNativeEntityDetails(model.nativeMetadata.cacheKey, selectedEntity.expressId)
|
|
223
|
-
.then((details) => {
|
|
224
|
-
if (!cancelled) {
|
|
225
|
-
setNativeDetails(details);
|
|
226
|
-
setNativeDetailsState('idle');
|
|
227
|
-
}
|
|
228
|
-
})
|
|
229
|
-
.catch(() => {
|
|
230
|
-
if (!cancelled) {
|
|
231
|
-
setNativeDetails(null);
|
|
232
|
-
setNativeDetailsState('error');
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
return () => {
|
|
236
|
-
cancelled = true;
|
|
237
|
-
};
|
|
238
|
-
}, [selectedEntity, model?.nativeMetadata]);
|
|
239
|
-
|
|
240
211
|
const copyToClipboard = useCallback((text: string) => {
|
|
241
212
|
navigator.clipboard.writeText(text);
|
|
242
213
|
setCopied(true);
|
|
@@ -958,51 +929,6 @@ export function PropertiesPanel() {
|
|
|
958
929
|
return names;
|
|
959
930
|
}, [attributes]);
|
|
960
931
|
|
|
961
|
-
const isNativeLazySelection = Boolean(selectedEntity && model?.nativeMetadata);
|
|
962
|
-
|
|
963
|
-
// Native-lazy entities (server-streamed without full STEP data) can
|
|
964
|
-
// never be edited; the per-row editors below self-guard via
|
|
965
|
-
// `enableEditing={editMode && !isNativeLazySelection}`. The old
|
|
966
|
-
// panel-local `editMode` would flip itself off when a native-lazy
|
|
967
|
-
// entity was selected; now that the flag is global it stays on, and
|
|
968
|
-
// the field-level guards do the work.
|
|
969
|
-
|
|
970
|
-
const nativeSpatialInfo = useMemo(() => {
|
|
971
|
-
if (!nativeDetails?.spatial?.storeyName) return null;
|
|
972
|
-
return {
|
|
973
|
-
storeyId: nativeDetails.spatial.storeyId ?? undefined,
|
|
974
|
-
storeyName: nativeDetails.spatial.storeyName,
|
|
975
|
-
elevation: nativeDetails.spatial.elevation ?? undefined,
|
|
976
|
-
height: nativeDetails.spatial.height ?? undefined,
|
|
977
|
-
};
|
|
978
|
-
}, [nativeDetails]);
|
|
979
|
-
|
|
980
|
-
const nativeOccurrenceProperties = useMemo<PropertySet[]>(() => {
|
|
981
|
-
if (!nativeDetails) return [];
|
|
982
|
-
return nativeDetails.properties.map((pset) => ({
|
|
983
|
-
name: pset.name,
|
|
984
|
-
properties: pset.properties.map((property) => ({
|
|
985
|
-
name: property.name,
|
|
986
|
-
value: property.value,
|
|
987
|
-
isMutated: false,
|
|
988
|
-
})),
|
|
989
|
-
isNewPset: false,
|
|
990
|
-
source: 'instance' as const,
|
|
991
|
-
}));
|
|
992
|
-
}, [nativeDetails]);
|
|
993
|
-
|
|
994
|
-
const nativeQuantities = useMemo<QuantitySet[]>(() => {
|
|
995
|
-
if (!nativeDetails) return [];
|
|
996
|
-
return nativeDetails.quantities.map((qset) => ({
|
|
997
|
-
name: qset.name,
|
|
998
|
-
quantities: qset.quantities.map((quantity) => ({
|
|
999
|
-
name: quantity.name,
|
|
1000
|
-
value: quantity.value,
|
|
1001
|
-
type: quantity.type ?? 0,
|
|
1002
|
-
})),
|
|
1003
|
-
}));
|
|
1004
|
-
}, [nativeDetails]);
|
|
1005
|
-
|
|
1006
932
|
// Overlay (authored) entities — split halves, duplicates, scripted
|
|
1007
933
|
// adds — live only in the StoreEditor overlay, NOT the parsed store.
|
|
1008
934
|
// `modelQuery.entity()` always returns a node, and its getters fall
|
|
@@ -1010,49 +936,27 @@ export function PropertiesPanel() {
|
|
|
1010
936
|
// table (entity-table.ts#getTypeName). Those non-null sentinels would
|
|
1011
937
|
// shadow the overlay record in an `entityNode ?? overlay` chain, so
|
|
1012
938
|
// when an overlay record exists it MUST take precedence.
|
|
1013
|
-
const renderedEntityType =
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
const
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
const
|
|
1029
|
-
const
|
|
1030
|
-
const
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
const renderedQuantities = isNativeLazySelection ? nativeQuantities : quantities;
|
|
1035
|
-
const renderedAttributes = isNativeLazySelection ? [] : attributes;
|
|
1036
|
-
const renderedClassifications = isNativeLazySelection ? [] : classifications;
|
|
1037
|
-
const renderedMaterialInfo = isNativeLazySelection ? null : materialInfo;
|
|
1038
|
-
const renderedMaterialProperties = isNativeLazySelection ? [] : materialProperties;
|
|
1039
|
-
const renderedDocuments = isNativeLazySelection ? [] : documents;
|
|
1040
|
-
const renderedEntityRelationships = isNativeLazySelection ? null : entityRelationships;
|
|
1041
|
-
const renderedGeoref = isNativeLazySelection ? null : georef;
|
|
1042
|
-
const renderedSpatialContainment = isNativeLazySelection ? null : spatialContainment;
|
|
1043
|
-
const renderedTypeProperties = isNativeLazySelection
|
|
1044
|
-
? (nativeDetails?.typeSummary
|
|
1045
|
-
? {
|
|
1046
|
-
typeName: nativeDetails.typeSummary.name,
|
|
1047
|
-
typeId: nativeDetails.typeSummary.expressId,
|
|
1048
|
-
psets: [] as PropertySet[],
|
|
1049
|
-
}
|
|
1050
|
-
: null)
|
|
1051
|
-
: typeProperties;
|
|
1052
|
-
const renderedTypeEditImpact = isNativeLazySelection ? null : typeEditImpact;
|
|
1053
|
-
const renderedIsTypeEntity = isNativeLazySelection
|
|
1054
|
-
? ((nativeDetails?.summary.type ?? '').endsWith('Type'))
|
|
1055
|
-
: 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;
|
|
1056
960
|
const renderedExistingProps = useMemo(() => {
|
|
1057
961
|
const keys = new Set<string>();
|
|
1058
962
|
for (const pset of renderedMergedProperties) {
|
|
@@ -1109,7 +1013,7 @@ export function PropertiesPanel() {
|
|
|
1109
1013
|
// `overlayEntity` when `entityNode` is empty. Without including
|
|
1110
1014
|
// `overlayEntity` here the panel collapses to the model-metadata
|
|
1111
1015
|
// view the moment a fresh add lands.
|
|
1112
|
-
if (!selectedEntityId ||
|
|
1016
|
+
if (!selectedEntityId || !modelQuery || (!entityNode && !overlayEntity)) {
|
|
1113
1017
|
// Show model metadata when a single model is loaded and nothing selected.
|
|
1114
1018
|
// Handles both federated models (models.size >= 1) and legacy single-model path (models.size === 0).
|
|
1115
1019
|
if (models.size === 1) {
|
|
@@ -1501,7 +1405,7 @@ export function PropertiesPanel() {
|
|
|
1501
1405
|
</div>
|
|
1502
1406
|
)}
|
|
1503
1407
|
{/* Edit toolbar - only shown when edit mode is active */}
|
|
1504
|
-
{editMode && selectedEntity &&
|
|
1408
|
+
{editMode && selectedEntity && (
|
|
1505
1409
|
<>
|
|
1506
1410
|
<GeometryEditCard
|
|
1507
1411
|
modelId={selectedEntity.modelId}
|
|
@@ -1547,7 +1451,7 @@ export function PropertiesPanel() {
|
|
|
1547
1451
|
pset={pset}
|
|
1548
1452
|
modelId={selectedEntity?.modelId}
|
|
1549
1453
|
entityId={selectedEntity?.expressId}
|
|
1550
|
-
enableEditing={editMode
|
|
1454
|
+
enableEditing={editMode}
|
|
1551
1455
|
isTypeProperty={renderedIsTypeEntity}
|
|
1552
1456
|
typeEditScope={renderedIsTypeEntity ? renderedTypeEditImpact ?? undefined : undefined}
|
|
1553
1457
|
/>
|
|
@@ -1571,7 +1475,7 @@ export function PropertiesPanel() {
|
|
|
1571
1475
|
pset={pset}
|
|
1572
1476
|
modelId={selectedEntity?.modelId}
|
|
1573
1477
|
entityId={renderedTypeProperties.typeId}
|
|
1574
|
-
enableEditing={editMode
|
|
1478
|
+
enableEditing={editMode}
|
|
1575
1479
|
isTypeProperty
|
|
1576
1480
|
typeEditScope={renderedTypeEditImpact?.mode === 'inherited' ? renderedTypeEditImpact : undefined}
|
|
1577
1481
|
/>
|
|
@@ -1695,7 +1599,7 @@ export function PropertiesPanel() {
|
|
|
1695
1599
|
</TabsContent>
|
|
1696
1600
|
|
|
1697
1601
|
<TabsContent value="raw-step" className="m-0 p-3 overflow-hidden">
|
|
1698
|
-
{selectedEntity
|
|
1602
|
+
{selectedEntity ? (
|
|
1699
1603
|
<RawStepCard
|
|
1700
1604
|
modelId={selectedEntity.modelId === 'legacy' ? '__legacy__' : selectedEntity.modelId}
|
|
1701
1605
|
entityId={selectedEntity.expressId}
|
|
@@ -1705,9 +1609,7 @@ export function PropertiesPanel() {
|
|
|
1705
1609
|
/>
|
|
1706
1610
|
) : (
|
|
1707
1611
|
<p className="text-sm text-zinc-500 dark:text-zinc-500 text-center py-8 font-mono">
|
|
1708
|
-
|
|
1709
|
-
? 'Raw STEP is not available for native-metadata selections'
|
|
1710
|
-
: 'Select an entity to inspect raw STEP arguments'}
|
|
1612
|
+
Select an entity to inspect raw STEP arguments
|
|
1711
1613
|
</p>
|
|
1712
1614
|
)}
|
|
1713
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)' : ''}
|
|
@@ -36,7 +36,6 @@ import { GanttPanel } from './schedule/GanttPanel';
|
|
|
36
36
|
import { ExtensionsPanel } from '@/components/extensions/ExtensionsPanel';
|
|
37
37
|
import { CommandPalette } from './CommandPalette';
|
|
38
38
|
import { SearchModal } from './SearchModal';
|
|
39
|
-
import { DesktopEntitlementBanner } from './DesktopEntitlementBanner';
|
|
40
39
|
import {
|
|
41
40
|
closeActiveAnalysisExtension,
|
|
42
41
|
getAnalysisExtensionById,
|
|
@@ -286,7 +285,6 @@ export function ViewerLayout() {
|
|
|
286
285
|
|
|
287
286
|
{/* Main Toolbar — use compact MobileToolbar on mobile */}
|
|
288
287
|
{isMobile ? <MobileToolbar /> : <MainToolbar onShowShortcuts={shortcutsDialog.toggle} />}
|
|
289
|
-
{!isMobile && <DesktopEntitlementBanner />}
|
|
290
288
|
|
|
291
289
|
{/* Main Content Area - Desktop Layout */}
|
|
292
290
|
{!isMobile && (
|
|
@@ -19,10 +19,7 @@ import { toGlobalIdFromModels } from '@/store/globalId';
|
|
|
19
19
|
import { collectIfcBuildingStoreyElementsWithIfcSpace } from '@/store/basketVisibleSet';
|
|
20
20
|
import { useIfc } from '@/hooks/useIfc';
|
|
21
21
|
import { useWebGPU } from '@/hooks/useWebGPU';
|
|
22
|
-
import { openIfcFileDialog } from '@/services/file-dialog';
|
|
23
|
-
import { logToDesktopTerminal } from '@/services/desktop-logger';
|
|
24
22
|
import { cacheFileBlobs, formatFileSize, getCachedFile, getRecentFiles, recordRecentFiles, type RecentFileEntry } from '@/lib/recent-files';
|
|
25
|
-
import { isTauri } from '@/lib/platform';
|
|
26
23
|
import { toast } from '@/components/ui/toast';
|
|
27
24
|
import { describeUnsupportedFormat } from '@/hooks/ingest/pointCloudIngest';
|
|
28
25
|
import { Upload, MousePointer, Layers, Info, Command, AlertTriangle, ChevronDown, ExternalLink, Plus, Clock3, Sparkles, ArrowUpRight, PackagePlus } from 'lucide-react';
|
|
@@ -69,6 +66,7 @@ export function ViewportContainer() {
|
|
|
69
66
|
const releaseGeometryMemory = useViewerStore((s) => s.releaseGeometryMemory);
|
|
70
67
|
const selectedStoreys = useViewerStore((s) => s.selectedStoreys);
|
|
71
68
|
const typeVisibility = useViewerStore((s) => s.typeVisibility);
|
|
69
|
+
const typeViewMode = useViewerStore((s) => s.typeViewMode);
|
|
72
70
|
const isolatedEntities = useViewerStore((s) => s.isolatedEntities);
|
|
73
71
|
const classFilter = useViewerStore((s) => s.classFilter);
|
|
74
72
|
const resetViewerState = useViewerStore((s) => s.resetViewerState);
|
|
@@ -392,20 +390,6 @@ export function ViewportContainer() {
|
|
|
392
390
|
setCesiumSourceModelId(georef?.sourceModelId ?? null);
|
|
393
391
|
}, [georef?.sourceModelId, setCesiumSourceModelId]);
|
|
394
392
|
|
|
395
|
-
useEffect(() => {
|
|
396
|
-
// Recent files are a desktop-only feature — the web viewer should not
|
|
397
|
-
// show previously opened files in the landing page empty state.
|
|
398
|
-
if (!isTauri()) return;
|
|
399
|
-
|
|
400
|
-
const refreshRecentFiles = () => {
|
|
401
|
-
setRecentFiles(getRecentFiles().slice(0, 3));
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
refreshRecentFiles();
|
|
405
|
-
window.addEventListener('focus', refreshRecentFiles);
|
|
406
|
-
return () => window.removeEventListener('focus', refreshRecentFiles);
|
|
407
|
-
}, []);
|
|
408
|
-
|
|
409
393
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
410
394
|
e.preventDefault();
|
|
411
395
|
e.stopPropagation();
|
|
@@ -504,7 +488,6 @@ export function ViewportContainer() {
|
|
|
504
488
|
|
|
505
489
|
const handleStartBlank = useCallback(async () => {
|
|
506
490
|
if (!webgpu.supported) return;
|
|
507
|
-
void logToDesktopTerminal('info', '[ViewportContainer] Start blank IFC clicked');
|
|
508
491
|
const file = createBlankIfcFile();
|
|
509
492
|
// Must await: loadFile() calls resetViewerState() internally which
|
|
510
493
|
// resets activeTool back to 'select'. Setting addElement before that
|
|
@@ -526,6 +509,7 @@ export function ViewportContainer() {
|
|
|
526
509
|
const filteredSourceLenRef = useRef(0);
|
|
527
510
|
const filteredSourceRef = useRef<MeshData[] | null>(null);
|
|
528
511
|
const filteredTypeVisRef = useRef(typeVisibility);
|
|
512
|
+
const filteredTypeModeRef = useRef(typeViewMode);
|
|
529
513
|
const filteredVersionRef = useRef(0);
|
|
530
514
|
|
|
531
515
|
const filteredGeometry = useMemo(() => {
|
|
@@ -540,18 +524,21 @@ export function ViewportContainer() {
|
|
|
540
524
|
const allMeshes = mergedGeometryResult.meshes;
|
|
541
525
|
const cache = filteredCacheRef.current;
|
|
542
526
|
|
|
543
|
-
// Full rebuild if: type visibility changed,
|
|
527
|
+
// Full rebuild if: type visibility changed, view mode changed, source shrunk
|
|
528
|
+
// (new file), or empty cache
|
|
544
529
|
const prevVis = filteredTypeVisRef.current;
|
|
545
530
|
const typeVisChanged =
|
|
546
531
|
prevVis.spaces !== typeVisibility.spaces ||
|
|
547
532
|
prevVis.openings !== typeVisibility.openings ||
|
|
548
|
-
prevVis.site !== typeVisibility.site
|
|
533
|
+
prevVis.site !== typeVisibility.site ||
|
|
534
|
+
filteredTypeModeRef.current !== typeViewMode;
|
|
549
535
|
const sourceChanged = filteredSourceRef.current !== allMeshes;
|
|
550
536
|
if (typeVisChanged || sourceChanged || allMeshes.length < filteredSourceLenRef.current) {
|
|
551
537
|
cache.length = 0;
|
|
552
538
|
filteredSourceLenRef.current = 0;
|
|
553
539
|
filteredSourceRef.current = allMeshes;
|
|
554
540
|
filteredTypeVisRef.current = typeVisibility;
|
|
541
|
+
filteredTypeModeRef.current = typeViewMode;
|
|
555
542
|
}
|
|
556
543
|
|
|
557
544
|
const needsFilter = !typeVisibility.spaces || !typeVisibility.openings || !typeVisibility.site;
|
|
@@ -562,6 +549,18 @@ export function ViewportContainer() {
|
|
|
562
549
|
const mesh = allMeshes[i];
|
|
563
550
|
const ifcType = mesh.ifcType;
|
|
564
551
|
|
|
552
|
+
// Model/Types view switch (#957 follow-up). geometryClass: 0 = occurrence,
|
|
553
|
+
// 1 = orphan type (no occurrence — shown in BOTH modes since it's the only
|
|
554
|
+
// geometry), 2 = instanced type-library shape. In 'model' mode hide class 2
|
|
555
|
+
// (else the AC20 duplicate boxes at the MappingOrigin reappear); in 'types'
|
|
556
|
+
// mode hide occurrences (class 0) so only the type library shows.
|
|
557
|
+
const geometryClass = mesh.geometryClass ?? 0;
|
|
558
|
+
if (typeViewMode === 'types') {
|
|
559
|
+
if (geometryClass === 0) continue;
|
|
560
|
+
} else if (geometryClass === 2) {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
|
|
565
564
|
if (needsFilter) {
|
|
566
565
|
if (ifcType === 'IfcSpace' && !typeVisibility.spaces) continue;
|
|
567
566
|
if (ifcType === 'IfcOpeningElement' && !typeVisibility.openings) continue;
|
|
@@ -588,7 +587,7 @@ export function ViewportContainer() {
|
|
|
588
587
|
// Return the same array reference — downstream change detection uses
|
|
589
588
|
// geometryVersion (which increments each batch) instead of array identity.
|
|
590
589
|
return cache;
|
|
591
|
-
}, [mergedGeometryResult, typeVisibility]);
|
|
590
|
+
}, [mergedGeometryResult, typeVisibility, typeViewMode]);
|
|
592
591
|
|
|
593
592
|
// Version counter that changes every batch — triggers useGeometryStreaming
|
|
594
593
|
// without requiring a new geometry array reference.
|
|
@@ -882,27 +881,10 @@ export function ViewportContainer() {
|
|
|
882
881
|
*/}
|
|
883
882
|
{/* Track 1 — open / drag */}
|
|
884
883
|
<button
|
|
885
|
-
onClick={
|
|
884
|
+
onClick={() => {
|
|
886
885
|
if (!webgpu.supported) {
|
|
887
886
|
return;
|
|
888
887
|
}
|
|
889
|
-
|
|
890
|
-
void logToDesktopTerminal('info', '[ViewportContainer] Empty-state open button clicked');
|
|
891
|
-
const file = await openIfcFileDialog();
|
|
892
|
-
if (file) {
|
|
893
|
-
void logToDesktopTerminal('info', `[ViewportContainer] Native dialog selected ${file.path}`);
|
|
894
|
-
recordRecentFiles([{
|
|
895
|
-
name: file.name,
|
|
896
|
-
size: file.size,
|
|
897
|
-
path: file.path,
|
|
898
|
-
modifiedMs: file.modifiedMs ?? null,
|
|
899
|
-
}]);
|
|
900
|
-
setRecentFiles(getRecentFiles().slice(0, 3));
|
|
901
|
-
loadFile(file);
|
|
902
|
-
return;
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
void logToDesktopTerminal('info', '[ViewportContainer] Falling back to browser file input');
|
|
906
888
|
fileInputRef.current?.click();
|
|
907
889
|
}}
|
|
908
890
|
disabled={!webgpu.supported || webgpu.checking}
|
|
@@ -1064,7 +1046,7 @@ export function ViewportContainer() {
|
|
|
1064
1046
|
)}
|
|
1065
1047
|
|
|
1066
1048
|
{/* Cesium 3D world context overlay — rendered behind the WebGPU canvas (web only) */}
|
|
1067
|
-
{cesiumEnabled && georef &&
|
|
1049
|
+
{cesiumEnabled && georef && (
|
|
1068
1050
|
<CesiumOverlay
|
|
1069
1051
|
mapConversion={georef.mapConversion}
|
|
1070
1052
|
cameraMapConversion={georef.baseMapConversion}
|
|
@@ -1075,7 +1057,7 @@ export function ViewportContainer() {
|
|
|
1075
1057
|
storeyElevations={georef.storeyElevations}
|
|
1076
1058
|
/>
|
|
1077
1059
|
)}
|
|
1078
|
-
{cesiumEnabled && georef?.mapConversion &&
|
|
1060
|
+
{cesiumEnabled && georef?.mapConversion && georef.baseMapConversion && (
|
|
1079
1061
|
<CesiumPlacementEditor
|
|
1080
1062
|
modelId={georef.sourceModelId}
|
|
1081
1063
|
mapConversion={georef.mapConversion}
|
|
@@ -1094,7 +1076,7 @@ export function ViewportContainer() {
|
|
|
1094
1076
|
coordinateInfo={mergedGeometryResult?.coordinateInfo}
|
|
1095
1077
|
computedIsolatedIds={computedIsolatedIds}
|
|
1096
1078
|
modelIdToIndex={modelIdToIndex}
|
|
1097
|
-
cesiumActive={cesiumEnabled && georef !== null
|
|
1079
|
+
cesiumActive={cesiumEnabled && georef !== null}
|
|
1098
1080
|
releaseGeometryAfterStream={false}
|
|
1099
1081
|
onGeometryReleased={releaseGeometryMemory}
|
|
1100
1082
|
/>
|
|
@@ -15,15 +15,12 @@ import { useViewerStore } from '@/store';
|
|
|
15
15
|
import { goHomeFromStore } from '@/store/homeView';
|
|
16
16
|
import { useIfc } from '@/hooks/useIfc';
|
|
17
17
|
import { cn } from '@/lib/utils';
|
|
18
|
-
import { isTauri } from '@/lib/platform';
|
|
19
18
|
import { ViewCube, type ViewCubeRef } from './ViewCube';
|
|
20
19
|
import { AxisHelper, type AxisHelperRef } from './AxisHelper';
|
|
21
20
|
import { BasepointOverlay } from './BasepointOverlay';
|
|
22
21
|
import { PointCloudPanel } from './PointCloudPanel';
|
|
23
22
|
import { Crosshair } from 'lucide-react';
|
|
24
23
|
|
|
25
|
-
const isDesktop = isTauri();
|
|
26
|
-
|
|
27
24
|
export function ViewportOverlays({ hideViewCube = false }: { hideViewCube?: boolean } = {}) {
|
|
28
25
|
const selectedStoreys = useViewerStore((s) => s.selectedStoreys);
|
|
29
26
|
const hiddenEntities = useViewerStore((s) => s.hiddenEntities);
|
|
@@ -144,7 +141,7 @@ export function ViewportOverlays({ hideViewCube = false }: { hideViewCube?: bool
|
|
|
144
141
|
<>
|
|
145
142
|
<PointCloudPanelMount />
|
|
146
143
|
{/* Bottom-right: Navigation controls (hidden when Cesium active — Cesium is web-only) */}
|
|
147
|
-
{!
|
|
144
|
+
{!cesiumEnabled && (
|
|
148
145
|
<div
|
|
149
146
|
className={cn(
|
|
150
147
|
'absolute flex flex-col gap-1 bg-background/90 backdrop-blur-sm border p-1',
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
import { useEffect, useRef, type MutableRefObject } from 'react';
|
|
22
22
|
import type { Renderer } from '@ifc-lite/renderer';
|
|
23
23
|
import type { MeshData, CoordinateInfo } from '@ifc-lite/geometry';
|
|
24
|
-
import { logToDesktopTerminal } from '@/services/desktop-logger';
|
|
25
24
|
import { toast } from '../ui/toast.js';
|
|
26
25
|
|
|
27
26
|
// Session-scoped flag so the linear-infrastructure hint fires at most once
|
|
@@ -87,7 +86,6 @@ const MAX_VALID_COORD = 10000;
|
|
|
87
86
|
|
|
88
87
|
function traceGeometrySync(message: string): void {
|
|
89
88
|
console.log(`[GeomSync] ${message}`);
|
|
90
|
-
void logToDesktopTerminal('info', `[GeomSync] ${message}`);
|
|
91
89
|
}
|
|
92
90
|
|
|
93
91
|
export function useGeometryStreaming(params: UseGeometryStreamingParams): void {
|
|
@@ -172,6 +172,13 @@ function buildGeorefAlignmentTransform(source: ModelGeoref, reference: ModelGeor
|
|
|
172
172
|
|
|
173
173
|
const yVx = (-refAxis.o * eVx + refAxis.a * nVx) * invRefDenom;
|
|
174
174
|
const yVz = (-refAxis.o * eVz + refAxis.a * nVz) * invRefDenom;
|
|
175
|
+
// NOTE: the refOffset handling is intentionally asymmetric between X and Z and
|
|
176
|
+
// must NOT be "symmetrised". refOffset is subtracted from the FINAL viewer
|
|
177
|
+
// coordinate on every axis. X maps positively (`tx = +xC`), so its offset is
|
|
178
|
+
// folded into xC above. Z maps to the NEGATED north axis (`tz = -yC`), so its
|
|
179
|
+
// offset is applied after the negation, leaving yC offset-free here. This
|
|
180
|
+
// matches alignGeometryAcrossCrs: alignedZ = refWorldZ - refOffset.z with
|
|
181
|
+
// refWorldZ = -ifcYr. Folding -refOffset.z into yC would flip its sign.
|
|
175
182
|
const yC = (-refAxis.o * eC + refAxis.a * nC) * invRefDenom;
|
|
176
183
|
|
|
177
184
|
return {
|