Skip to content

Commit

Permalink
feat: add support for node data fetching in React Components (#3518)
Browse files Browse the repository at this point in the history
* feat: add support for node data fetching in React Components

* feat: add story

---------

Co-authored-by: Savelii Novikov <45129444+Savokr@users.noreply.github.com>
Co-authored-by: cognite-bulldozer[bot] <51074376+cognite-bulldozer[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 1, 2023
1 parent ca6fde2 commit bc0d4a7
Show file tree
Hide file tree
Showing 8 changed files with 456 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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[];
Expand All @@ -35,24 +39,28 @@ export type Reveal3DResourcesStyling = {
groups?: FdmAssetStylingGroup[];
};

export type Reveal3DResourcesProps = {
export type Reveal3DResourcesProps<NodeType = any> = {
resources: AddResourceOptions[];
fdmAssetMappingConfig?: FdmAssetMappingsConfig;
fdmAssetMappingConfig: FdmAssetMappingsConfig;
styling?: Reveal3DResourcesStyling;
onNodeClick?: (node: NodeDataResult<NodeType>) => void;
};

export const Reveal3DResources = ({
export const Reveal3DResources = <NodeType = any,>({
resources,
styling,
fdmAssetMappingConfig
}: Reveal3DResourcesProps): ReactElement => {
fdmAssetMappingConfig,
onNodeClick
}: Reveal3DResourcesProps<NodeType>): ReactElement => {
const [reveal3DModels, setReveal3DModels] = useState<TypedReveal3DModel[]>([]);
const [reveal3DModelsStyling, setReveal3DModelsStyling] = useState<
Array<PointCloudModelStyling | CadModelStyling>
>([]);

const { setModelsAdded } = useContext(ModelsLoadingStateContext);
const viewer = useReveal();
const fdmSdk = useFdmSdk();
const client = useSDK();
const numModelsLoaded = useRef(0);

useEffect(() => {
Expand All @@ -65,6 +73,29 @@ export const Reveal3DResources = ({
setReveal3DModelsStyling(modelsStyling);
}, [modelsStyling]);

useEffect(() => {
const callback = (event: PointerEventData): void => {
void (async (event: PointerEventData): Promise<void> => {
const data = await queryMappedData<NodeType>(
viewer,
client,
fdmSdk,
fdmAssetMappingConfig,
event
);
if (onNodeClick !== undefined && data !== undefined) {
onNodeClick?.(data);
}
})(event);
};

viewer.on('click', callback);

return () => {
viewer.off('click', callback);
};
}, [viewer, client, fdmSdk, fdmAssetMappingConfig, onNodeClick]);

const image360CollectionAddOptions = resources.filter(
(resource): resource is AddImageCollection360Options =>
(resource as AddImageCollection360Options).siteId !== undefined
Expand Down
172 changes: 172 additions & 0 deletions react-components/src/components/Reveal3DResources/queryMappedData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*!
* Copyright 2023 Cognite AS
*/

import { type Cognite3DViewer, type PointerEventData, type CogniteCadModel } from '@cognite/reveal';
import { type CogniteInternalId, type CogniteClient, type Node3D } from '@cognite/sdk';
import {
type EdgeItem,
type InspectResultList,
type FdmSDK,
type DmsUniqueIdentifier,
type Source,
type FdmNode
} from '../../utilities/FdmSDK';
import { type FdmAssetMappingsConfig } from '../../hooks/types';
import { type NodeDataResult } from './types';
import assert from 'assert';

export async function queryMappedData<NodeType>(
viewer: Cognite3DViewer,
cdfClient: CogniteClient,
fdmClient: FdmSDK,
fdmConfig: FdmAssetMappingsConfig,
clickEvent: PointerEventData
): Promise<NodeDataResult<NodeType> | undefined> {
const intersection = await viewer.getIntersectionFromPixel(
clickEvent.offsetX,
clickEvent.offsetY
);

if (intersection === null || intersection.type !== 'cad') {
return;
}

const cadIntersection = intersection;
const model = cadIntersection.model;

const ancestors = await getAncestorNodesForTreeIndex(cdfClient, model, cadIntersection.treeIndex);

const mappings = await getMappingEdges(
fdmClient,
fdmConfig,
model,
ancestors.map((n) => n.id)
);

if (mappings.edges.length === 0) {
return;
}

const selectedEdge = mappings.edges[0];
const selectedNodeId =
selectedEdge.properties[fdmConfig.source.space][
`${fdmConfig.source.externalId}/${fdmConfig.source.version}`
].revisionNodeId;
const selectedNode = ancestors.find((n) => n.id === selectedNodeId);
assert(selectedNode !== undefined);

const dataNode = selectedEdge.startNode;

const inspectionResult = await inspectNode(fdmClient, dataNode);

const dataView =
inspectionResult.items[0]?.inspectionResults.involvedViewsAndContainers?.views[0];

const nodeData = await filterNodeData<NodeType>(fdmClient, dataNode, dataView);

if (nodeData === undefined) {
return undefined;
}

return {
data: nodeData,
view: dataView,
cadNode: selectedNode,
model: cadIntersection.model
};
}

async function getAncestorNodesForTreeIndex(
client: CogniteClient,
model: CogniteCadModel,
treeIndex: number
): Promise<Node3D[]> {
const nodeId = await model.mapTreeIndexToNodeId(treeIndex);

const ancestorNodes = await client.revisions3D.list3DNodeAncestors(
model.modelId,
model.revisionId,
nodeId
);

return ancestorNodes.items;
}

async function getMappingEdges(
fdmClient: FdmSDK,
fdmConfig: FdmAssetMappingsConfig,
model: CogniteCadModel,
ancestorIds: CogniteInternalId[]
): Promise<{ edges: Array<EdgeItem<Record<string, any>>> }> {
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}`,
'revisionNodeId'
],
values: ancestorIds
}
}
]
};

return await fdmClient.filterAllInstances(filter, 'edge', fdmConfig.source);
}

async function inspectNode(
fdmClient: FdmSDK,
dataNode: DmsUniqueIdentifier
): Promise<InspectResultList> {
const inspectionResult = await fdmClient.inspectInstances({
inspectionOperations: { involvedViewsAndContainers: {} },
items: [
{
instanceType: 'node',
externalId: dataNode.externalId,
space: dataNode.space
}
]
});

return inspectionResult;
}

async function filterNodeData<NodeType>(
fdmClient: FdmSDK,
dataNode: DmsUniqueIdentifier,
dataView: Source
): Promise<FdmNode<NodeType> | undefined> {
if (dataView === undefined) {
return undefined;
}

const dataQueryResult = await fdmClient.getByExternalIds<NodeType>(
[{ instanceType: 'node', ...dataNode }],
dataView
);

return dataQueryResult.items[0];
}
17 changes: 16 additions & 1 deletion react-components/src/components/Reveal3DResources/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,29 @@
* Copyright 2023 Cognite AS
*/

import { type AddModelOptions, type SupportedModelTypes } from '@cognite/reveal';
import {
type CogniteCadModel,
type AddModelOptions,
type SupportedModelTypes
} from '@cognite/reveal';
import { type Matrix4 } from 'three';
import { type FdmNode, type Source } from '../../utilities/FdmSDK';
import { type Node3D } from '@cognite/sdk/dist/src';

export type AddImageCollection360Options = {
siteId: string;
};

export type FdmPropertyType<NodeType> = Record<string, Record<string, NodeType>>;

export type AddResourceOptions = AddReveal3DModelOptions | AddImageCollection360Options;

export type AddReveal3DModelOptions = AddModelOptions & { transform?: Matrix4 };
export type TypedReveal3DModel = AddReveal3DModelOptions & { type: SupportedModelTypes | '' };

export type NodeDataResult<NodeType> = {
data: FdmNode<NodeType>;
view: Source;
cadNode: Node3D;
model: CogniteCadModel;
};
5 changes: 5 additions & 0 deletions react-components/src/hooks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export type FdmAssetMappingsConfig = {
* FDM space where model assets are located
*/
assetFdmSpace: string;
/*
* Global FDM 3D space
* TODO: Remove when the system data model is functional
*/
global3dSpace: string;
};

export type ThreeDModelMappings = {
Expand Down
4 changes: 3 additions & 1 deletion react-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ export { CameraController } from './components/CameraController/CameraController
export type {
AddImageCollection360Options,
AddResourceOptions,
AddReveal3DModelOptions
AddReveal3DModelOptions,
NodeDataResult
} from './components/Reveal3DResources/types';
export type { Source } from './utilities/FdmSDK';
export { RevealToolbar } from './components/RevealToolbar/RevealToolbar';
export { useFdmAssetMappings } from './hooks/useFdmAssetMappings';
export { type FdmAssetMappingsConfig } from './hooks/types';
Expand Down
Loading

0 comments on commit bc0d4a7

Please sign in to comment.