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
@@ -1,22 +1,17 @@
/*!
* Copyright 2023 Cognite AS
*/
import { type ReactElement, useEffect, useState } from 'react';
import { type ReactElement, useEffect, useState, useRef } from 'react';
import {
type NodeAppearance,
type AddModelOptions,
type CogniteCadModel,
TreeIndexNodeCollection,
NodeIdNodeCollection,
DefaultNodeAppearance,
type NodeCollection,
type Cognite3DViewer
} 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 { useApplyCadModelStyling } from './useApplyCadModelStyling';

export type NodeStylingGroup = {
nodeIds: number[];
Expand All @@ -33,7 +28,7 @@ export type CadModelStyling = {
groups?: Array<NodeStylingGroup | TreeIndexStylingGroup>;
};

type CogniteCadModelProps = {
export type CogniteCadModelProps = {
addModelOptions: AddModelOptions;
styling?: CadModelStyling;
transform?: Matrix4;
Expand All @@ -47,14 +42,10 @@ 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);

const { modelId, revisionId, geometryFilter } = addModelOptions;

useEffect(() => {
Expand All @@ -63,33 +54,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 @@ -135,28 +104,7 @@ export function CadModelContainer({
}
}

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(
export function modelExists(
model: CogniteCadModel | undefined,
viewer: Cognite3DViewer
): model is CogniteCadModel {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { CogniteCadModel, DefaultNodeAppearance, NodeAppearance, NodeCollection, NodeIdNodeCollection, TreeIndexNodeCollection, IndexSet } from "@cognite/reveal";
import { useEffect } from "react";
import { CadModelStyling, useReveal } from "../..";
import { NodeStylingGroup, TreeIndexStylingGroup, modelExists } from "./CadModelContainer";
Savokr marked this conversation as resolved.
Show resolved Hide resolved
import { useSDK } from "../RevealContainer/SDKProvider";
import { CogniteClient } from "@cognite/sdk";
import { isEqual } from "lodash";

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

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

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

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

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

model.setDefaultNodeAppearance(defaultStyle);

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

model.setDefaultNodeAppearance(DefaultNodeAppearance.Default);
Savokr marked this conversation as resolved.
Show resolved Hide resolved
};
}, [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 Array<number>;
const isEqualContent = isEqual(collectionNodeIds, group.nodeIds);

return isEqualGroupStyle && isEqualContent;
}

return false;
}

function isEqualTreeIndex(collectionA: TreeIndexNodeCollection, collectionB: TreeIndexNodeCollection): boolean {
const isEqualContent = collectionA.getIndexSet().intersectWith(collectionB.getIndexSet()).count === collectionA.getIndexSet().count;
Savokr marked this conversation as resolved.
Show resolved Hide resolved
return isEqualContent;
}

function isEqualStyle(styleA: NodeAppearance, styleB: NodeAppearance): boolean {
const color = (styleA.color === undefined || styleB.color === undefined) ? Boolean(styleA.color ?? styleB.color) : styleA.color.equals(styleB.color);
const visible = styleA.visible === styleB.visible;
const renderInFront = styleA.renderInFront === styleB.renderInFront;
const renderGhosted = styleA.renderGhosted === styleB.renderGhosted;
const outlineColor = styleA.outlineColor === styleB.outlineColor;
const prioritizedForLoadingHint = styleA.prioritizedForLoadingHint === styleB.prioritizedForLoadingHint;

return (color && visible && renderInFront && renderGhosted && outlineColor && prioritizedForLoadingHint);
}
Savokr marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 2 additions & 1 deletion react-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export {
export {
type CadModelStyling,
type TreeIndexStylingGroup,
type NodeStylingGroup
type NodeStylingGroup,
type CogniteCadModelProps,
} from './components/CadModelContainer/CadModelContainer';
export {
type Reveal3DResourcesProps,
Expand Down
Loading