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): implement styling capability for 3D resources component and models containers #3485

Merged
merged 31 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4b3d0de
feat: add container for CAD model
christjt Jun 29, 2023
a669d7e
feat: add 360 image collection container
christjt Jun 30, 2023
4309d88
fix: add 3d resource container
christjt Jul 3, 2023
a07843c
feat: add CameraController component
christjt Jul 3, 2023
cbcec38
fix: add missing import
christjt Jul 3, 2023
2dd7ead
Merge master into christjt/UX-847
cognite-bulldozer[bot] Jul 3, 2023
b363d07
Merge master into christjt/UX-847
cognite-bulldozer[bot] Jul 3, 2023
7128636
feat: infer model type using Reveal's determineModelType
christjt Jul 3, 2023
1a3ede0
Merge branch 'christjt/UX-847' of https://github.com/cognitedata/reve…
christjt Jul 3, 2023
1c0cc1c
Merge refs/heads/master into christjt/UX-847
cognite-bulldozer[bot] Jul 4, 2023
bfc5a32
Merge refs/heads/master into christjt/UX-847
cognite-bulldozer[bot] Jul 4, 2023
194860a
Merge refs/heads/master into christjt/UX-847
cognite-bulldozer[bot] Jul 4, 2023
b3e395f
Working styling for CAD
Savokr Jul 6, 2023
43639f9
Merge branch 'master' into savokr/react-styling-components
Savokr Jul 6, 2023
023c14f
Annotation testing capability
Savokr Jul 6, 2023
d21043c
Working point cloud styling
Savokr Jul 6, 2023
a8c138e
Working default style for 3d resources
Savokr Jul 6, 2023
35d21dc
Working styling based on externalIds
Savokr Jul 11, 2023
552c8c4
Working styling component
Savokr Jul 11, 2023
7277d22
Removed annotation styling scaffolding
Savokr Jul 12, 2023
4ebcccf
Fixing lint issues
Savokr Jul 12, 2023
4962370
Merge branch 'master' into savokr/react-styling-components
Savokr Jul 12, 2023
218f64b
Added dev tools
Savokr Jul 12, 2023
f46bf0e
Added FDM config
Savokr Jul 12, 2023
2b24a28
Fixed yarn.lock
Savokr Jul 12, 2023
bc8ef9f
Examples lint
Savokr Jul 12, 2023
5aee57e
Refactored annotation creation
Savokr Jul 12, 2023
edd1420
Added types for hooks
Savokr Jul 12, 2023
11fde8e
Added staleTime constant
Savokr Jul 12, 2023
69b0165
Styling extracted as a hook
Savokr Jul 12, 2023
062d6b0
Merge refs/heads/master into savokr/react-styling-components
cognite-bulldozer[bot] Jul 13, 2023
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
27 changes: 27 additions & 0 deletions examples/src/pages/Viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,35 @@ export function Viewer() {
new THREE.SphereGeometry(0.1),
new THREE.MeshBasicMaterial({ color: 'red' })
);

sphere.position.copy(point);
viewer.addObject3D(sphere);

if (pointCloudObjectsUi.createAnnotationsOnClick) {
Savokr marked this conversation as resolved.
Show resolved Hide resolved
const cdfPosition = point.clone().applyMatrix4(model.getCdfToDefaultModelTransformation().invert());

const annotation = await client.annotations.create([{
annotatedResourceId: model.modelId,
annotatedResourceType: 'threedmodel',
annotationType: 'pointcloud.BoundingVolume',
status: 'suggested',
creatingApp: 'reveal-examples',
creatingUser: 'reveal-user',
creatingAppVersion: '0.0.1',
data: {
label: 'Dummy annotation',
region: [
{
box: {
matrix: new THREE.Matrix4().makeTranslation(cdfPosition.x, cdfPosition.y, cdfPosition.z).transpose().elements,
}
}
]
},
}])

console.log('Annotation successfully created', annotation);
}
}
}
break;
Expand Down
24 changes: 21 additions & 3 deletions examples/src/utils/PointCloudObjectStylingUI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class PointCloudObjectStylingUI {
creatingUser: '',
assetRef: 0
};
private _createAnnotationsOnClick = false;

constructor(uiFolder: dat.GUI, model: CognitePointCloudModel, viewer: Cognite3DViewer, client: CogniteClient) {
this._model = model;
Expand All @@ -53,10 +54,18 @@ export class PointCloudObjectStylingUI {
this.createAnnotationUi(this._selectedAnnotationFolder);

const state = {
showBoundingBoxes: false
showBoundingBoxes: false,
createAnnotationsOnClick: false
};

const actions = {
deleteAllAnnotations: async () => {
const annotations = await client.annotations.list({ filter: { annotatedResourceIds: [{ id: model.modelId }], annotatedResourceType: 'threedmodel' } }).autoPagingToArray({ limit: 1000 });

await client.annotations.delete(annotations.map(annotation => ({ id: annotation.id })));

console.log(`Deleted ${annotations.length} annotations`);
},
reset: () => {
this._model.removeAllStyledObjectCollections();
},
Expand All @@ -75,13 +84,22 @@ export class PointCloudObjectStylingUI {
});
}
};


uiFolder.addFolder('DANGER ZONE').add(actions, 'deleteAllAnnotations').name('Delete all annotations for model');
uiFolder.add(actions, 'reset').name('Reset all styled objects');
uiFolder.add(actions, 'randomColors').name('Set random for objects');
uiFolder.add(actions, 'randomColors').name('Set random colors for objects');
uiFolder
.add(state, 'showBoundingBoxes')
.name('Show object bounding boxes')
.onChange((value: boolean) => this.toggleObjectBoundingBoxes(value));
uiFolder
.add(state, 'createAnnotationsOnClick')
.name('Create annotations on model click')
.onChange((value: boolean) => (this._createAnnotationsOnClick = value));
}

get createAnnotationsOnClick() {
return this._createAnnotationsOnClick;
}

async updateSelectedAnnotation(annotationId: number | undefined) {
Expand Down
2 changes: 2 additions & 0 deletions react-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@storybook/react": "7.0.26",
"@storybook/react-webpack5": "7.0.26",
"@storybook/testing-library": "0.2.0",
"@tanstack/react-query-devtools": "^4.29.19",
"@types/lodash": "^4.14.190",
"@types/react": "18.2.7",
"@types/styled-components": "5.1.26",
Expand Down Expand Up @@ -69,6 +70,7 @@
"dist"
],
"dependencies": {
"@tanstack/react-query": "^4.29.19",
"lodash": "^4.17.21",
"style-loader": "^3.3.3"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,52 @@
/*!
* Copyright 2023 Cognite AS
*/
import { type ReactElement, useEffect, useRef } from 'react';
import { type AddModelOptions, type CogniteCadModel } from '@cognite/reveal';
import { type ReactElement, useEffect, useState } from 'react';
import {
type NodeAppearance,
type AddModelOptions,
type CogniteCadModel,
TreeIndexNodeCollection,
NodeIdNodeCollection,
DefaultNodeAppearance
} from '@cognite/reveal';
import { useReveal } from '../RevealContainer/RevealContext';
import { type Matrix4 } from 'three';
import { useSDK } from '../RevealContainer/SDKProvider';
import { type CogniteClient } from '@cognite/sdk';

type Cognite3dModelProps = {
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 = {
addModelOptions: AddModelOptions;
styling?: CadModelStyling;
transform?: Matrix4;
onLoad?: () => void;
onLoad?: (model: CogniteCadModel) => void;
};

export function CadModelContainer({
addModelOptions,
transform,
styling,
onLoad
}: Cognite3dModelProps): ReactElement {
const modelRef = useRef<CogniteCadModel>();
}: CogniteCadModelProps): ReactElement {
const [model, setModel] = useState<CogniteCadModel>();
pramodcog marked this conversation as resolved.
Show resolved Hide resolved
const viewer = useReveal();
const sdk = useSDK();

const { modelId, revisionId, geometryFilter } = addModelOptions;

useEffect(() => {
Expand All @@ -27,29 +55,67 @@ export function CadModelContainer({
}, [modelId, revisionId, geometryFilter]);

useEffect(() => {
if (modelRef.current === undefined || transform === undefined) return;
modelRef.current.setModelTransformation(transform);
}, [transform]);
if (model === undefined || transform === undefined) return;
model.setModelTransformation(transform);
}, [transform, model]);

useEffect(() => {
if (model === undefined || styling === undefined) return;

applyStyling(sdk, model, styling).catch(console.error);

return () => {
model.removeAllStyledNodeCollections();
model.setDefaultNodeAppearance(DefaultNodeAppearance.Default);
};
}, [styling, model]);

return <></>;

async function addModel(
modelId: number,
revisionId: number,
transform?: Matrix4,
onLoad?: () => void
): Promise<void> {
onLoad?: (model: CogniteCadModel) => void
): Promise<CogniteCadModel> {
pramodcog marked this conversation as resolved.
Show resolved Hide resolved
const cadModel = await viewer.addCadModel({ modelId, revisionId });
if (transform !== undefined) {
cadModel.setModelTransformation(transform);
}
modelRef.current = cadModel;
onLoad?.();
setModel(cadModel);
onLoad?.(cadModel);

return cadModel;
}

function removeModel(): void {
if (modelRef.current === undefined || !viewer.models.includes(modelRef.current)) return;
viewer.removeModel(modelRef.current);
modelRef.current = undefined;
if (model === undefined || !viewer.models.includes(model)) return;
viewer.removeModel(model);
setModel(undefined);
}
}

async function applyStyling(
sdk: CogniteClient,
model: CogniteCadModel,
styling?: CadModelStyling
): Promise<void> {
if (styling === undefined) return;

if (styling.defaultStyle !== undefined) {
model.setDefaultNodeAppearance(styling.defaultStyle);
}

if (styling.groups !== undefined) {
for (const group of styling.groups) {
if ('treeIndices' in group && group.style !== undefined) {
const nodes = new TreeIndexNodeCollection(group.treeIndices);
model.assignStyledNodeCollection(nodes, group.style);
} else if ('nodeIds' in group && group.style !== undefined) {
const nodes = new NodeIdNodeCollection(sdk, model);
await nodes.executeFilter(group.nodeIds);
model.assignStyledNodeCollection(nodes, group.style);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,41 @@
import { type ReactElement, useEffect, useContext, useRef } from 'react';
import { useReveal } from '../RevealContainer/RevealContext';
import { ModelsLoadingStateContext } from '../Reveal3DResources/ModelsLoadingContext';
import { type CameraState } from '@cognite/reveal';
import {
DefaultCameraManager,
type CameraControlsOptions,
type CameraState
} from '@cognite/reveal';

export type CameraControllerProps = {
initialFitCamera?: FittingStrategy;
cameraControlsOptions?: CameraControlsOptions;
};

type FittingStrategy =
| { to: 'cameraState'; state: CameraState }
| { to: 'allModels' }
| { to: 'none' };

export function CameraController({ initialFitCamera }: CameraControllerProps): ReactElement {
export function CameraController({
initialFitCamera,
cameraControlsOptions
}: CameraControllerProps): ReactElement {
const initialCameraSet = useRef(false);
const viewer = useReveal();
const { modelsAdded } = useContext(ModelsLoadingStateContext);

const fittingStrategy: Required<FittingStrategy> = initialFitCamera ?? { to: 'allModels' };

useEffect(() => {
if (cameraControlsOptions === undefined) return;

if (!(viewer.cameraManager instanceof DefaultCameraManager))
throw new Error('CameraControlsOptions can be set only on default CameraManager');

viewer.cameraManager.setCameraControlsOptions(cameraControlsOptions);
}, [cameraControlsOptions]);

useEffect(() => {
if (initialCameraSet.current) return;
if (fittingStrategy.to === 'none') {
Expand All @@ -34,7 +51,7 @@ export function CameraController({ initialFitCamera }: CameraControllerProps): R
return;
}
if (fittingStrategy.to === 'allModels' && modelsAdded) {
viewer.fitCameraToModels(viewer.models, 0, true);
viewer.fitCameraToModels(undefined, undefined, true);
pramodcog marked this conversation as resolved.
Show resolved Hide resolved
initialCameraSet.current = true;
}
}, [modelsAdded]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { type Image360Collection } from '@cognite/reveal';

type Image360CollectionContainerProps = {
siteId: string;
onLoad?: () => void;
onLoad?: (image360: Image360Collection) => void;
};

export function Image360CollectionContainer({
Expand All @@ -27,7 +27,7 @@ export function Image360CollectionContainer({
async function add360Collection(): Promise<void> {
const image360Collection = await viewer.add360ImageSet('events', { site_id: siteId });
modelRef.current = image360Collection;
onLoad?.();
onLoad?.(image360Collection);
}

function remove360Collection(): void {
Expand Down
Loading
Loading