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 CAD models style caching support #3603

Merged
merged 15 commits into from
Aug 21, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,17 @@
* Copyright 2023 Cognite AS
*/
import { type ReactElement, useEffect, useState } from 'react';
import {
type NodeAppearance,
type AddModelOptions,
type CogniteCadModel,
TreeIndexNodeCollection,
NodeIdNodeCollection,
DefaultNodeAppearance,
type NodeCollection,
type Cognite3DViewer
} from '@cognite/reveal';
import { type AddModelOptions, type CogniteCadModel } from '@cognite/reveal';
import { useReveal } from '../RevealContainer/RevealContext';
import { Matrix4 } from 'three';
import { useSDK } from '../RevealContainer/SDKProvider';
import { type CogniteClient } from '@cognite/sdk';
import { useRevealKeepAlive } from '../RevealKeepAlive/RevealKeepAliveContext';
import {
type CadModelStyling,
useApplyCadModelStyling,
modelExists
} from './useApplyCadModelStyling';

export type NodeStylingGroup = {
nodeIds: number[];
style?: NodeAppearance;
};

export type TreeIndexStylingGroup = {
treeIndices: number[];
style?: NodeAppearance;
};

export type CadModelStyling = {
defaultStyle?: NodeAppearance;
groups?: Array<NodeStylingGroup | TreeIndexStylingGroup>;
};

type CogniteCadModelProps = {
export type CogniteCadModelProps = {
addModelOptions: AddModelOptions;
styling?: CadModelStyling;
transform?: Matrix4;
Expand All @@ -47,13 +26,13 @@ export function CadModelContainer({
onLoad
}: CogniteCadModelProps): ReactElement {
const cachedViewerRef = useRevealKeepAlive();
const [model, setModel] = useState<CogniteCadModel>();

const viewer = useReveal();
const sdk = useSDK();

const defaultStyle = styling?.defaultStyle ?? DefaultNodeAppearance.Default;
const styleGroups = styling?.groups;
const [model, setModel] = useState<CogniteCadModel | undefined>(
viewer.models.find(
(m) => m.modelId === addModelOptions.modelId && m.revisionId === addModelOptions.revisionId
) as CogniteCadModel | undefined
);

const { modelId, revisionId, geometryFilter } = addModelOptions;

Expand All @@ -63,33 +42,11 @@ export function CadModelContainer({

useEffect(() => {
if (!modelExists(model, viewer) || transform === undefined) return;

model.setModelTransformation(transform);
}, [transform, model]);

useEffect(() => {
if (!modelExists(model, viewer) || styleGroups === undefined) return;
const stylingCollections = applyStyling(sdk, model, styleGroups);

return () => {
if (!modelExists(model, viewer)) return;
void stylingCollections.then((nodeCollections) => {
nodeCollections.forEach((nodeCollection) => {
model.unassignStyledNodeCollection(nodeCollection);
});
});
};
}, [styleGroups, model]);

useEffect(() => {
if (!modelExists(model, viewer)) return;
model.setDefaultNodeAppearance(defaultStyle);
return () => {
if (!modelExists(model, viewer)) {
return;
}
model.setDefaultNodeAppearance(DefaultNodeAppearance.Default);
};
}, [defaultStyle, model]);
useApplyCadModelStyling(model, styling);
Savokr marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => removeModel, [model]);

Expand Down Expand Up @@ -134,31 +91,3 @@ export function CadModelContainer({
setModel(undefined);
}
}

async function applyStyling(
sdk: CogniteClient,
model: CogniteCadModel,
stylingGroups: Array<NodeStylingGroup | TreeIndexStylingGroup>
): Promise<NodeCollection[]> {
const collections: NodeCollection[] = [];
for (const group of stylingGroups) {
if ('treeIndices' in group && group.style !== undefined) {
const nodes = new TreeIndexNodeCollection(group.treeIndices);
model.assignStyledNodeCollection(nodes, group.style);
collections.push(nodes);
} else if ('nodeIds' in group && group.style !== undefined) {
const nodes = new NodeIdNodeCollection(sdk, model);
await nodes.executeFilter(group.nodeIds);
model.assignStyledNodeCollection(nodes, group.style);
collections.push(nodes);
}
}
return collections;
}

function modelExists(
model: CogniteCadModel | undefined,
viewer: Cognite3DViewer
): model is CogniteCadModel {
return model !== undefined && viewer.models.includes(model);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*!
* Copyright 2023 Cognite AS
*/
import {
type CogniteCadModel,
DefaultNodeAppearance,
type NodeAppearance,
type NodeCollection,
NodeIdNodeCollection,
TreeIndexNodeCollection,
type Cognite3DViewer
} from '@cognite/reveal';
import { useEffect } from 'react';
import { useSDK } from '../RevealContainer/SDKProvider';
import { type CogniteClient } from '@cognite/sdk';
import { isEqual } from 'lodash';
import { useReveal } from '../RevealContainer/RevealContext';

export type NodeStylingGroup = {
nodeIds: number[];
style?: NodeAppearance;
};

export type TreeIndexStylingGroup = {
treeIndices: number[];
style?: NodeAppearance;
};

export type CadModelStyling = {
defaultStyle?: NodeAppearance;
groups?: Array<NodeStylingGroup | TreeIndexStylingGroup>;
};

export const useApplyCadModelStyling = (
model?: CogniteCadModel,
modelStyling?: CadModelStyling
): void => {
const viewer = useReveal();
const sdk = useSDK();

const defaultStyle = modelStyling?.defaultStyle ?? DefaultNodeAppearance.Default;
const styleGroups = modelStyling?.groups;

useEffect(() => {
if (!modelExists(model, viewer) || styleGroups === undefined) return;

void applyStyling(sdk, model, styleGroups);
}, [styleGroups, model]);

useEffect(() => {
if (!modelExists(model, viewer)) return;

model.setDefaultNodeAppearance(defaultStyle);
}, [defaultStyle, model]);
};

async function applyStyling(
sdk: CogniteClient,
model: CogniteCadModel,
stylingGroups: Array<NodeStylingGroup | TreeIndexStylingGroup>
): Promise<void> {
const firstChangeIndex = getFirstChangeIndex();

for (let i = firstChangeIndex; i < model.styledNodeCollections.length; i++) {
const viewerStyledNodeCollection = model.styledNodeCollections[i];
model.unassignStyledNodeCollection(viewerStyledNodeCollection.nodeCollection);
}

for (let i = firstChangeIndex; i < stylingGroups.length; i++) {
const stylingGroup = stylingGroups[i];

if (stylingGroup.style === undefined) continue;

if ('treeIndices' in stylingGroup) {
const nodes = new TreeIndexNodeCollection(stylingGroup.treeIndices);
model.assignStyledNodeCollection(nodes, stylingGroup.style);
}

if ('nodeIds' in stylingGroup) {
const nodes = new NodeIdNodeCollection(sdk, model);
await nodes.executeFilter(stylingGroup.nodeIds);
model.assignStyledNodeCollection(nodes, stylingGroup.style);
}
}

function getFirstChangeIndex(): number {
for (let i = 0; i < model.styledNodeCollections.length; i++) {
const stylingGroup = stylingGroups[i];
const viewerStyledNodeCollection = model.styledNodeCollections[i];

const areEqual = isEqualStylingGroupAndCollection(stylingGroup, viewerStyledNodeCollection);

if (!areEqual) {
return i;
}
}

return model.styledNodeCollections.length;
}
}

function isEqualStylingGroupAndCollection(
group: NodeStylingGroup | TreeIndexStylingGroup,
collection: {
nodeCollection: NodeCollection;
appearance: NodeAppearance;
}
): boolean {
if (group?.style === undefined) return false;

const isEqualGroupStyle = isEqualStyle(collection.appearance, group.style);

if (collection.nodeCollection instanceof TreeIndexNodeCollection && 'treeIndices' in group) {
const compareCollection = new TreeIndexNodeCollection(group.treeIndices);
const isEqualContent = isEqualTreeIndex(collection.nodeCollection, compareCollection);

return isEqualGroupStyle && isEqualContent;
}

if (collection.nodeCollection instanceof NodeIdNodeCollection && 'nodeIds' in group) {
const collectionNodeIds = collection.nodeCollection.serialize().state.nodeIds as number[];
const isEqualContent = isEqual(collectionNodeIds, group.nodeIds);

return isEqualGroupStyle && isEqualContent;
}

return false;
}

function isEqualTreeIndex(
collectionA: TreeIndexNodeCollection,
collectionB: TreeIndexNodeCollection
): boolean {
const isEqualContent =
collectionA.getIndexSet().differenceWith(collectionB.getIndexSet()).count === 0;
return isEqualContent;
}

function isEqualStyle(styleA: NodeAppearance, styleB: NodeAppearance): boolean {
const { color: colorA, ...restA } = styleA;
const { color: colorB, ...restB } = styleB;

const color =
colorA === undefined || colorB === undefined
? Boolean(colorA ?? colorB)
: colorA.equals(colorB);

return color && isEqual(restA, restB);
}

export function modelExists(
model: CogniteCadModel | undefined,
viewer: Cognite3DViewer
): model is CogniteCadModel {
return model !== undefined && viewer.models.includes(model);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
*/
import { useRef, type ReactElement, useState, useEffect } from 'react';
import { type Cognite3DViewer } from '@cognite/reveal';
import { CadModelContainer, type CadModelStyling } from '../CadModelContainer/CadModelContainer';
import { CadModelContainer } from '../CadModelContainer/CadModelContainer';
import { type CadModelStyling } from '../CadModelContainer/useApplyCadModelStyling';
import {
PointCloudContainer,
type PointCloudModelStyling
Expand Down
8 changes: 4 additions & 4 deletions react-components/src/hooks/useCalculateModelsStyling.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ import {
type TypedReveal3DModel
} from '../components/Reveal3DResources/types';
import { type PointCloudModelStyling } from '../components/PointCloudContainer/PointCloudContainer';
import {
type NodeStylingGroup,
type CadModelStyling
} from '../components/CadModelContainer/CadModelContainer';
import { type InModel3dEdgeProperties } from '../utilities/globalDataModels';
import { type EdgeItem } from '../utilities/FdmSDK';
import { type NodeAppearance } from '@cognite/reveal';
Expand All @@ -18,6 +14,10 @@ import { type CogniteExternalId, type CogniteInternalId } from '@cognite/sdk';
import { useFdmAssetMappings } from './useFdmAssetMappings';
import { useEffect, useMemo } from 'react';
import { useMappedEdgesForRevisions } from '../components/NodeCacheProvider/NodeCacheProvider';
import {
type CadModelStyling,
type NodeStylingGroup
} from '../components/CadModelContainer/useApplyCadModelStyling';

type ModelStyleGroup = {
model: TypedReveal3DModel;
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 @@ -21,6 +21,7 @@ export { use3DModelName } from './hooks/use3DModelName';
export { useFdmAssetMappings } from './hooks/useFdmAssetMappings';
export { useClickedNodeData, type ClickedNodeData } from './hooks/useClickedNode';
export { useCameraNavigation } from './hooks/useCameraNavigation';
export { useMappedEdgesForRevisions } from './components/NodeCacheProvider/NodeCacheProvider';

// Higher order components
export { withSuppressRevealEvents } from './higher-order-components/withSuppressRevealEvents';
Expand All @@ -30,11 +31,12 @@ export {
type PointCloudModelStyling,
type AnnotationIdStylingGroup
} from './components/PointCloudContainer/PointCloudContainer';
export { type CogniteCadModelProps } from './components/CadModelContainer/CadModelContainer';
export {
type CadModelStyling,
type TreeIndexStylingGroup,
type NodeStylingGroup
} from './components/CadModelContainer/CadModelContainer';
} from './components/CadModelContainer/useApplyCadModelStyling';
export {
type Reveal3DResourcesProps,
type FdmAssetStylingGroup,
Expand Down
Loading
Loading