diff --git a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx index 30a1b329f07..25629ff3689 100644 --- a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx +++ b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx @@ -5,7 +5,8 @@ import { useRef, type ReactElement, useContext, useState, useEffect } from 'reac import { type NodeAppearance, type Cognite3DViewer, - type PointCloudAppearance + type PointCloudAppearance, + type PointerEventData, } from '@cognite/reveal'; import { ModelsLoadingStateContext } from './ModelsLoadingContext'; import { CadModelContainer, type CadModelStyling } from '../CadModelContainer/CadModelContainer'; @@ -19,11 +20,14 @@ import { type AddReveal3DModelOptions, type AddImageCollection360Options, type TypedReveal3DModel, - type AddResourceOptions + type AddResourceOptions, + type NodeDataResult } from './types'; import { type CogniteExternalId } from '@cognite/sdk'; import { type FdmAssetMappingsConfig } from '../../hooks/types'; import { useCalculateModelsStyling } from '../../hooks/useCalculateModelsStyling'; +import { queryMappedData } from './queryMappedData'; +import { useFdmSdk, useSDK } from '../RevealContainer/SDKProvider'; export type FdmAssetStylingGroup = { fdmAssetExternalIds: CogniteExternalId[]; @@ -35,17 +39,19 @@ export type Reveal3DResourcesStyling = { groups?: FdmAssetStylingGroup[]; }; -export type Reveal3DResourcesProps = { +export type Reveal3DResourcesProps = { resources: AddResourceOptions[]; - fdmAssetMappingConfig?: FdmAssetMappingsConfig; + fdmAssetMappingConfig: FdmAssetMappingsConfig; styling?: Reveal3DResourcesStyling; + onNodeClick?: (node: NodeDataResult) => void; }; -export const Reveal3DResources = ({ +export const Reveal3DResources = ({ resources, styling, - fdmAssetMappingConfig -}: Reveal3DResourcesProps): ReactElement => { + fdmAssetMappingConfig, + onNodeClick +}: Reveal3DResourcesProps): ReactElement => { const [reveal3DModels, setReveal3DModels] = useState([]); const [reveal3DModelsStyling, setReveal3DModelsStyling] = useState< Array @@ -53,6 +59,8 @@ export const Reveal3DResources = ({ const { setModelsAdded } = useContext(ModelsLoadingStateContext); const viewer = useReveal(); + const fdmSdk = useFdmSdk(); + const client = useSDK(); const numModelsLoaded = useRef(0); useEffect(() => { @@ -65,6 +73,19 @@ export const Reveal3DResources = ({ setReveal3DModelsStyling(modelsStyling); }, [modelsStyling]); + useEffect(() => { + const callback = async (event: PointerEventData) => { + const data = await queryMappedData(viewer, client, fdmSdk, fdmAssetMappingConfig, event); + if (onNodeClick !== undefined && data !== undefined) { + onNodeClick?.(data); + } + } + + viewer.on('click', callback); + + return () => viewer.off('click', callback); + }, [onNodeClick]); + const image360CollectionAddOptions = resources.filter( (resource): resource is AddImageCollection360Options => (resource as AddImageCollection360Options).siteId !== undefined diff --git a/react-components/src/components/Reveal3DResources/queryMappedData.ts b/react-components/src/components/Reveal3DResources/queryMappedData.ts new file mode 100644 index 00000000000..5ac0677edfc --- /dev/null +++ b/react-components/src/components/Reveal3DResources/queryMappedData.ts @@ -0,0 +1,136 @@ +/*! + * Copyright 2023 Cognite AS + */ + +import { Cognite3DViewer, PointerEventData, CadIntersection } from '@cognite/reveal'; +import { CogniteClient } from '@cognite/sdk'; +import { FdmSDK } from '../../utilities/FdmSDK'; +import { FdmAssetMappingsConfig } from '../../hooks/types'; +import { NodeDataResult } from './types'; + +export async function queryMappedData( + viewer: Cognite3DViewer, + cdfClient: CogniteClient, + fdmClient: FdmSDK, + fdmConfig: FdmAssetMappingsConfig, + clickEvent: PointerEventData +): Promise | undefined> { + const intersection = await viewer.getIntersectionFromPixel( + clickEvent.offsetX, + clickEvent.offsetY + ); + + if (intersection === null || intersection.type !== 'cad') { + return; + } + + const cadIntersection = intersection as CadIntersection; + const model = cadIntersection.model; + + const nodeId = await cadIntersection.model.mapTreeIndexToNodeId( + cadIntersection.treeIndex + ); + + const ancestorNodes = await cdfClient.revisions3D.list3DNodeAncestors( + model.modelId, + model.revisionId, + nodeId + ); + + const ancestorIds = ancestorNodes.items.map((n) => n.id); + + const filter = { + and: [ + { + equals: { + property: ['edge', 'endNode'], + value: { + space: fdmConfig.global3dSpace, + externalId: 'model_3d_' + model.modelId, + }, + }, + }, + { + equals: { + property: [ + fdmConfig.source.space, + `${fdmConfig.source.externalId}/${fdmConfig.source.version}`, + 'revisionId', + ], + value: model.revisionId, + }, + }, + { + in: { + property: [ + fdmConfig.source.space, + `${fdmConfig.source.externalId}/${fdmConfig.source.version}`, + 'nodeId', + ], + values: ancestorIds, + }, + }, + ], + }; + + let mappings = await fdmClient.filterInstances( + filter, + 'edge', + fdmConfig.source + ); + + while (mappings.nextCursor) { + const nextMappings = await fdmClient.filterInstances( + filter, + 'edge', + fdmConfig.source, + mappings.nextCursor + ); + + mappings = { + edges: [...mappings.edges, ...nextMappings.edges], + nextCursor: nextMappings.nextCursor, + }; + } + + if (mappings.edges.length === 0) { + return; + } + + const dataNode = mappings.edges[0].startNode; + + const inspectionResult = await fdmClient.inspectInstances({ + inspectionOperations: { involvedViewsAndContainers: {} }, + items: [ + { + instanceType: 'node', + externalId: dataNode.externalId, + space: dataNode.space, + }, + ], + }); + + const dataView = + inspectionResult.items[0].inspectionResults.involvedViewsAndContainers + .views[0]; + + const dataQueryResult = await fdmClient.filterInstances( + { + and: [ + { equals: { property: ['node', 'space'], value: dataNode.space } }, + { + equals: { + property: ['node', 'externalId'], + value: dataNode.externalId, + }, + }, + ], + }, + 'node', + dataView + ); + + const nodeData = dataQueryResult.edges[0].properties[dataView.space][`${dataView.externalId}/${dataView.version}`]; + + return { data: nodeData as NodeType, view: dataView }; +} diff --git a/react-components/src/components/Reveal3DResources/types.ts b/react-components/src/components/Reveal3DResources/types.ts index 87d0319ffc1..ab18ecefada 100644 --- a/react-components/src/components/Reveal3DResources/types.ts +++ b/react-components/src/components/Reveal3DResources/types.ts @@ -13,3 +13,15 @@ export type AddResourceOptions = AddReveal3DModelOptions | AddImageCollection360 export type AddReveal3DModelOptions = AddModelOptions & { transform?: Matrix4 }; export type TypedReveal3DModel = AddReveal3DModelOptions & { type: SupportedModelTypes | '' }; + +export type ViewInfo = { + type: 'view', + space: string, + externalId: string, + version: string, +}; + +export type NodeDataResult = { + data: NodeType, + view: ViewInfo +}; diff --git a/react-components/src/index.ts b/react-components/src/index.ts index dff60b68e4d..8b8a153bbd8 100644 --- a/react-components/src/index.ts +++ b/react-components/src/index.ts @@ -27,7 +27,9 @@ export { CameraController } from './components/CameraController/CameraController export type { AddImageCollection360Options, AddResourceOptions, - AddReveal3DModelOptions + AddReveal3DModelOptions, + NodeDataResult, + ViewInfo } from './components/Reveal3DResources/types'; export { RevealToolbar } from './components/RevealToolbar/RevealToolbar'; export { LayersButton } from './components/RevealToolbar/LayersButton'; @@ -36,3 +38,4 @@ export { FitModelsButton } from './components/RevealToolbar/FitModelsButton'; export { useFdmAssetMappings } from './hooks/useFdmAssetMappings'; export { type FdmAssetMappingsConfig } from './hooks/types'; export { use3DModelName } from './hooks/use3DModelName'; +export { useReveal } from './components/RevealContainer/RevealContext'; diff --git a/react-components/src/utilities/FdmSDK.ts b/react-components/src/utilities/FdmSDK.ts index edfd866b161..0090edcc0cf 100644 --- a/react-components/src/utilities/FdmSDK.ts +++ b/react-components/src/utilities/FdmSDK.ts @@ -26,10 +26,41 @@ export type EdgeItem = { properties: PropertiesType; }; +export type InspectFilter = { + inspectionOperations: { involvedViewsAndContainers: {} }; + items: { instanceType: InstanceType; externalId: string; space: string }[]; +}; + +export type InspectResult = { + involvedViewsAndContainers: { + containers: { + type: 'container'; + space: string; + externalId: string; + }[]; + views: { + type: 'view'; + space: string; + externalId: string; + version: string; + }[]; + }; +}; + +export type InspectResultList = { + items: { + instanceType: InstanceType; + externalId: string; + space: string; + inspectionResults: InspectResult; + }[]; +}; + export class FdmSDK { private readonly _sdk: CogniteClient; private readonly _byIdsEndpoint: string; private readonly _listEndpoint: string; + private readonly _inspectEndpoint: string; constructor(sdk: CogniteClient) { const baseUrl = sdk.getBaseUrl(); @@ -37,6 +68,7 @@ export class FdmSDK { this._listEndpoint = `${baseUrl}/api/v1/projects/${project}/models/instances/list`; this._byIdsEndpoint = `${baseUrl}/api/v1/projects/${project}/models/instances/byids`; + this._inspectEndpoint = `${baseUrl}/api/v1/projects/${project}/models/instances/inspect`; this._sdk = sdk; } @@ -64,4 +96,17 @@ export class FdmSDK { } throw new Error(`Failed to fetch instances. Status: ${result.status}`); } + + public async inspectInstances( + inspectFilter: InspectFilter + ): Promise { + const data: any = inspectFilter; + const result = await this._sdk.post(this._inspectEndpoint, { data }); + + if (result.status === 200) { + return result.data as InspectResultList; + } + + throw new Error(`Failed to fetch instances. Status: ${result.status}`); + } }