Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react-components): add pagination support for styling hook allowing styling more than 1000 nodes #3544

Merged
merged 15 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ export function PointCloudContainer({
async function addModel(modelId: number, revisionId: number, transform?: Matrix4): Promise<void> {
const pointCloudModel = await viewer.addPointCloudModel({ modelId, revisionId });

viewer.fitCameraToModel(pointCloudModel);

if (transform !== undefined) {
pointCloudModel.setModelTransformation(transform);
}
Expand Down
8 changes: 7 additions & 1 deletion react-components/src/hooks/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*!
* Copyright 2023 Cognite AS
*/
import { type CogniteExternalId, type CogniteInternalId } from '@cognite/sdk';
import { type Source } from '../utilities/FdmSDK';

export type FdmAssetMappingsConfig = {
Expand All @@ -22,5 +23,10 @@ export type FdmAssetMappingsConfig = {
export type ThreeDModelMappings = {
modelId: number;
revisionId: number;
mappings: Array<{ nodeId: number; externalId: string }>;
mappings: Map<CogniteExternalId, CogniteInternalId>;
};

export type Model3DEdgeProperties = {
revisionId: number;
revisionNodeId: number;
};
119 changes: 79 additions & 40 deletions react-components/src/hooks/useCalculateModelsStyling.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
/*!
* Copyright 2023 Cognite AS
*/
import { useMemo } from 'react';
import { type FdmAssetMappingsConfig } from './types';
import { useEffect, useMemo } from 'react';
import { type ThreeDModelMappings, type FdmAssetMappingsConfig } from './types';
import { type Reveal3DResourcesStyling } from '../components/Reveal3DResources/Reveal3DResources';
import { type TypedReveal3DModel } from '../components/Reveal3DResources/types';
import { useFdmAssetMappings } from './useFdmAssetMappings';
import { type PointCloudModelStyling } from '../components/PointCloudContainer/PointCloudContainer';
import {
type CadModelStyling,
type NodeStylingGroup
} from '../components/CadModelContainer/CadModelContainer';
import { type CadModelStyling } from '../components/CadModelContainer/CadModelContainer';
import { type CogniteExternalId, type CogniteInternalId } from '@cognite/sdk';

/**
* Calculates the styling for the models based on the styling configuration and the mappings.
Expand All @@ -29,49 +27,30 @@ export const useCalculateModelsStyling = (
[styling]
);

const { data: mappings } = useFdmAssetMappings(stylingExternalIds, fdmAssetMappingConfig);
const {
data: mappings,
hasNextPage,
fetchNextPage,
isFetchingNextPage
} = useFdmAssetMappings(stylingExternalIds, fdmAssetMappingConfig);

useEffect(() => {
if (hasNextPage !== undefined && !isFetchingNextPage) {
void fetchNextPage();
}
}, [hasNextPage, fetchNextPage]);

const modelsStyling = useMemo(() => {
Savokr marked this conversation as resolved.
Show resolved Hide resolved
if (styling === undefined || models === undefined) return [];

const allPagesMappings = mappings?.pages.flatMap((page) => page.items);

const internalModelsStyling = models.map((model) => {
let modelStyling: PointCloudModelStyling | CadModelStyling;

switch (model.type) {
case 'cad': {
const modelNodeMappings = mappings?.find(
(mapping) =>
mapping.modelId === model.modelId && mapping.revisionId === model.revisionId
);

const newStylingGroups: NodeStylingGroup[] | undefined =
styling.groups !== null ? [] : undefined;

styling.groups?.forEach((group) => {
const connectedExternalIds = group.fdmAssetExternalIds.filter(
(externalId) =>
modelNodeMappings?.mappings.some(
(modelNodeMapping) => modelNodeMapping.externalId === externalId
)
);

const newGroup: NodeStylingGroup = {
style: group.style.cad,
nodeIds: connectedExternalIds.map((externalId) => {
const mapping = modelNodeMappings?.mappings.find(
(mapping) => mapping.externalId === externalId
);
return mapping?.nodeId ?? -1;
})
};

if (connectedExternalIds.length > 0) newStylingGroups?.push(newGroup);
});

modelStyling = {
defaultStyle: styling.defaultStyle?.cad,
groups: newStylingGroups
};
modelStyling = calculateCadModelStyling(styling, allPagesMappings, model);
break;
}
case 'pointcloud': {
Expand All @@ -94,3 +73,63 @@ export const useCalculateModelsStyling = (

return modelsStyling;
};

function getModelMappings(
mappings: ThreeDModelMappings[] | undefined,
model: TypedReveal3DModel
): Map<CogniteExternalId, CogniteInternalId> | undefined {
return mappings
?.filter(
(mapping) => mapping.modelId === model.modelId && mapping.revisionId === model.revisionId
)
.reduce(
(acc, mapping) => {
// reduce is added to avoid duplicate models from several pages.
mergeMaps(acc.mappings, mapping.mappings);
return acc;
},
{ modelId: model.modelId, revisionId: model.revisionId, mappings: new Map<string, number>() }
).mappings;
}

function calculateCadModelStyling(
styling: Reveal3DResourcesStyling,
mappings: ThreeDModelMappings[] | undefined,
model: TypedReveal3DModel
): CadModelStyling {
const modelMappings = getModelMappings(mappings, model);

const resourcesStylingGroups = styling?.groups;

if (resourcesStylingGroups === undefined || modelMappings === undefined)
return {
defaultStyle: styling.defaultStyle?.cad
};

const modelStylingGroups = resourcesStylingGroups
.map((resourcesGroup) => {
const modelMappedNodeIds = resourcesGroup.fdmAssetExternalIds
.map((externalId) => modelMappings.get(externalId))
.filter((nodeId): nodeId is number => nodeId !== undefined);

return {
style: resourcesGroup.style.cad,
nodeIds: modelMappedNodeIds
};
})
.filter((group) => group.nodeIds.length > 0);

return {
defaultStyle: styling.defaultStyle?.cad,
groups: modelStylingGroups
};
}

function mergeMaps(
targetMap: Map<string, number>,
addedMap: Map<string, number>
): Map<string, number> {
addedMap.forEach((value, key) => targetMap.set(key, value));

return targetMap;
}
45 changes: 25 additions & 20 deletions react-components/src/hooks/useFdmAssetMappings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
*/
import { type CogniteExternalId } from '@cognite/sdk';
import { useFdmSdk } from '../components/RevealContainer/SDKProvider';
import { type UseQueryResult, useQuery } from '@tanstack/react-query';
import { type FdmAssetMappingsConfig, type ThreeDModelMappings } from './types';
import { useInfiniteQuery, type UseInfiniteQueryResult } from '@tanstack/react-query';
import {
type Model3DEdgeProperties,
type FdmAssetMappingsConfig,
type ThreeDModelMappings
} from './types';
import { DEFAULT_QUERY_STALE_TIME } from '../utilities/constants';

/**
Expand All @@ -13,13 +17,13 @@ import { DEFAULT_QUERY_STALE_TIME } from '../utilities/constants';
export const useFdmAssetMappings = (
fdmAssetExternalIds: CogniteExternalId[],
fdmConfig?: FdmAssetMappingsConfig
): UseQueryResult<ThreeDModelMappings[]> => {
): UseInfiniteQueryResult<{ items: ThreeDModelMappings[]; nextCursor: string }> => {
const fdmSdk = useFdmSdk();

return useQuery(
return useInfiniteQuery(
['reveal', 'react-components', fdmAssetExternalIds],
async () => {
if (fdmAssetExternalIds?.length === 0) return [];
async ({ pageParam }) => {
if (fdmAssetExternalIds?.length === 0) return { items: [], nextCursor: undefined };
if (fdmConfig === undefined)
throw Error('FDM config must be defined when using FDM asset mappings');

Expand All @@ -36,16 +40,16 @@ export const useFdmAssetMappings = (
const instances = await fdmSdk.filterInstances(
fdmAssetMappingFilter,
'edge',
fdmConfig.source
fdmConfig.source,
pageParam
);

const modelMappingsTemp: ThreeDModelMappings[] = [];

instances.edges.forEach((instance) => {
const mappingProperty =
instance.properties[fdmConfig.source.space][
`${fdmConfig.source.externalId}/${fdmConfig.source.version}`
];
const mappingProperty = instance.properties[fdmConfig.source.space][
`${fdmConfig.source.externalId}/${fdmConfig.source.version}`
] as Model3DEdgeProperties;

const modelId = Number.parseInt(instance.endNode.externalId.slice(9));
const revisionId = mappingProperty.revisionId;
Expand All @@ -55,27 +59,28 @@ export const useFdmAssetMappings = (
);

if (!isAdded) {
const mappingsMap = new Map<string, number>();
mappingsMap.set(instance.startNode.externalId, mappingProperty.revisionNodeId);

modelMappingsTemp.push({
modelId,
revisionId,
mappings: [
{ nodeId: mappingProperty.revisionNodeId, externalId: instance.startNode.externalId }
]
mappings: mappingsMap
});
} else {
const modelMapping = modelMappingsTemp.find(
(mapping) => mapping.modelId === modelId && mapping.revisionId === revisionId
);

modelMapping?.mappings.push({
nodeId: mappingProperty.revisionNodeId,
externalId: instance.startNode.externalId
});
modelMapping?.mappings.set(instance.startNode.externalId, mappingProperty.revisionNodeId);
}
});

return modelMappingsTemp;
return { items: modelMappingsTemp, nextCursor: instances.nextCursor };
},
{ staleTime: DEFAULT_QUERY_STALE_TIME }
{
staleTime: DEFAULT_QUERY_STALE_TIME,
getNextPageParam: (lastPage) => lastPage.nextCursor
}
);
};
37 changes: 37 additions & 0 deletions react-components/src/hooks/useMappedEquipmentBy3DModelsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*!
* Copyright 2023 Cognite AS
*/
import { type UseQueryResult, useQuery } from '@tanstack/react-query';
import { type FdmAssetMappingsConfig } from '..';
import { useFdmSdk } from '../components/RevealContainer/SDKProvider';

export const useMappedEquipmentBy3DModelsList = (
Default3DFdmConfig: FdmAssetMappingsConfig,
modelsList: Array<{ modelId: number; revisionId: number }> = []
): UseQueryResult<string[]> => {
const fdmClient = useFdmSdk();

return useQuery(
['reveal', 'react-components', ...modelsList.map(({ modelId }) => modelId.toString()).sort()],
async () => {
const filter = {
in: {
property: ['edge', 'endNode'],
values: modelsList.map(({ modelId }) => ({
space: Default3DFdmConfig.global3dSpace,
externalId: `model_3d_${modelId}`
}))
}
};

const mappings = await fdmClient.filterAllInstances(
filter,
'edge',
Default3DFdmConfig.source
);

return mappings.edges.map((edge) => edge.startNode.externalId);
},
{ staleTime: Infinity }
);
};
12 changes: 1 addition & 11 deletions react-components/stories/HighlightNode.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,7 @@ import { Color, Matrix4 } from 'three';
import { type ReactElement, useState } from 'react';
import { DefaultNodeAppearance, TreeIndexNodeCollection } from '@cognite/reveal';
import { createSdkByUrlToken } from './utilities/createSdkByUrlToken';

const DefaultFdmConfig: FdmAssetMappingsConfig = {
source: {
space: 'hf_3d_schema',
version: '1',
type: 'view',
externalId: 'cdf_3d_connection_data'
},
global3dSpace: 'hf_3d_global_data',
assetFdmSpace: 'hf_customer_a'
};
import { DefaultFdmConfig } from './utilities/fdmConfig';

const meta = {
title: 'Example/HighlightNode',
Expand Down
Loading
Loading