Skip to content

Commit

Permalink
feat(react-components): add useCameraNavigation hook (#3581)
Browse files Browse the repository at this point in the history
* feat: add cameraNavigation hook

* fix: restore stories and fix styling cleanup issue

* chore: bump react components version to 0.10.0

* chore: import string
  • Loading branch information
christjt authored Aug 15, 2023
1 parent 6b259d9 commit 04ac5aa
Show file tree
Hide file tree
Showing 20 changed files with 159 additions and 198 deletions.
1 change: 1 addition & 0 deletions react-components/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module.exports = {
rules: {
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/consistent-type-definitions': ['error', 'type'],
'@typescript-eslint/no-misused-promises': 'off',
eqeqeq: ['error', 'always']
},
settings: {
Expand Down
2 changes: 1 addition & 1 deletion react-components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cognite/reveal-react-components",
"version": "0.9.0",
"version": "0.10.0",
"exports": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
TreeIndexNodeCollection,
NodeIdNodeCollection,
DefaultNodeAppearance,
type NodeCollection
type NodeCollection,
type Cognite3DViewer
} from '@cognite/reveal';
import { useReveal } from '../RevealContainer/RevealContext';
import { Matrix4 } from 'three';
Expand Down Expand Up @@ -61,16 +62,16 @@ export function CadModelContainer({
}, [modelId, revisionId, geometryFilter]);

useEffect(() => {
if (model === undefined || transform === undefined) return;
if (!modelExists(model, viewer) || transform === undefined) return;
model.setModelTransformation(transform);
}, [transform, model]);

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

return () => {
if (model === undefined) return;
if (!modelExists(model, viewer)) return;
void stylingCollections.then((nodeCollections) => {
nodeCollections.forEach((nodeCollection) => {
model.unassignStyledNodeCollection(nodeCollection);
Expand All @@ -80,12 +81,13 @@ export function CadModelContainer({
}, [styleGroups, model]);

useEffect(() => {
if (model === undefined) return;
if (!modelExists(model, viewer)) return;
model.setDefaultNodeAppearance(defaultStyle);
return () => {
if (model !== undefined) {
model.setDefaultNodeAppearance(DefaultNodeAppearance.Default);
if (!modelExists(model, viewer)) {
return;
}
model.setDefaultNodeAppearance(DefaultNodeAppearance.Default);
};
}, [defaultStyle, model]);

Expand Down Expand Up @@ -123,7 +125,7 @@ export function CadModelContainer({
}

function removeModel(): void {
if (model === undefined || !viewer.models.includes(model)) return;
if (!modelExists(model, viewer)) return;

if (cachedViewerRef !== undefined && !cachedViewerRef.isRevealContainerMountedRef.current)
return;
Expand Down Expand Up @@ -153,3 +155,10 @@ async function applyStyling(
}
return collections;
}

function modelExists(
model: CogniteCadModel | undefined,
viewer: Cognite3DViewer
): model is CogniteCadModel {
return model !== undefined && viewer.models.includes(model);
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export function PointCloudContainer({
}

function cleanStyling(): void {
if (model === undefined) return;
if (model === undefined || !viewer.models.includes(model)) return;

model.setDefaultPointCloudAppearance(DefaultPointCloudAppearance);
model.removeAllStyledObjectCollections();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/*!
* Copyright 2023 Cognite AS
*/
import { useRef, type ReactElement, useContext, useState, useEffect } from 'react';
import { useRef, type ReactElement, useState, useEffect } from 'react';
import { type Cognite3DViewer, type PointerEventData } from '@cognite/reveal';
import { ModelsLoadingStateContext } from './ModelsLoadingContext';
import { CadModelContainer, type CadModelStyling } from '../CadModelContainer/CadModelContainer';
import {
PointCloudContainer,
Expand All @@ -27,11 +26,11 @@ export const Reveal3DResources = ({
resources,
defaultResourceStyling,
instanceStyling,
onNodeClick
onNodeClick,
onResourcesAdded
}: Reveal3DResourcesProps): ReactElement => {
const [reveal3DModels, setReveal3DModels] = useState<TypedReveal3DModel[]>([]);

const { setModelsAdded } = useContext(ModelsLoadingStateContext);
const viewer = useReveal();
const fdmSdk = useFdmSdk();
const client = useSDK();
Expand Down Expand Up @@ -73,8 +72,8 @@ export const Reveal3DResources = ({
const onModelLoaded = (): void => {
numModelsLoaded.current += 1;

if (numModelsLoaded.current === resources.length) {
setModelsAdded(true);
if (numModelsLoaded.current === resources.length && onResourcesAdded !== undefined) {
onResourcesAdded();
}
};

Expand Down
1 change: 1 addition & 0 deletions react-components/src/components/Reveal3DResources/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ export type Reveal3DResourcesProps = {
defaultResourceStyling?: DefaultResourceStyling;
instanceStyling?: FdmAssetStylingGroup[];
onNodeClick?: (node: Promise<NodeDataResult | undefined>) => void;
onResourcesAdded?: () => void;
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { createPortal } from 'react-dom';
import { Cognite3DViewer, type Cognite3DViewerOptions } from '@cognite/reveal';
import { RevealContext } from './RevealContext';
import { type Color } from 'three';
import { ModelsLoadingStateContext } from '../Reveal3DResources/ModelsLoadingContext';
import { SDKProvider } from './SDKProvider';
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
import { useRevealKeepAlive } from '../RevealKeepAlive/RevealKeepAliveContext';
Expand Down Expand Up @@ -78,9 +77,7 @@ export function RevealContainer({
<>
<RevealContainerElementContext.Provider value={wrapperDomElement.current}>
<RevealContext.Provider value={viewer}>
<ModelsLoadingProvider>
{createPortal(children, viewerDomElement.current)}
</ModelsLoadingProvider>
{createPortal(children, viewerDomElement.current)}
</RevealContext.Provider>
</RevealContainerElementContext.Provider>
</>
Expand All @@ -104,13 +101,3 @@ export function RevealContainer({
return viewer;
}
}

function ModelsLoadingProvider({ children }: { children?: ReactNode }): ReactElement {
const [modelsLoading, setModelsLoading] = useState(false);
return (
<ModelsLoadingStateContext.Provider
value={{ modelsAdded: modelsLoading, setModelsAdded: setModelsLoading }}>
{children}
</ModelsLoadingStateContext.Provider>
);
}
14 changes: 4 additions & 10 deletions react-components/src/components/RevealToolbar/FitModelsButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,15 @@

import { type ReactElement, useCallback } from 'react';

import { Box3 } from 'three';

import { useReveal } from '../RevealContainer/RevealContext';
import { Button } from '@cognite/cogs.js';
import { useCameraNavigation } from '../../hooks/useCameraNavigation';

export const FitModelsButton = (): ReactElement => {
const viewer = useReveal();
const cameraNavigation = useCameraNavigation();

const updateCamera = useCallback(() => {
const box = new Box3();

viewer.models.forEach((model) => box.union(model.getModelBoundingBox()));

viewer.cameraManager.fitCameraToBoundingBox(box);
}, [viewer, ...viewer.models]);
cameraNavigation.fitCameraToAllModels();
}, []);

return (
<Button
Expand Down
68 changes: 68 additions & 0 deletions react-components/src/hooks/useCameraNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*!
* Copyright 2023 Cognite AS
*/

import { type CogniteCadModel } from '@cognite/reveal';
import { useReveal } from '../components/RevealContainer/RevealContext';
import { useFdmSdk } from '../components/RevealContainer/SDKProvider';
import { SYSTEM_3D_EDGE_SOURCE, type InModel3dEdgeProperties } from '../utilities/globalDataModels';

export type CameraNavigationActions = {
fitCameraToAllModels: () => void;
fitCameraToModelNode: (revisionId: number, nodeId: number) => Promise<void>;
fitCameraToInstance: (externalId: string, space: string) => Promise<void>;
};

export const useCameraNavigation = (): CameraNavigationActions => {
const viewer = useReveal();
const fdmSDK = useFdmSdk();

const fitCameraToAllModels = (): void => {
const models = viewer.models;
if (models.length === 0) {
return;
}
viewer.fitCameraToModels(models, undefined, true);
};

const fitCameraToModelNode = async (revisionId: number, nodeId: number): Promise<void> => {
const model = viewer.models.find((m) => m.revisionId === revisionId);
if (model === undefined) {
await Promise.reject(new Error(`Could not find model with revision ${revisionId}`));
return;
}
const nodeBoundingBox = await (model as CogniteCadModel).getBoundingBoxByNodeId(nodeId);
viewer.cameraManager.fitCameraToBoundingBox(nodeBoundingBox);
};

const fitCameraToInstance = async (externalId: string, space: string): Promise<void> => {
const fdmAssetMappingFilter = {
equals: {
property: ['edge', 'startNode'],
value: { externalId, space }
}
};

const assetEdges = await fdmSDK.filterInstances<InModel3dEdgeProperties>(
fdmAssetMappingFilter,
'edge',
SYSTEM_3D_EDGE_SOURCE
);

if (assetEdges.edges.length === 0) {
await Promise.reject(
new Error(`Could not find a connected model to instance ${externalId} in space ${space}`)
);
return;
}

const { revisionId, revisionNodeId } = assetEdges.edges[0].properties;
await fitCameraToModelNode(revisionId, revisionNodeId);
};

return {
fitCameraToAllModels,
fitCameraToInstance,
fitCameraToModelNode
};
};
3 changes: 2 additions & 1 deletion react-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ export { CadModelContainer } from './components/CadModelContainer/CadModelContai
export { Image360CollectionContainer } from './components/Image360CollectionContainer/Image360CollectionContainer';
export { Image360HistoricalDetails } from './components/Image360HistoricalDetails/Image360HistoricalDetails';
export { ViewerAnchor } from './components/ViewerAnchor/ViewerAnchor';
export { CameraController } from './components/CameraController/CameraController';
export { RevealToolbar } from './components/RevealToolbar/RevealToolbar';
export { RevealKeepAlive } from './components/RevealKeepAlive/RevealKeepAlive';

// Hooks
export { useReveal } from './components/RevealContainer/RevealContext';
export { use3DModelName } from './hooks/use3DModelName';
export { useFdmAssetMappings } from './hooks/useFdmAssetMappings';
export { useCameraNavigation } from './hooks/useCameraNavigation';

// Higher order components
export { withSuppressRevealEvents } from './higher-order-components/withSuppressRevealEvents';
Expand All @@ -44,4 +44,5 @@ export type {
AddReveal3DModelOptions,
NodeDataResult
} from './components/Reveal3DResources/types';
export type { CameraNavigationActions } from './hooks/useCameraNavigation';
export type { Source } from './utilities/FdmSDK';
10 changes: 2 additions & 8 deletions react-components/stories/CadModelContainer.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
* Copyright 2023 Cognite AS
*/
import type { Meta, StoryObj } from '@storybook/react';
import { CadModelContainer, CameraController, RevealContainer } from '../src';
import { Color, Matrix4, Vector3 } from 'three';
import { CadModelContainer, RevealContainer } from '../src';
import { Color, Matrix4 } from 'three';
import { createSdkByUrlToken } from './utilities/createSdkByUrlToken';
import { NumericRange } from '@cognite/reveal';

Expand Down Expand Up @@ -84,12 +84,6 @@ export const Main: Story = {
<RevealContainer sdk={sdk} color={new Color(0x4a4a4a)}>
<CadModelContainer addModelOptions={addModelOptions} styling={styling} />
<CadModelContainer addModelOptions={addModelOptions} transform={transform} />
<CameraController
initialFitCamera={{
to: 'cameraState',
state: { position: new Vector3(10, 20, 15), target: new Vector3(10, 0, -10) }
}}
/>
</RevealContainer>
)
};
Loading

0 comments on commit 04ac5aa

Please sign in to comment.