From 4b3d0de61cb0a5438c39598b6c35fef9d3373680 Mon Sep 17 00:00:00 2001 From: Christopher Tannum Date: Thu, 29 Jun 2023 17:18:02 +0200 Subject: [PATCH 01/22] feat: add container for CAD model --- .../CadModelContainer.tsx} | 2 +- .../PointCloudContainer.tsx | 49 +++++++++++++++++++ react-components/src/components/index.ts | 3 +- ...ries.tsx => CadModelContainer.stories.tsx} | 12 ++--- .../stories/PointCloudContainer.stories.tsx | 39 +++++++++++++++ 5 files changed, 97 insertions(+), 8 deletions(-) rename react-components/src/components/{CogniteCadModelContainer/CogniteCadModelContainer.tsx => CadModelContainer/CadModelContainer.tsx} (96%) create mode 100644 react-components/src/components/PointCloudContainer/PointCloudContainer.tsx rename react-components/stories/{CogniteCadModelContainer.stories.tsx => CadModelContainer.stories.tsx} (70%) create mode 100644 react-components/stories/PointCloudContainer.stories.tsx diff --git a/react-components/src/components/CogniteCadModelContainer/CogniteCadModelContainer.tsx b/react-components/src/components/CadModelContainer/CadModelContainer.tsx similarity index 96% rename from react-components/src/components/CogniteCadModelContainer/CogniteCadModelContainer.tsx rename to react-components/src/components/CadModelContainer/CadModelContainer.tsx index 8f3a5345378..4d7f55f86cb 100644 --- a/react-components/src/components/CogniteCadModelContainer/CogniteCadModelContainer.tsx +++ b/react-components/src/components/CadModelContainer/CadModelContainer.tsx @@ -11,7 +11,7 @@ type Cognite3dModelProps = { transform?: Matrix4; }; -export default function CogniteCadModelContainer({ +export default function CadModelContainer({ addModelOptions, transform }: Cognite3dModelProps): ReactElement { diff --git a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx new file mode 100644 index 00000000000..251f40c6243 --- /dev/null +++ b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx @@ -0,0 +1,49 @@ +/*! + * Copyright 2023 Cognite AS + */ + +import { type CognitePointCloudModel, type AddModelOptions } from '@cognite/reveal'; +import { useEffect, useRef, type ReactElement } from 'react'; +import { type Matrix4 } from 'three'; +import { useReveal } from '../RevealContainer/RevealContext'; + +type Cognite3dModelProps = { + addModelOptions: AddModelOptions; + transform?: Matrix4; +}; + +export default function PointCloudContainer({ + addModelOptions, + transform +}: Cognite3dModelProps): ReactElement { + const modelRef = useRef(); + const viewer = useReveal(); + const { modelId, revisionId } = addModelOptions; + + useEffect(() => { + addModel(modelId, revisionId, transform).catch(console.error); + return removeModel; + }, [addModelOptions]); + + useEffect(() => { + if (modelRef.current === undefined || transform === undefined) return; + modelRef.current.setModelTransformation(transform); + }, [transform]); + + return <>; + + async function addModel(modelId: number, revisionId: number, transform?: Matrix4): Promise { + const pointCloudModel = await viewer.addPointCloudModel({ modelId, revisionId }); + if (transform !== undefined) { + pointCloudModel.setModelTransformation(transform); + } + console.log(pointCloudModel.getModelBoundingBox()); + modelRef.current = pointCloudModel; + } + + function removeModel(): void { + if (modelRef.current === undefined || !viewer.models.includes(modelRef.current)) return; + viewer.removeModel(modelRef.current); + modelRef.current = undefined; + } +} diff --git a/react-components/src/components/index.ts b/react-components/src/components/index.ts index eb0f43bf4b0..ec64eb4c150 100644 --- a/react-components/src/components/index.ts +++ b/react-components/src/components/index.ts @@ -3,5 +3,6 @@ */ import '@cognite/cogs.js/dist/cogs.css'; export { default as RevealContainer } from './RevealContainer/RevealContainer'; -export { default as CogniteCadModelContainer } from './CogniteCadModelContainer/CogniteCadModelContainer'; +export { default as PointCloudContainer } from './PointCloudContainer/PointCloudContainer'; +export { default as CadModelContainer } from './CadModelContainer/CadModelContainer'; export { Image360HistoricalDetails } from './Image360HistoricalDetails/Image360HistoricalDetails'; diff --git a/react-components/stories/CogniteCadModelContainer.stories.tsx b/react-components/stories/CadModelContainer.stories.tsx similarity index 70% rename from react-components/stories/CogniteCadModelContainer.stories.tsx rename to react-components/stories/CadModelContainer.stories.tsx index 5aabb207368..1bd0e1ea1c1 100644 --- a/react-components/stories/CogniteCadModelContainer.stories.tsx +++ b/react-components/stories/CadModelContainer.stories.tsx @@ -2,15 +2,15 @@ * Copyright 2023 Cognite AS */ import type { Meta, StoryObj } from '@storybook/react'; -import { CogniteCadModelContainer, RevealContainer } from '../src'; +import { CadModelContainer, RevealContainer } from '../src'; import { CogniteClient } from '@cognite/sdk'; import { Color, Matrix4 } from 'three'; const meta = { - title: 'Example/CogniteCadModelContainer', - component: CogniteCadModelContainer, + title: 'Example/CadModelContainer', + component: CadModelContainer, tags: ['autodocs'] -} satisfies Meta; +} satisfies Meta; export default meta; type Story = StoryObj; @@ -33,8 +33,8 @@ export const Main: Story = { }, render: ({ addModelOptions, transform }) => ( - - + + ) }; diff --git a/react-components/stories/PointCloudContainer.stories.tsx b/react-components/stories/PointCloudContainer.stories.tsx new file mode 100644 index 00000000000..cb5c4b12e70 --- /dev/null +++ b/react-components/stories/PointCloudContainer.stories.tsx @@ -0,0 +1,39 @@ +/*! + * Copyright 2023 Cognite AS + */ +import type { Meta, StoryObj } from '@storybook/react'; +import { PointCloudContainer, RevealContainer } from '../src'; +import { CogniteClient } from '@cognite/sdk'; +import { Color, Matrix4 } from 'three'; + +const meta = { + title: 'Example/PointCloudContainer', + component: PointCloudContainer, + tags: ['autodocs'] +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const token = new URLSearchParams(window.location.search).get('token') ?? ''; +const sdk = new CogniteClient({ + appId: 'reveal.example', + baseUrl: 'https://greenfield.cognitedata.com', + project: '3d-test', + getToken: async () => await Promise.resolve(token) +}); + +export const Main: Story = { + args: { + addModelOptions: { + modelId: 3865289545346058, + revisionId: 4160448151596909 + }, + transform: new Matrix4() + }, + render: ({ addModelOptions, transform }) => ( + + + + ) +}; From a669d7e99ebfe95944764364fc939b72b2669948 Mon Sep 17 00:00:00 2001 From: Christopher Tannum Date: Fri, 30 Jun 2023 12:49:07 +0200 Subject: [PATCH 02/22] feat: add 360 image collection container --- .../Image360CollectionContainer.tsx | 35 +++++++++++++++++++ react-components/src/components/index.ts | 1 + .../Image360CollectionContainer.stories.tsx | 35 +++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx create mode 100644 react-components/stories/Image360CollectionContainer.stories.tsx diff --git a/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx b/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx new file mode 100644 index 00000000000..9c1b8ae1842 --- /dev/null +++ b/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx @@ -0,0 +1,35 @@ +/*! + * Copyright 2023 Cognite AS + */ +import { type ReactElement, useEffect, useRef } from 'react'; +import { useReveal } from '../RevealContainer/RevealContext'; +import { type Image360Collection } from '@cognite/reveal'; + +type Image360CollectionContainerProps = { + siteId: string; +}; + +export function Image360CollectionContainer({ + siteId +}: Image360CollectionContainerProps): ReactElement { + const modelRef = useRef(); + const viewer = useReveal(); + + useEffect(() => { + addModel().catch(console.error); + return removeModel; + }, [siteId]); + + return <>; + + async function addModel(): Promise { + const image360Collection = await viewer.add360ImageSet('events', { site_id: siteId }); + modelRef.current = image360Collection; + } + + function removeModel(): void { + if (modelRef.current === undefined) return; + viewer.remove360ImageSet(modelRef.current); + modelRef.current = undefined; + } +} diff --git a/react-components/src/components/index.ts b/react-components/src/components/index.ts index ec64eb4c150..54c7ca24415 100644 --- a/react-components/src/components/index.ts +++ b/react-components/src/components/index.ts @@ -5,4 +5,5 @@ import '@cognite/cogs.js/dist/cogs.css'; export { default as RevealContainer } from './RevealContainer/RevealContainer'; export { default as PointCloudContainer } from './PointCloudContainer/PointCloudContainer'; export { default as CadModelContainer } from './CadModelContainer/CadModelContainer'; +export { Image360CollectionContainer } from './Image360CollectionContainer/Image360CollectionContainer'; export { Image360HistoricalDetails } from './Image360HistoricalDetails/Image360HistoricalDetails'; diff --git a/react-components/stories/Image360CollectionContainer.stories.tsx b/react-components/stories/Image360CollectionContainer.stories.tsx new file mode 100644 index 00000000000..c00836db787 --- /dev/null +++ b/react-components/stories/Image360CollectionContainer.stories.tsx @@ -0,0 +1,35 @@ +/*! + * Copyright 2023 Cognite AS + */ +import type { Meta, StoryObj } from '@storybook/react'; +import { Image360CollectionContainer, RevealContainer } from '../src'; +import { CogniteClient } from '@cognite/sdk'; +import { Color } from 'three'; + +const meta = { + title: 'Example/Image360CollectionContainer', + component: Image360CollectionContainer, + tags: ['autodocs'] +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const token = new URLSearchParams(window.location.search).get('token') ?? ''; +const sdk = new CogniteClient({ + appId: 'reveal.example', + baseUrl: 'https://greenfield.cognitedata.com', + project: '3d-test', + getToken: async () => await Promise.resolve(token) +}); + +export const Main: Story = { + args: { + siteId: 'Hibernia_RS2' + }, + render: ({ siteId }) => ( + + + + ) +}; From 4309d88c2c300f2519b3517d4ac458b4c38a9403 Mon Sep 17 00:00:00 2001 From: Christopher Tannum Date: Mon, 3 Jul 2023 13:45:22 +0200 Subject: [PATCH 03/22] fix: add 3d resource container --- react-components/.eslintrc.cjs | 5 +- .../CadModelContainer/CadModelContainer.tsx | 18 ++-- .../Image360CollectionContainer.tsx | 5 +- .../PointCloudContainer.tsx | 8 +- .../Reveal3DResources/ModelsLoadingContext.ts | 13 +++ .../Reveal3DResources/Reveal3DResources.tsx | 84 +++++++++++++++++++ .../RevealContainer/RevealContainer.tsx | 15 +++- react-components/src/components/index.ts | 1 + .../stories/Reveal3DResources.stories.tsx | 55 ++++++++++++ react-components/tsconfig.json | 2 +- 10 files changed, 192 insertions(+), 14 deletions(-) create mode 100644 react-components/src/components/Reveal3DResources/ModelsLoadingContext.ts create mode 100644 react-components/src/components/Reveal3DResources/Reveal3DResources.tsx create mode 100644 react-components/stories/Reveal3DResources.stories.tsx diff --git a/react-components/.eslintrc.cjs b/react-components/.eslintrc.cjs index 15deb934592..5d1d664f654 100644 --- a/react-components/.eslintrc.cjs +++ b/react-components/.eslintrc.cjs @@ -1,5 +1,3 @@ -const path = require('path'); - module.exports = { env: { browser: true, @@ -14,7 +12,8 @@ module.exports = { parserOptions: { ecmaVersion: 'latest', sourceType: 'module', - project: [path.join(__dirname, 'tsconfig.json')] + project: './tsconfig.json', + tsconfigRootDir: __dirname }, plugins: ['react', 'prettier', 'header'], rules: { diff --git a/react-components/src/components/CadModelContainer/CadModelContainer.tsx b/react-components/src/components/CadModelContainer/CadModelContainer.tsx index 4d7f55f86cb..564e0c50b1d 100644 --- a/react-components/src/components/CadModelContainer/CadModelContainer.tsx +++ b/react-components/src/components/CadModelContainer/CadModelContainer.tsx @@ -9,20 +9,22 @@ import { type Matrix4 } from 'three'; type Cognite3dModelProps = { addModelOptions: AddModelOptions; transform?: Matrix4; + onLoad?: () => void; }; export default function CadModelContainer({ addModelOptions, - transform + transform, + onLoad }: Cognite3dModelProps): ReactElement { const modelRef = useRef(); const viewer = useReveal(); - const { modelId, revisionId } = addModelOptions; + const { modelId, revisionId, geometryFilter } = addModelOptions; useEffect(() => { - addModel(modelId, revisionId, transform).catch(console.error); + addModel(modelId, revisionId, transform, onLoad).catch(console.error); return removeModel; - }, [addModelOptions]); + }, [modelId, revisionId, geometryFilter]); useEffect(() => { if (modelRef.current === undefined || transform === undefined) return; @@ -31,12 +33,18 @@ export default function CadModelContainer({ return <>; - async function addModel(modelId: number, revisionId: number, transform?: Matrix4): Promise { + async function addModel( + modelId: number, + revisionId: number, + transform?: Matrix4, + onLoad?: () => void + ): Promise { const cadModel = await viewer.addCadModel({ modelId, revisionId }); if (transform !== undefined) { cadModel.setModelTransformation(transform); } modelRef.current = cadModel; + onLoad?.(); } function removeModel(): void { diff --git a/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx b/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx index 9c1b8ae1842..5caffad1911 100644 --- a/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx +++ b/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx @@ -7,10 +7,12 @@ import { type Image360Collection } from '@cognite/reveal'; type Image360CollectionContainerProps = { siteId: string; + onLoad?: () => void; }; export function Image360CollectionContainer({ - siteId + siteId, + onLoad }: Image360CollectionContainerProps): ReactElement { const modelRef = useRef(); const viewer = useReveal(); @@ -25,6 +27,7 @@ export function Image360CollectionContainer({ async function addModel(): Promise { const image360Collection = await viewer.add360ImageSet('events', { site_id: siteId }); modelRef.current = image360Collection; + onLoad?.(); } function removeModel(): void { diff --git a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx index 251f40c6243..88b380994d1 100644 --- a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx +++ b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx @@ -10,11 +10,13 @@ import { useReveal } from '../RevealContainer/RevealContext'; type Cognite3dModelProps = { addModelOptions: AddModelOptions; transform?: Matrix4; + onLoad?: () => void; }; export default function PointCloudContainer({ addModelOptions, - transform + transform, + onLoad }: Cognite3dModelProps): ReactElement { const modelRef = useRef(); const viewer = useReveal(); @@ -23,7 +25,7 @@ export default function PointCloudContainer({ useEffect(() => { addModel(modelId, revisionId, transform).catch(console.error); return removeModel; - }, [addModelOptions]); + }, [modelId, revisionId]); useEffect(() => { if (modelRef.current === undefined || transform === undefined) return; @@ -37,8 +39,8 @@ export default function PointCloudContainer({ if (transform !== undefined) { pointCloudModel.setModelTransformation(transform); } - console.log(pointCloudModel.getModelBoundingBox()); modelRef.current = pointCloudModel; + onLoad?.(); } function removeModel(): void { diff --git a/react-components/src/components/Reveal3DResources/ModelsLoadingContext.ts b/react-components/src/components/Reveal3DResources/ModelsLoadingContext.ts new file mode 100644 index 00000000000..6ee0a25325d --- /dev/null +++ b/react-components/src/components/Reveal3DResources/ModelsLoadingContext.ts @@ -0,0 +1,13 @@ +/*! + * Copyright 2023 Cognite AS + */ +import { createContext } from 'react'; + +export type ModelsLoadingState = { + modelsAdded: boolean; + setModelsAdded: (value: boolean) => void; +}; +export const ModelsLoadingStateContext = createContext({ + modelsAdded: false, + setModelsAdded: () => {} +}); diff --git a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx new file mode 100644 index 00000000000..c4e13b2190b --- /dev/null +++ b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx @@ -0,0 +1,84 @@ +/*! + * Copyright 2023 Cognite AS + */ +import { useRef, type ReactElement, useContext } from 'react'; +import { type AddModelOptions } from '@cognite/reveal'; +import { type Matrix4 } from 'three'; +import { CadModelContainer, Image360CollectionContainer, PointCloudContainer } from '..'; +import { ModelsLoadingStateContext } from './ModelsLoadingContext'; + +export type AddImageCollection360Options = { + siteId: string; +}; + +export type AddResourceOptions = Reveal3DModel | AddImageCollection360Options; + +export type Reveal3DResourcesProps = { + resources: AddResourceOptions[]; +}; + +type Reveal3DModel = AddModelOptions & { type: 'cad' | 'pointcloud'; transform?: Matrix4 }; + +export const Reveal3DResources = ({ resources }: Reveal3DResourcesProps): ReactElement => { + const { setModelsAdded } = useContext(ModelsLoadingStateContext); + const numModelsLoaded = useRef(0); + const cadModelAddOptions = resources.filter( + (resource): resource is Reveal3DModel => + (resource as Reveal3DModel).type !== undefined && (resource as Reveal3DModel).type === 'cad' + ); + + const pointcloudAddOptions = resources.filter( + (resource): resource is Reveal3DModel => + (resource as Reveal3DModel).type !== undefined && + (resource as Reveal3DModel).type === 'pointcloud' + ); + + const image360CollectionAddOptions = resources.filter( + (resource): resource is AddImageCollection360Options => + (resource as AddImageCollection360Options).siteId !== undefined + ); + + const totalNumberOfModels = + cadModelAddOptions.length + pointcloudAddOptions.length + image360CollectionAddOptions.length; + + const onModelLoaded = (): void => { + numModelsLoaded.current += 1; + if (numModelsLoaded.current === totalNumberOfModels) { + setModelsAdded(true); + } + }; + + return ( + <> + {cadModelAddOptions.map((addModelOption) => { + return ( + + ); + })} + {pointcloudAddOptions.map((addModelOption) => { + return ( + + ); + })} + {image360CollectionAddOptions.map((addModelOption) => { + return ( + + ); + })} + + ); +}; diff --git a/react-components/src/components/RevealContainer/RevealContainer.tsx b/react-components/src/components/RevealContainer/RevealContainer.tsx index a5e1d9fda19..c8a653b96df 100644 --- a/react-components/src/components/RevealContainer/RevealContainer.tsx +++ b/react-components/src/components/RevealContainer/RevealContainer.tsx @@ -6,6 +6,7 @@ import { useEffect, useRef, type ReactNode, useState, type ReactElement } from ' import { Cognite3DViewer } from '@cognite/reveal'; import { RevealContext } from './RevealContext'; import { type Color } from 'three'; +import { ModelsLoadingStateContext } from '../Reveal3DResources/ModelsLoadingContext'; type RevealContainerProps = { color?: Color; @@ -36,7 +37,9 @@ export default function RevealContainer({ if (viewer === undefined) return <>; return ( <> - {children} + + {children} + ); } @@ -57,3 +60,13 @@ export default function RevealContainer({ setViewer(undefined); } } + +function ModelsLoadingProvider({ children }: { children?: ReactNode }): ReactElement { + const [modelsLoading, setModelsLoading] = useState(false); + return ( + + {children} + + ); +} diff --git a/react-components/src/components/index.ts b/react-components/src/components/index.ts index 54c7ca24415..75613c04608 100644 --- a/react-components/src/components/index.ts +++ b/react-components/src/components/index.ts @@ -7,3 +7,4 @@ export { default as PointCloudContainer } from './PointCloudContainer/PointCloud export { default as CadModelContainer } from './CadModelContainer/CadModelContainer'; export { Image360CollectionContainer } from './Image360CollectionContainer/Image360CollectionContainer'; export { Image360HistoricalDetails } from './Image360HistoricalDetails/Image360HistoricalDetails'; +export { Reveal3DResources } from './Reveal3DResources/Reveal3DResources'; diff --git a/react-components/stories/Reveal3DResources.stories.tsx b/react-components/stories/Reveal3DResources.stories.tsx new file mode 100644 index 00000000000..d9aea50b1b9 --- /dev/null +++ b/react-components/stories/Reveal3DResources.stories.tsx @@ -0,0 +1,55 @@ +/*! + * Copyright 2023 Cognite AS + */ +import type { Meta, StoryObj } from '@storybook/react'; +import { Reveal3DResources, RevealContainer } from '../src'; +import { CogniteClient } from '@cognite/sdk'; +import { Color, Matrix4 } from 'three'; + +const meta = { + title: 'Example/Reveal3DResources', + component: Reveal3DResources, + tags: ['autodocs'] +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const token = new URLSearchParams(window.location.search).get('token') ?? ''; +const sdk = new CogniteClient({ + appId: 'reveal.example', + baseUrl: 'https://greenfield.cognitedata.com', + project: '3d-test', + getToken: async () => await Promise.resolve(token) +}); + +export const Main: Story = { + args: { + resources: [ + { + type: 'cad', + modelId: 1791160622840317, + revisionId: 498427137020189 + }, + { + type: 'cad', + modelId: 1791160622840317, + revisionId: 502149125550840, + transform: new Matrix4().makeTranslation(0, 10, 0) + }, + { + siteId: 'c_RC_2' + }, + { + type: 'pointcloud', + modelId: 3865289545346058, + revisionId: 4160448151596909 + } + ] + }, + render: ({ resources }) => ( + + + + ) +}; diff --git a/react-components/tsconfig.json b/react-components/tsconfig.json index 8cb3e155679..aaf3ce337cc 100644 --- a/react-components/tsconfig.json +++ b/react-components/tsconfig.json @@ -14,5 +14,5 @@ "moduleResolution": "bundler", "allowSyntheticDefaultImports": true }, - "include": ["src/*", "stories/*"] + "include": ["src/**/*", "stories/*"] } From a07843cc30212ab0bb245a232dc8385520856ed9 Mon Sep 17 00:00:00 2001 From: Christopher Tannum Date: Mon, 3 Jul 2023 14:24:06 +0200 Subject: [PATCH 04/22] feat: add CameraController component --- .../CameraController/CameraController.tsx | 36 +++++++++++++++++++ react-components/src/components/index.ts | 1 + .../stories/CadModelContainer.stories.tsx | 2 +- .../Image360CollectionContainer.stories.tsx | 2 +- .../stories/PointCloudContainer.stories.tsx | 2 +- .../stories/Reveal3DResources.stories.tsx | 6 ++++ 6 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 react-components/src/components/CameraController/CameraController.tsx diff --git a/react-components/src/components/CameraController/CameraController.tsx b/react-components/src/components/CameraController/CameraController.tsx new file mode 100644 index 00000000000..0b261341b06 --- /dev/null +++ b/react-components/src/components/CameraController/CameraController.tsx @@ -0,0 +1,36 @@ +/*! + * Copyright 2023 Cognite AS + */ +import { type ReactElement, useEffect, useContext, useRef } from 'react'; +import { useReveal } from '../RevealContainer/RevealContext'; +import { ModelsLoadingStateContext } from '../Reveal3DResources/ModelsLoadingContext'; +import { type CameraState } from '@cognite/reveal'; + +export type CameraControllerProps = { + initialFitCamera?: { to: 'allModels' } | { to: 'cameraState'; state: CameraState }; +}; + +export function CameraController({ initialFitCamera }: CameraControllerProps): ReactElement { + const initialCameraSet = useRef(false); + const viewer = useReveal(); + const { modelsAdded } = useContext(ModelsLoadingStateContext); + + useEffect(() => { + if (initialCameraSet.current) return; + if (initialFitCamera === undefined) { + initialCameraSet.current = true; + return; + } + if (initialFitCamera.to === 'cameraState') { + viewer.cameraManager.setCameraState(initialFitCamera.state); + initialCameraSet.current = true; + return; + } + if (initialFitCamera.to === 'allModels' && modelsAdded) { + viewer.fitCameraToModels(viewer.models, 0, true); + initialCameraSet.current = true; + } + }, [modelsAdded]); + + return <>; +} diff --git a/react-components/src/components/index.ts b/react-components/src/components/index.ts index 75613c04608..3d15c1f117c 100644 --- a/react-components/src/components/index.ts +++ b/react-components/src/components/index.ts @@ -8,3 +8,4 @@ export { default as CadModelContainer } from './CadModelContainer/CadModelContai export { Image360CollectionContainer } from './Image360CollectionContainer/Image360CollectionContainer'; export { Image360HistoricalDetails } from './Image360HistoricalDetails/Image360HistoricalDetails'; export { Reveal3DResources } from './Reveal3DResources/Reveal3DResources'; +export { CameraController } from './CameraController/CameraController'; diff --git a/react-components/stories/CadModelContainer.stories.tsx b/react-components/stories/CadModelContainer.stories.tsx index 1bd0e1ea1c1..527f938f7af 100644 --- a/react-components/stories/CadModelContainer.stories.tsx +++ b/react-components/stories/CadModelContainer.stories.tsx @@ -7,7 +7,7 @@ import { CogniteClient } from '@cognite/sdk'; import { Color, Matrix4 } from 'three'; const meta = { - title: 'Example/CadModelContainer', + title: 'Example/PrimitiveWrappers/CadModelContainer', component: CadModelContainer, tags: ['autodocs'] } satisfies Meta; diff --git a/react-components/stories/Image360CollectionContainer.stories.tsx b/react-components/stories/Image360CollectionContainer.stories.tsx index c00836db787..819a4320ec1 100644 --- a/react-components/stories/Image360CollectionContainer.stories.tsx +++ b/react-components/stories/Image360CollectionContainer.stories.tsx @@ -7,7 +7,7 @@ import { CogniteClient } from '@cognite/sdk'; import { Color } from 'three'; const meta = { - title: 'Example/Image360CollectionContainer', + title: 'Example/PrimitiveWrappers/Image360CollectionContainer', component: Image360CollectionContainer, tags: ['autodocs'] } satisfies Meta; diff --git a/react-components/stories/PointCloudContainer.stories.tsx b/react-components/stories/PointCloudContainer.stories.tsx index cb5c4b12e70..c1d2950b149 100644 --- a/react-components/stories/PointCloudContainer.stories.tsx +++ b/react-components/stories/PointCloudContainer.stories.tsx @@ -7,7 +7,7 @@ import { CogniteClient } from '@cognite/sdk'; import { Color, Matrix4 } from 'three'; const meta = { - title: 'Example/PointCloudContainer', + title: 'Example/PrimitiveWrappers/PointCloudContainer', component: PointCloudContainer, tags: ['autodocs'] } satisfies Meta; diff --git a/react-components/stories/Reveal3DResources.stories.tsx b/react-components/stories/Reveal3DResources.stories.tsx index d9aea50b1b9..d9f8e78942b 100644 --- a/react-components/stories/Reveal3DResources.stories.tsx +++ b/react-components/stories/Reveal3DResources.stories.tsx @@ -5,6 +5,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { Reveal3DResources, RevealContainer } from '../src'; import { CogniteClient } from '@cognite/sdk'; import { Color, Matrix4 } from 'three'; +import { CameraController } from '../src/'; const meta = { title: 'Example/Reveal3DResources', @@ -50,6 +51,11 @@ export const Main: Story = { render: ({ resources }) => ( + ) }; From cbcec3879325caf1e38bf6d30cccbeddc713dea7 Mon Sep 17 00:00:00 2001 From: Christopher Tannum Date: Mon, 3 Jul 2023 15:55:08 +0200 Subject: [PATCH 05/22] fix: add missing import --- .../Reveal3DResources/Reveal3DResources.tsx | 4 +++- react-components/src/components/index.ts | 11 ----------- react-components/src/index.ts | 12 +++++++++++- react-components/webpack.config.js | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) delete mode 100644 react-components/src/components/index.ts diff --git a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx index c4e13b2190b..d1316f433f1 100644 --- a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx +++ b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx @@ -4,8 +4,10 @@ import { useRef, type ReactElement, useContext } from 'react'; import { type AddModelOptions } from '@cognite/reveal'; import { type Matrix4 } from 'three'; -import { CadModelContainer, Image360CollectionContainer, PointCloudContainer } from '..'; import { ModelsLoadingStateContext } from './ModelsLoadingContext'; +import CadModelContainer from '../CadModelContainer/CadModelContainer'; +import PointCloudContainer from '../PointCloudContainer/PointCloudContainer'; +import { Image360CollectionContainer } from '../Image360CollectionContainer/Image360CollectionContainer'; export type AddImageCollection360Options = { siteId: string; diff --git a/react-components/src/components/index.ts b/react-components/src/components/index.ts deleted file mode 100644 index 3d15c1f117c..00000000000 --- a/react-components/src/components/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*! - * Copyright 2023 Cognite AS - */ -import '@cognite/cogs.js/dist/cogs.css'; -export { default as RevealContainer } from './RevealContainer/RevealContainer'; -export { default as PointCloudContainer } from './PointCloudContainer/PointCloudContainer'; -export { default as CadModelContainer } from './CadModelContainer/CadModelContainer'; -export { Image360CollectionContainer } from './Image360CollectionContainer/Image360CollectionContainer'; -export { Image360HistoricalDetails } from './Image360HistoricalDetails/Image360HistoricalDetails'; -export { Reveal3DResources } from './Reveal3DResources/Reveal3DResources'; -export { CameraController } from './CameraController/CameraController'; diff --git a/react-components/src/index.ts b/react-components/src/index.ts index 17dbab7eb81..8537e56c0c0 100644 --- a/react-components/src/index.ts +++ b/react-components/src/index.ts @@ -1,4 +1,14 @@ /*! * Copyright 2023 Cognite AS */ -export * from './components'; +import '@cognite/cogs.js/dist/cogs.css'; +export { default as RevealContainer } from './components/RevealContainer/RevealContainer'; +export { default as PointCloudContainer } from './components/PointCloudContainer/PointCloudContainer'; +export { default as CadModelContainer } from './components/CadModelContainer/CadModelContainer'; +export { Image360CollectionContainer } from './components/Image360CollectionContainer/Image360CollectionContainer'; +export { Image360HistoricalDetails } from './components/Image360HistoricalDetails/Image360HistoricalDetails'; +export { + Reveal3DResources, + type AddResourceOptions +} from './components/Reveal3DResources/Reveal3DResources'; +export { CameraController } from './components/CameraController/CameraController'; diff --git a/react-components/webpack.config.js b/react-components/webpack.config.js index 57826127587..94ae0fa4841 100644 --- a/react-components/webpack.config.js +++ b/react-components/webpack.config.js @@ -14,7 +14,7 @@ export default (_, argv) => { output: { path: path.resolve(__dirname, 'dist'), filename: 'index.js', - clean: true, + clean: false, libraryTarget: 'module' }, module: { From 71286363f46ff8b5e6bc0ce17ac875c75a46fcdd Mon Sep 17 00:00:00 2001 From: Christopher Tannum Date: Mon, 3 Jul 2023 17:21:44 +0200 Subject: [PATCH 06/22] feat: infer model type using Reveal's determineModelType --- .../Reveal3DResources/Reveal3DResources.tsx | 105 ++++++++++-------- .../src/components/Reveal3DResources/types.ts | 15 +++ react-components/src/index.ts | 7 +- .../stories/Reveal3DResources.stories.tsx | 3 - 4 files changed, 82 insertions(+), 48 deletions(-) create mode 100644 react-components/src/components/Reveal3DResources/types.ts diff --git a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx index d1316f433f1..86499c44fbc 100644 --- a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx +++ b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx @@ -1,77 +1,72 @@ /*! * Copyright 2023 Cognite AS */ -import { useRef, type ReactElement, useContext } from 'react'; -import { type AddModelOptions } from '@cognite/reveal'; -import { type Matrix4 } from 'three'; +import { useRef, type ReactElement, useContext, useState, useEffect } from 'react'; +import { type Cognite3DViewer } from '@cognite/reveal'; import { ModelsLoadingStateContext } from './ModelsLoadingContext'; import CadModelContainer from '../CadModelContainer/CadModelContainer'; import PointCloudContainer from '../PointCloudContainer/PointCloudContainer'; import { Image360CollectionContainer } from '../Image360CollectionContainer/Image360CollectionContainer'; - -export type AddImageCollection360Options = { - siteId: string; -}; - -export type AddResourceOptions = Reveal3DModel | AddImageCollection360Options; +import { useReveal } from '../RevealContainer/RevealContext'; +import { + type AddReveal3DModelOptions, + type AddImageCollection360Options, + type TypedReveal3DModel, + type AddResourceOptions +} from './types'; export type Reveal3DResourcesProps = { resources: AddResourceOptions[]; }; -type Reveal3DModel = AddModelOptions & { type: 'cad' | 'pointcloud'; transform?: Matrix4 }; - export const Reveal3DResources = ({ resources }: Reveal3DResourcesProps): ReactElement => { + const [reveal3DModels, setReveal3DModels] = useState([]); const { setModelsAdded } = useContext(ModelsLoadingStateContext); + const viewer = useReveal(); const numModelsLoaded = useRef(0); - const cadModelAddOptions = resources.filter( - (resource): resource is Reveal3DModel => - (resource as Reveal3DModel).type !== undefined && (resource as Reveal3DModel).type === 'cad' - ); - const pointcloudAddOptions = resources.filter( - (resource): resource is Reveal3DModel => - (resource as Reveal3DModel).type !== undefined && - (resource as Reveal3DModel).type === 'pointcloud' - ); + useEffect(() => { + getTypedModels(resources, viewer).then(setReveal3DModels).catch(console.error); + }, []); const image360CollectionAddOptions = resources.filter( (resource): resource is AddImageCollection360Options => (resource as AddImageCollection360Options).siteId !== undefined ); - const totalNumberOfModels = - cadModelAddOptions.length + pointcloudAddOptions.length + image360CollectionAddOptions.length; - const onModelLoaded = (): void => { numModelsLoaded.current += 1; - if (numModelsLoaded.current === totalNumberOfModels) { + if (numModelsLoaded.current === resources.length) { setModelsAdded(true); } }; return ( <> - {cadModelAddOptions.map((addModelOption) => { - return ( - - ); - })} - {pointcloudAddOptions.map((addModelOption) => { - return ( - - ); - })} + {reveal3DModels + .filter(({ type }) => type === 'cad') + .map((addModelOption) => { + return ( + + ); + })} + {reveal3DModels + .filter(({ type }) => type === 'pointcloud') + .map((addModelOption) => { + return ( + + ); + })} {image360CollectionAddOptions.map((addModelOption) => { return ( ); }; + +async function getTypedModels( + resources: AddResourceOptions[], + viewer: Cognite3DViewer +): Promise { + return await Promise.all( + resources + .filter( + (resource): resource is AddReveal3DModelOptions => + (resource as AddReveal3DModelOptions).modelId !== undefined && + (resource as AddReveal3DModelOptions).revisionId !== undefined + ) + .map(async (addModelOptions) => { + const type = await viewer.determineModelType( + addModelOptions.modelId, + addModelOptions.revisionId + ); + const typedModel: TypedReveal3DModel = { ...addModelOptions, type }; + return typedModel; + }) + ); +} diff --git a/react-components/src/components/Reveal3DResources/types.ts b/react-components/src/components/Reveal3DResources/types.ts new file mode 100644 index 00000000000..87d0319ffc1 --- /dev/null +++ b/react-components/src/components/Reveal3DResources/types.ts @@ -0,0 +1,15 @@ +/*! + * Copyright 2023 Cognite AS + */ + +import { type AddModelOptions, type SupportedModelTypes } from '@cognite/reveal'; +import { type Matrix4 } from 'three'; + +export type AddImageCollection360Options = { + siteId: string; +}; + +export type AddResourceOptions = AddReveal3DModelOptions | AddImageCollection360Options; + +export type AddReveal3DModelOptions = AddModelOptions & { transform?: Matrix4 }; +export type TypedReveal3DModel = AddReveal3DModelOptions & { type: SupportedModelTypes | '' }; diff --git a/react-components/src/index.ts b/react-components/src/index.ts index 8537e56c0c0..58e0c16a7bd 100644 --- a/react-components/src/index.ts +++ b/react-components/src/index.ts @@ -9,6 +9,11 @@ export { Image360CollectionContainer } from './components/Image360CollectionCont export { Image360HistoricalDetails } from './components/Image360HistoricalDetails/Image360HistoricalDetails'; export { Reveal3DResources, - type AddResourceOptions + type Reveal3DResourcesProps } from './components/Reveal3DResources/Reveal3DResources'; export { CameraController } from './components/CameraController/CameraController'; +export type { + AddImageCollection360Options, + AddResourceOptions, + AddReveal3DModelOptions +} from './components/Reveal3DResources/types'; diff --git a/react-components/stories/Reveal3DResources.stories.tsx b/react-components/stories/Reveal3DResources.stories.tsx index d9f8e78942b..9e993a475e4 100644 --- a/react-components/stories/Reveal3DResources.stories.tsx +++ b/react-components/stories/Reveal3DResources.stories.tsx @@ -28,12 +28,10 @@ export const Main: Story = { args: { resources: [ { - type: 'cad', modelId: 1791160622840317, revisionId: 498427137020189 }, { - type: 'cad', modelId: 1791160622840317, revisionId: 502149125550840, transform: new Matrix4().makeTranslation(0, 10, 0) @@ -42,7 +40,6 @@ export const Main: Story = { siteId: 'c_RC_2' }, { - type: 'pointcloud', modelId: 3865289545346058, revisionId: 4160448151596909 } From b3e395f4db3cae23eac56e7962c6bcb4260ee0a6 Mon Sep 17 00:00:00 2001 From: Savelii Novikov Date: Thu, 6 Jul 2023 11:09:04 +0200 Subject: [PATCH 07/22] Working styling for CAD --- .../CadModelContainer/CadModelContainer.tsx | 67 +++++++++++++++++-- .../Image360CollectionContainer.tsx | 8 ++- .../PointCloudContainer.tsx | 15 ++++- .../Reveal3DResources/Reveal3DResources.tsx | 11 ++- .../RevealContainer/RevealContainer.tsx | 11 ++- .../stories/CadModelContainer.stories.tsx | 36 +++++++++- 6 files changed, 132 insertions(+), 16 deletions(-) diff --git a/react-components/src/components/CadModelContainer/CadModelContainer.tsx b/react-components/src/components/CadModelContainer/CadModelContainer.tsx index 564e0c50b1d..858716afbdf 100644 --- a/react-components/src/components/CadModelContainer/CadModelContainer.tsx +++ b/react-components/src/components/CadModelContainer/CadModelContainer.tsx @@ -2,12 +2,30 @@ * Copyright 2023 Cognite AS */ import { type ReactElement, useEffect, useRef } from 'react'; -import { type AddModelOptions, type CogniteCadModel } from '@cognite/reveal'; +import { 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 { CogniteClient } from '@cognite/sdk'; -type Cognite3dModelProps = { +type NodeStylingGroup = { + nodeIds: number[]; + style: NodeAppearance; +}; + +type TreeIndexStylingGroup = { + treeIndices: number[]; + style: NodeAppearance; +}; + +type CadModelStyling = { + defaultStyle?: NodeAppearance; + groups?: (NodeStylingGroup | TreeIndexStylingGroup) [] +}; + +type CogniteCadModelProps = { addModelOptions: AddModelOptions; + styling?: CadModelStyling; transform?: Matrix4; onLoad?: () => void; }; @@ -15,14 +33,17 @@ type Cognite3dModelProps = { export default function CadModelContainer({ addModelOptions, transform, + styling, onLoad -}: Cognite3dModelProps): ReactElement { +}: CogniteCadModelProps): ReactElement { const modelRef = useRef(); const viewer = useReveal(); + const sdk = useSDK(); + const { modelId, revisionId, geometryFilter } = addModelOptions; useEffect(() => { - addModel(modelId, revisionId, transform, onLoad).catch(console.error); + addModel(modelId, revisionId, transform, onLoad).then((model) => applyStyling(sdk, model, styling)).catch(console.error); return removeModel; }, [modelId, revisionId, geometryFilter]); @@ -31,6 +52,19 @@ export default function CadModelContainer({ modelRef.current.setModelTransformation(transform); }, [transform]); + useEffect(() => { + const model = modelRef.current; + + if (model === undefined || styling === undefined) return; + + applyStyling(sdk, model, styling); + + return () => { + model.removeAllStyledNodeCollections(); + model.setDefaultNodeAppearance(DefaultNodeAppearance.Default); + }; + }, [styling, modelRef.current]); + return <>; async function addModel( @@ -38,13 +72,15 @@ export default function CadModelContainer({ revisionId: number, transform?: Matrix4, onLoad?: () => void - ): Promise { + ): Promise { const cadModel = await viewer.addCadModel({ modelId, revisionId }); if (transform !== undefined) { cadModel.setModelTransformation(transform); } modelRef.current = cadModel; onLoad?.(); + + return cadModel; } function removeModel(): void { @@ -53,3 +89,24 @@ export default function CadModelContainer({ modelRef.current = undefined; } } + +function applyStyling(sdk: CogniteClient, model: CogniteCadModel, styling?: CadModelStyling): 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) { + const nodes = new TreeIndexNodeCollection(group.treeIndices); + model.assignStyledNodeCollection(nodes, group.style); + } else if ('nodeIds' in group) { + const nodes = new NodeIdNodeCollection(sdk, model); + nodes.executeFilter(group.nodeIds); + model.assignStyledNodeCollection(nodes, group.style); + } + } + } +} diff --git a/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx b/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx index 5caffad1911..e3e00ce2c09 100644 --- a/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx +++ b/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx @@ -3,15 +3,21 @@ */ import { type ReactElement, useEffect, useRef } from 'react'; import { useReveal } from '../RevealContainer/RevealContext'; -import { type Image360Collection } from '@cognite/reveal'; +import { type Image360Collection, type Image360AnnotationAppearance } from '@cognite/reveal'; + +type Image360CollectionStyling = { + defaultStyle?: Image360AnnotationAppearance; +}; type Image360CollectionContainerProps = { siteId: string; + styling?: Image360CollectionStyling; onLoad?: () => void; }; export function Image360CollectionContainer({ siteId, + styling, onLoad }: Image360CollectionContainerProps): ReactElement { const modelRef = useRef(); diff --git a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx index 88b380994d1..4cfe804b6c2 100644 --- a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx +++ b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx @@ -2,13 +2,22 @@ * Copyright 2023 Cognite AS */ -import { type CognitePointCloudModel, type AddModelOptions } from '@cognite/reveal'; +import { type CognitePointCloudModel, type AddModelOptions, PointCloudAppearance } from '@cognite/reveal'; import { useEffect, useRef, type ReactElement } from 'react'; import { type Matrix4 } from 'three'; import { useReveal } from '../RevealContainer/RevealContext'; -type Cognite3dModelProps = { +export type AnnotationIdStylingGroup = { + annotationIds: number[]; + style: PointCloudAppearance; +}; + +export type CognitePointCloudModelProps = { addModelOptions: AddModelOptions; + styling?: { + defaultStyle?: PointCloudAppearance; + groups?: AnnotationIdStylingGroup[] + }; transform?: Matrix4; onLoad?: () => void; }; @@ -17,7 +26,7 @@ export default function PointCloudContainer({ addModelOptions, transform, onLoad -}: Cognite3dModelProps): ReactElement { +}: CognitePointCloudModelProps): ReactElement { const modelRef = useRef(); const viewer = useReveal(); const { modelId, revisionId } = addModelOptions; diff --git a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx index 86499c44fbc..a0372907093 100644 --- a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx +++ b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx @@ -2,7 +2,7 @@ * Copyright 2023 Cognite AS */ import { useRef, type ReactElement, useContext, useState, useEffect } from 'react'; -import { type Cognite3DViewer } from '@cognite/reveal'; +import { type Cognite3DViewer, AssetNodeCollection, type NodeAppearance, PointCloudAppearance } from '@cognite/reveal'; import { ModelsLoadingStateContext } from './ModelsLoadingContext'; import CadModelContainer from '../CadModelContainer/CadModelContainer'; import PointCloudContainer from '../PointCloudContainer/PointCloudContainer'; @@ -14,9 +14,16 @@ import { type TypedReveal3DModel, type AddResourceOptions } from './types'; +import { CogniteExternalId } from '@cognite/sdk'; + +export type AssetStylingGroup = { + assetIds: CogniteExternalId[]; + style: { cad?: NodeAppearance; pointcloud?: PointCloudAppearance }; +} export type Reveal3DResourcesProps = { resources: AddResourceOptions[]; + styling?: { groups?: AssetStylingGroup[], defaultStyle?: {cad?: NodeAppearance, pointcloud?: PointCloudAppearance } }; }; export const Reveal3DResources = ({ resources }: Reveal3DResourcesProps): ReactElement => { @@ -27,7 +34,7 @@ export const Reveal3DResources = ({ resources }: Reveal3DResourcesProps): ReactE useEffect(() => { getTypedModels(resources, viewer).then(setReveal3DModels).catch(console.error); - }, []); + }, [resources]); const image360CollectionAddOptions = resources.filter( (resource): resource is AddImageCollection360Options => diff --git a/react-components/src/components/RevealContainer/RevealContainer.tsx b/react-components/src/components/RevealContainer/RevealContainer.tsx index c8a653b96df..0b83d5abbad 100644 --- a/react-components/src/components/RevealContainer/RevealContainer.tsx +++ b/react-components/src/components/RevealContainer/RevealContainer.tsx @@ -7,6 +7,7 @@ import { Cognite3DViewer } from '@cognite/reveal'; import { RevealContext } from './RevealContext'; import { type Color } from 'three'; import { ModelsLoadingStateContext } from '../Reveal3DResources/ModelsLoadingContext'; +import { SDKProvider } from './SDKProvider'; type RevealContainerProps = { color?: Color; @@ -37,9 +38,13 @@ export default function RevealContainer({ if (viewer === undefined) return <>; return ( <> - - {children} - + + + + {children} + + + ); } diff --git a/react-components/stories/CadModelContainer.stories.tsx b/react-components/stories/CadModelContainer.stories.tsx index 527f938f7af..1e5d21de8d4 100644 --- a/react-components/stories/CadModelContainer.stories.tsx +++ b/react-components/stories/CadModelContainer.stories.tsx @@ -5,10 +5,40 @@ import type { Meta, StoryObj } from '@storybook/react'; import { CadModelContainer, RevealContainer } from '../src'; import { CogniteClient } from '@cognite/sdk'; import { Color, Matrix4 } from 'three'; +import { NumericRange } from '@cognite/reveal'; const meta = { title: 'Example/PrimitiveWrappers/CadModelContainer', component: CadModelContainer, + argTypes: { + styling: { + description: 'Styling of the first model', + options: ['FullRed', 'HalfGreen', 'SomeBlue', 'None'], + label: 'Styling of the first model', + mapping: { + FullRed: { + defaultStyle: { color: new Color('red') } + }, + HalfGreen: { + groups: [ + { + treeIndices: new NumericRange(0, 40), + style: { color: new Color('green') } + } + ] + }, + SomeBlue: { + groups: [ + { + nodeIds: [8757509474262596, 2712303310330098, 1903632131225149, 8923105504012177, 3709428615074138], + style: { color: new Color('blue') } + } + ] + }, + None: {} + } + } + }, tags: ['autodocs'] } satisfies Meta; @@ -29,11 +59,13 @@ export const Main: Story = { modelId: 1791160622840317, revisionId: 498427137020189 }, + styling: { + }, transform: new Matrix4().makeTranslation(0, 10, 0) }, - render: ({ addModelOptions, transform }) => ( + render: ({ addModelOptions, transform, styling}) => ( - + ) From 023c14fe646115209aac039ceb57672e64bf0e5a Mon Sep 17 00:00:00 2001 From: Savelii Novikov Date: Thu, 6 Jul 2023 15:31:04 +0200 Subject: [PATCH 08/22] Annotation testing capability --- examples/src/pages/Viewer.tsx | 31 ++++++++++++++++++- .../src/utils/PointCloudObjectStylingUI.ts | 24 ++++++++++++-- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/examples/src/pages/Viewer.tsx b/examples/src/pages/Viewer.tsx index bc8bcd8c7ad..0025855fe87 100644 --- a/examples/src/pages/Viewer.tsx +++ b/examples/src/pages/Viewer.tsx @@ -16,7 +16,8 @@ import { CameraControlsOptions, DefaultCameraManager, CogniteModel, - AnnotationIdPointCloudObjectCollection + AnnotationIdPointCloudObjectCollection, + CDF_TO_VIEWER_TRANSFORMATION } from '@cognite/reveal'; import { DebugCameraTool, Corner, AxisViewTool } from '@cognite/reveal/tools'; import * as reveal from '@cognite/reveal'; @@ -471,8 +472,36 @@ export function Viewer() { new THREE.SphereGeometry(0.1), new THREE.MeshBasicMaterial({ color: 'red' }) ); + sphere.position.copy(point); viewer.addObject3D(sphere); + + if (pointCloudObjectsUi.createAnnotationsOnClick) { + const cdfPosition = point.clone().applyMatrix4(model.getCdfToDefaultModelTransformation().invert()); + //model.mapPointFromCdfToModelCoordinates(cdfPosition); + + 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; diff --git a/examples/src/utils/PointCloudObjectStylingUI.ts b/examples/src/utils/PointCloudObjectStylingUI.ts index 0bea2b7ad99..e80c551ebd7 100644 --- a/examples/src/utils/PointCloudObjectStylingUI.ts +++ b/examples/src/utils/PointCloudObjectStylingUI.ts @@ -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; @@ -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(); }, @@ -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) { From d21043c0e22eff9430adb91b7ca63cd98bf8480d Mon Sep 17 00:00:00 2001 From: Savelii Novikov Date: Thu, 6 Jul 2023 15:37:40 +0200 Subject: [PATCH 09/22] Working point cloud styling --- .../CadModelContainer/CadModelContainer.tsx | 26 ++++---- .../PointCloudContainer.tsx | 62 +++++++++++++++---- .../RevealContainer/SDKProvider.tsx | 20 ++++++ .../stories/CadModelContainer.stories.tsx | 11 +++- .../stories/PointCloudContainer.stories.tsx | 40 ++++++++++-- 5 files changed, 125 insertions(+), 34 deletions(-) create mode 100644 react-components/src/components/RevealContainer/SDKProvider.tsx diff --git a/react-components/src/components/CadModelContainer/CadModelContainer.tsx b/react-components/src/components/CadModelContainer/CadModelContainer.tsx index 858716afbdf..0dbc9c060b2 100644 --- a/react-components/src/components/CadModelContainer/CadModelContainer.tsx +++ b/react-components/src/components/CadModelContainer/CadModelContainer.tsx @@ -1,7 +1,7 @@ /*! * Copyright 2023 Cognite AS */ -import { type ReactElement, useEffect, useRef } from 'react'; +import { type ReactElement, useEffect, useRef, useState } from 'react'; import { NodeAppearance, type AddModelOptions, type CogniteCadModel, TreeIndexNodeCollection, NodeIdNodeCollection, DefaultNodeAppearance} from '@cognite/reveal'; import { useReveal } from '../RevealContainer/RevealContext'; import { type Matrix4 } from 'three'; @@ -30,31 +30,29 @@ type CogniteCadModelProps = { onLoad?: () => void; }; -export default function CadModelContainer({ +export function CadModelContainer({ addModelOptions, transform, styling, onLoad }: CogniteCadModelProps): ReactElement { - const modelRef = useRef(); + const [model, setModel] = useState(); const viewer = useReveal(); const sdk = useSDK(); const { modelId, revisionId, geometryFilter } = addModelOptions; useEffect(() => { - addModel(modelId, revisionId, transform, onLoad).then((model) => applyStyling(sdk, model, styling)).catch(console.error); + addModel(modelId, revisionId, transform, onLoad).catch(console.error); return removeModel; }, [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(() => { - const model = modelRef.current; - if (model === undefined || styling === undefined) return; applyStyling(sdk, model, styling); @@ -63,7 +61,7 @@ export default function CadModelContainer({ model.removeAllStyledNodeCollections(); model.setDefaultNodeAppearance(DefaultNodeAppearance.Default); }; - }, [styling, modelRef.current]); + }, [styling, model]); return <>; @@ -77,16 +75,16 @@ export default function CadModelContainer({ if (transform !== undefined) { cadModel.setModelTransformation(transform); } - modelRef.current = cadModel; + setModel(cadModel); onLoad?.(); 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); } } diff --git a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx index fdc0a17512b..48dce775d73 100644 --- a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx +++ b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx @@ -1,9 +1,9 @@ /*! * Copyright 2023 Cognite AS */ -import { type CognitePointCloudModel, type AddModelOptions, PointCloudAppearance } from '@cognite/reveal'; +import { type CognitePointCloudModel, type AddModelOptions, PointCloudAppearance, DefaultPointCloudAppearance, AnnotationIdPointCloudObjectCollection } from '@cognite/reveal'; -import { useEffect, useRef, type ReactElement } from 'react'; +import { useEffect, type ReactElement, useState } from 'react'; import { type Matrix4 } from 'three'; import { useReveal } from '../RevealContainer/RevealContext'; @@ -12,12 +12,14 @@ export type AnnotationIdStylingGroup = { style: PointCloudAppearance; }; +export type PointCloudModelStyling = { + defaultStyle?: PointCloudAppearance; + groups?: AnnotationIdStylingGroup[]; +}; + export type CognitePointCloudModelProps = { addModelOptions: AddModelOptions; - styling?: { - defaultStyle?: PointCloudAppearance; - groups?: AnnotationIdStylingGroup[] - }; + styling?: PointCloudModelStyling; transform?: Matrix4; onLoad?: () => void; }; @@ -28,7 +30,7 @@ export function PointCloudContainer({ transform, onLoad }: CognitePointCloudModelProps): ReactElement { - const modelRef = useRef(); + const [model, setModel] = useState(); const viewer = useReveal(); const { modelId, revisionId } = addModelOptions; @@ -38,25 +40,59 @@ export function PointCloudContainer({ }, [modelId, revisionId]); useEffect(() => { - if (modelRef.current === undefined || transform === undefined) return; - modelRef.current.setModelTransformation(transform); + if (model === undefined || transform === undefined) return; + model.setModelTransformation(transform); }, [transform]); + useEffect(() => { + if (model === undefined || styling === undefined) return; + + applyStyling(model, styling); + + return cleanStyling; + }, [styling, model]); + return <>; async function addModel(modelId: number, revisionId: number, transform?: Matrix4): Promise { const pointCloudModel = await viewer.addPointCloudModel({ modelId, revisionId }); + viewer.fitCameraToModel(pointCloudModel); + if (transform !== undefined) { pointCloudModel.setModelTransformation(transform); } - modelRef.current = pointCloudModel; + setModel(pointCloudModel); onLoad?.(); } 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); + } + + function cleanStyling(): void { + if (model === undefined) return; + + model.setDefaultPointCloudAppearance(DefaultPointCloudAppearance); + model.removeAllStyledObjectCollections(); + } +} + +function applyStyling(model: CognitePointCloudModel, styling: PointCloudModelStyling): void { + if (styling.defaultStyle !== undefined) { + model.setDefaultPointCloudAppearance(styling.defaultStyle); + } + + if (styling.groups !== undefined) { + for (const group of styling.groups) { + if (group.annotationIds !== undefined) { + const collection = new AnnotationIdPointCloudObjectCollection(group.annotationIds); + + model.assignStyledObjectCollection(collection, group.style); + } + } } } diff --git a/react-components/src/components/RevealContainer/SDKProvider.tsx b/react-components/src/components/RevealContainer/SDKProvider.tsx new file mode 100644 index 00000000000..c2bb38ce180 --- /dev/null +++ b/react-components/src/components/RevealContainer/SDKProvider.tsx @@ -0,0 +1,20 @@ +import React, { useContext, createContext} from 'react'; +import { CogniteClient } from '@cognite/sdk'; + +const SdkContext = createContext(null); +SdkContext.displayName = 'CogniteSdkProvider'; + +type Props = { sdk: CogniteClient; children: any }; +export function SDKProvider({ sdk, children }: Props) { + return {children}; +} + +export const useSDK = () => { + const sdk = useContext(SdkContext); + if (!sdk) { + throw new Error( + `SdkContext not found, add '' around your component/app` + ); + } + return sdk; +}; \ No newline at end of file diff --git a/react-components/stories/CadModelContainer.stories.tsx b/react-components/stories/CadModelContainer.stories.tsx index 890a5c91321..7e9a9e065a1 100644 --- a/react-components/stories/CadModelContainer.stories.tsx +++ b/react-components/stories/CadModelContainer.stories.tsx @@ -2,8 +2,8 @@ * Copyright 2023 Cognite AS */ import type { Meta, StoryObj } from '@storybook/react'; -import { CadModelContainer, RevealContainer } from '../src'; -import { Color, Matrix4 } from 'three'; +import { CadModelContainer, CameraController, RevealContainer } from '../src'; +import { Color, Matrix4, Vector3 } from 'three'; import { createSdkByUrlToken } from './utilities/createSdkByUrlToken'; import { NumericRange } from '@cognite/reveal'; @@ -54,6 +54,7 @@ export const Main: Story = { revisionId: 498427137020189 }, styling: { + defaultStyle: { color: new Color('red') } }, transform: new Matrix4().makeTranslation(0, 10, 0) }, @@ -61,6 +62,12 @@ export const Main: Story = { + ) }; diff --git a/react-components/stories/PointCloudContainer.stories.tsx b/react-components/stories/PointCloudContainer.stories.tsx index b316f323d2f..8228d34bfce 100644 --- a/react-components/stories/PointCloudContainer.stories.tsx +++ b/react-components/stories/PointCloudContainer.stories.tsx @@ -2,13 +2,36 @@ * Copyright 2023 Cognite AS */ import type { Meta, StoryObj } from '@storybook/react'; -import { PointCloudContainer, RevealContainer } from '../src'; -import { Color, Matrix4 } from 'three'; +import { CameraController, PointCloudContainer, RevealContainer } from '../src'; +import { Color, Matrix4, Vector3 } from 'three'; import { createSdkByUrlToken } from './utilities/createSdkByUrlToken'; const meta = { title: 'Example/PrimitiveWrappers/PointCloudContainer', component: PointCloudContainer, + argTypes: { + styling: { + options: ['FullRed', 'FullGreen', 'None', 'BlueAnnotations'], + label: 'Styling of the first model', + mapping: { + FullRed: { + defaultStyle: { color: new Color('red') } + }, + FullGreen: { + defaultStyle: { color: new Color('green') } + }, + BlueAnnotations: { + groups: [ + { + annotationIds: [7526447911236241, 4768908317649926, 560990282344486], + style: { color: new Color('blue') } + } + ] + }, + None: {} + } + } + }, tags: ['autodocs'] } satisfies Meta; @@ -23,11 +46,18 @@ export const Main: Story = { modelId: 3865289545346058, revisionId: 4160448151596909 }, - transform: new Matrix4() + styling: { + }, + transform: new Matrix4(), }, - render: ({ addModelOptions, transform }) => ( + render: ({ addModelOptions, transform, styling}) => ( - + + ) }; From a8c138ea6fd288edbefae8c50efa1bf186de3ba6 Mon Sep 17 00:00:00 2001 From: Savelii Novikov Date: Thu, 6 Jul 2023 16:06:43 +0200 Subject: [PATCH 10/22] Working default style for 3d resources --- .../CadModelContainer/CadModelContainer.tsx | 6 +- .../PointCloudContainer.tsx | 2 +- .../Reveal3DResources/Reveal3DResources.tsx | 66 +++++++++++++++---- .../stories/Reveal3DResources.stories.tsx | 16 ++++- 4 files changed, 70 insertions(+), 20 deletions(-) diff --git a/react-components/src/components/CadModelContainer/CadModelContainer.tsx b/react-components/src/components/CadModelContainer/CadModelContainer.tsx index 0dbc9c060b2..fa0a4aeffa2 100644 --- a/react-components/src/components/CadModelContainer/CadModelContainer.tsx +++ b/react-components/src/components/CadModelContainer/CadModelContainer.tsx @@ -8,17 +8,17 @@ import { type Matrix4 } from 'three'; import { useSDK } from '../RevealContainer/SDKProvider'; import { CogniteClient } from '@cognite/sdk'; -type NodeStylingGroup = { +export type NodeStylingGroup = { nodeIds: number[]; style: NodeAppearance; }; -type TreeIndexStylingGroup = { +export type TreeIndexStylingGroup = { treeIndices: number[]; style: NodeAppearance; }; -type CadModelStyling = { +export type CadModelStyling = { defaultStyle?: NodeAppearance; groups?: (NodeStylingGroup | TreeIndexStylingGroup) [] }; diff --git a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx index 48dce775d73..2c70ba42ab8 100644 --- a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx +++ b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx @@ -42,7 +42,7 @@ export function PointCloudContainer({ useEffect(() => { if (model === undefined || transform === undefined) return; model.setModelTransformation(transform); - }, [transform]); + }, [transform, model]); useEffect(() => { if (model === undefined || styling === undefined) return; diff --git a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx index 80743e703b2..e42b5f86724 100644 --- a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx +++ b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx @@ -4,8 +4,8 @@ import { useRef, type ReactElement, useContext, useState, useEffect } from 'react'; import { NodeAppearance, type Cognite3DViewer, PointCloudAppearance } from '@cognite/reveal'; import { ModelsLoadingStateContext } from './ModelsLoadingContext'; -import { CadModelContainer } from '../CadModelContainer/CadModelContainer'; -import { PointCloudContainer } from '../PointCloudContainer/PointCloudContainer'; +import { CadModelContainer, CadModelStyling } from '../CadModelContainer/CadModelContainer'; +import { PointCloudContainer, PointCloudModelStyling } from '../PointCloudContainer/PointCloudContainer'; import { Image360CollectionContainer } from '../Image360CollectionContainer/Image360CollectionContainer'; import { useReveal } from '../RevealContainer/RevealContext'; import { @@ -17,17 +17,24 @@ import { import { CogniteExternalId } from '@cognite/sdk'; export type AssetStylingGroup = { - assetIds: CogniteExternalId[]; + assetExternalIds: CogniteExternalId[]; style: { cad?: NodeAppearance; pointcloud?: PointCloudAppearance }; } +export type Reveal3DResourcesStyling = { + defaultStyle?: { cad?: NodeAppearance, pointcloud?: PointCloudAppearance } + groups?: AssetStylingGroup[], +}; + export type Reveal3DResourcesProps = { resources: AddResourceOptions[]; - styling?: { groups?: AssetStylingGroup[], defaultStyle?: {cad?: NodeAppearance, pointcloud?: PointCloudAppearance } }; + styling?: Reveal3DResourcesStyling; }; -export const Reveal3DResources = ({ resources }: Reveal3DResourcesProps): ReactElement => { +export const Reveal3DResources = ({ resources, styling }: Reveal3DResourcesProps): ReactElement => { const [reveal3DModels, setReveal3DModels] = useState([]); + const [reveal3DModelsStyling, setReveal3DModelsStyling] = useState<(PointCloudModelStyling | CadModelStyling)[]>([]); + const { setModelsAdded } = useContext(ModelsLoadingStateContext); const viewer = useReveal(); const numModelsLoaded = useRef(0); @@ -36,6 +43,35 @@ export const Reveal3DResources = ({ resources }: Reveal3DResourcesProps): ReactE getTypedModels(resources, viewer).then(setReveal3DModels).catch(console.error); }, [resources]); + useEffect(() => { + if (styling === undefined || reveal3DModels === undefined) return; + + const modelStylings = reveal3DModels.map((model) => { + let modelStyling: PointCloudModelStyling | CadModelStyling; + switch (model.type) { + case 'cad': + modelStyling = { + defaultStyle: styling.defaultStyle?.cad, + }; + break; + case 'pointcloud': + modelStyling = { + defaultStyle: styling.defaultStyle?.pointcloud, + }; + break; + default: + modelStyling = {}; + console.warn(`Unknown model type: ${model.type}`); + break; + } + + return modelStyling; + }); + + setReveal3DModelsStyling(modelStylings); + + }, [styling, reveal3DModels]); + const image360CollectionAddOptions = resources.filter( (resource): resource is AddImageCollection360Options => (resource as AddImageCollection360Options).siteId !== undefined @@ -51,25 +87,29 @@ export const Reveal3DResources = ({ resources }: Reveal3DResourcesProps): ReactE return ( <> {reveal3DModels + .map((modelData, index) => ({...modelData, styling: reveal3DModelsStyling[index] as CadModelStyling})) .filter(({ type }) => type === 'cad') - .map((addModelOption, index) => { + .map((modelData, index) => { return ( ); })} {reveal3DModels + .map((modelData, index) => ({...modelData, styling: reveal3DModelsStyling[index] as PointCloudModelStyling})) .filter(({ type }) => type === 'pointcloud') - .map((addModelOption, index) => { + .map((modelData, index) => { return ( ); diff --git a/react-components/stories/Reveal3DResources.stories.tsx b/react-components/stories/Reveal3DResources.stories.tsx index 33426b46088..cf1820c7748 100644 --- a/react-components/stories/Reveal3DResources.stories.tsx +++ b/react-components/stories/Reveal3DResources.stories.tsx @@ -37,9 +37,19 @@ export const Main: Story = { modelId: 3865289545346058, revisionId: 4160448151596909 } - ] + ], + styling: { + defaultStyle: { + cad: { + color: new Color('yellow') + }, + pointcloud: { + color: new Color('blue') + } + } + } }, - render: ({ resources }) => ( + render: ({ resources, styling}) => ( - + Date: Tue, 11 Jul 2023 10:33:30 +0200 Subject: [PATCH 11/22] Working styling based on externalIds --- examples/src/pages/Viewer.tsx | 4 +- .../CadModelContainer/CadModelContainer.tsx | 6 +- .../CameraController/CameraController.tsx | 16 ++- .../Image360CollectionContainer.tsx | 4 +- .../PointCloudContainer.tsx | 4 +- .../Reveal3DResources/Reveal3DResources.tsx | 65 +++++++++-- .../RevealContainer/SDKProvider.tsx | 24 ++++- .../src/hooks/useFdmAssetMappings.tsx | 64 +++++++++++ react-components/src/utilities/FdmSDK.ts | 73 +++++++++++++ .../stories/Reveal3DResources.stories.tsx | 102 +++++++++++++++++- 10 files changed, 332 insertions(+), 30 deletions(-) create mode 100644 react-components/src/hooks/useFdmAssetMappings.tsx create mode 100644 react-components/src/utilities/FdmSDK.ts diff --git a/examples/src/pages/Viewer.tsx b/examples/src/pages/Viewer.tsx index 0025855fe87..ca09506e9ea 100644 --- a/examples/src/pages/Viewer.tsx +++ b/examples/src/pages/Viewer.tsx @@ -16,8 +16,7 @@ import { CameraControlsOptions, DefaultCameraManager, CogniteModel, - AnnotationIdPointCloudObjectCollection, - CDF_TO_VIEWER_TRANSFORMATION + AnnotationIdPointCloudObjectCollection } from '@cognite/reveal'; import { DebugCameraTool, Corner, AxisViewTool } from '@cognite/reveal/tools'; import * as reveal from '@cognite/reveal'; @@ -478,7 +477,6 @@ export function Viewer() { if (pointCloudObjectsUi.createAnnotationsOnClick) { const cdfPosition = point.clone().applyMatrix4(model.getCdfToDefaultModelTransformation().invert()); - //model.mapPointFromCdfToModelCoordinates(cdfPosition); const annotation = await client.annotations.create([{ annotatedResourceId: model.modelId, diff --git a/react-components/src/components/CadModelContainer/CadModelContainer.tsx b/react-components/src/components/CadModelContainer/CadModelContainer.tsx index fa0a4aeffa2..6364456ae50 100644 --- a/react-components/src/components/CadModelContainer/CadModelContainer.tsx +++ b/react-components/src/components/CadModelContainer/CadModelContainer.tsx @@ -27,7 +27,7 @@ type CogniteCadModelProps = { addModelOptions: AddModelOptions; styling?: CadModelStyling; transform?: Matrix4; - onLoad?: () => void; + onLoad?: (model: CogniteCadModel) => void; }; export function CadModelContainer({ @@ -69,14 +69,14 @@ export function CadModelContainer({ modelId: number, revisionId: number, transform?: Matrix4, - onLoad?: () => void + onLoad?: (model: CogniteCadModel) => void ): Promise { const cadModel = await viewer.addCadModel({ modelId, revisionId }); if (transform !== undefined) { cadModel.setModelTransformation(transform); } setModel(cadModel); - onLoad?.(); + onLoad?.(cadModel); return cadModel; } diff --git a/react-components/src/components/CameraController/CameraController.tsx b/react-components/src/components/CameraController/CameraController.tsx index c06e0eea3c2..45b14a780a2 100644 --- a/react-components/src/components/CameraController/CameraController.tsx +++ b/react-components/src/components/CameraController/CameraController.tsx @@ -4,10 +4,11 @@ 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 = @@ -15,13 +16,22 @@ type FittingStrategy = | { 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 = 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') { @@ -34,7 +44,7 @@ export function CameraController({ initialFitCamera }: CameraControllerProps): R return; } if (fittingStrategy.to === 'allModels' && modelsAdded) { - viewer.fitCameraToModels(viewer.models, 0, true); + viewer.fitCameraToModels(undefined, undefined, true); initialCameraSet.current = true; } }, [modelsAdded]); diff --git a/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx b/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx index e44fba8d826..3377c686962 100644 --- a/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx +++ b/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx @@ -12,7 +12,7 @@ type Image360CollectionStyling = { type Image360CollectionContainerProps = { siteId: string; styling?: Image360CollectionStyling; - onLoad?: () => void; + onLoad?: (image360: Image360Collection) => void; }; export function Image360CollectionContainer({ @@ -33,7 +33,7 @@ export function Image360CollectionContainer({ async function add360Collection(): Promise { const image360Collection = await viewer.add360ImageSet('events', { site_id: siteId }); modelRef.current = image360Collection; - onLoad?.(); + onLoad?.(image360Collection); } function remove360Collection(): void { diff --git a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx index 2c70ba42ab8..10ac601d253 100644 --- a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx +++ b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx @@ -21,7 +21,7 @@ export type CognitePointCloudModelProps = { addModelOptions: AddModelOptions; styling?: PointCloudModelStyling; transform?: Matrix4; - onLoad?: () => void; + onLoad?: (model: CognitePointCloudModel) => void; }; export function PointCloudContainer({ @@ -63,7 +63,7 @@ export function PointCloudContainer({ pointCloudModel.setModelTransformation(transform); } setModel(pointCloudModel); - onLoad?.(); + onLoad?.(pointCloudModel); } function removeModel(): void { diff --git a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx index e42b5f86724..3113f3c703d 100644 --- a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx +++ b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx @@ -1,8 +1,8 @@ /*! * Copyright 2023 Cognite AS */ -import { useRef, type ReactElement, useContext, useState, useEffect } from 'react'; -import { NodeAppearance, type Cognite3DViewer, PointCloudAppearance } from '@cognite/reveal'; +import { useRef, type ReactElement, useContext, useState, useEffect, useMemo } from 'react'; +import { NodeAppearance, type Cognite3DViewer, PointCloudAppearance, CogniteCadModel, CognitePointCloudModel, Image360Collection } from '@cognite/reveal'; import { ModelsLoadingStateContext } from './ModelsLoadingContext'; import { CadModelContainer, CadModelStyling } from '../CadModelContainer/CadModelContainer'; import { PointCloudContainer, PointCloudModelStyling } from '../PointCloudContainer/PointCloudContainer'; @@ -15,15 +15,18 @@ import { type AddResourceOptions } from './types'; import { CogniteExternalId } from '@cognite/sdk'; +import { useSDK } from '../RevealContainer/SDKProvider'; +import { FdmAssetMappingsConfig, useFdmAssetMappings } from '../../hooks/useFdmAssetMappings'; -export type AssetStylingGroup = { - assetExternalIds: CogniteExternalId[]; + +export type FdmAssetStylingGroup = { + fdmAssetExternalIds: CogniteExternalId[]; style: { cad?: NodeAppearance; pointcloud?: PointCloudAppearance }; } export type Reveal3DResourcesStyling = { defaultStyle?: { cad?: NodeAppearance, pointcloud?: PointCloudAppearance } - groups?: AssetStylingGroup[], + groups?: FdmAssetStylingGroup[], }; export type Reveal3DResourcesProps = { @@ -39,14 +42,28 @@ export const Reveal3DResources = ({ resources, styling }: Reveal3DResourcesProps const viewer = useReveal(); const numModelsLoaded = useRef(0); + const stylingExternalIds = useMemo(() => styling?.groups?.flatMap((group) => group.fdmAssetExternalIds) ?? [], [styling]); + const fdmAssetMappingSource: FdmAssetMappingsConfig = { + source: { + space: 'fdm-3d-test-savelii', + version: '1', + type: 'view', + externalId: 'CDF_3D_Connection_Data', + }, + assetFdmSpace: 'bark-corporation' + }; + + const mappings = useFdmAssetMappings(stylingExternalIds, fdmAssetMappingSource); + useEffect(() => { getTypedModels(resources, viewer).then(setReveal3DModels).catch(console.error); - }, [resources]); - + }, [resources, viewer]); + + useEffect(() => { - if (styling === undefined || reveal3DModels === undefined) return; + if (styling === undefined || reveal3DModels === undefined) return; - const modelStylings = reveal3DModels.map((model) => { + const modelsStyling = reveal3DModels.map((model) => { let modelStyling: PointCloudModelStyling | CadModelStyling; switch (model.type) { case 'cad': @@ -68,9 +85,34 @@ export const Reveal3DResources = ({ resources, styling }: Reveal3DResourcesProps return modelStyling; }); - setReveal3DModelsStyling(modelStylings); + mappings.then((modelMappings) => { + const newModelsStyling = modelsStyling.map((modelStyling, index) => { + const model = reveal3DModels[index]; + if (model.type === 'cad') { + const modelNodeMappings = modelMappings.find((mapping) => mapping.modelId === model.modelId && mapping.revisionId === model.revisionId); + const nodeStyling = styling?.groups?.find((group) => + group.fdmAssetExternalIds.filter((externalId) => + modelNodeMappings?.mappings.some((modelNodeMapping) => + modelNodeMapping.externalId === externalId)) + .length > 0 + ); + + if (modelNodeMappings && styling?.groups) { + (modelStyling as CadModelStyling).groups = [...((modelStyling as CadModelStyling)?.groups ?? []), {style: nodeStyling?.style?.cad!, nodeIds: modelNodeMappings.mappings.map((mapping) => mapping.nodeId)}] + } - }, [styling, reveal3DModels]); + return modelStyling; + } else if (model.type === 'pointcloud') { + return modelStyling; + } + + return modelStyling; + }); + + setReveal3DModelsStyling(newModelsStyling); + }); + + }, [mappings, styling, reveal3DModels]); const image360CollectionAddOptions = resources.filter( (resource): resource is AddImageCollection360Options => @@ -79,6 +121,7 @@ export const Reveal3DResources = ({ resources, styling }: Reveal3DResourcesProps const onModelLoaded = (): void => { numModelsLoaded.current += 1; + if (numModelsLoaded.current === resources.length) { setModelsAdded(true); } diff --git a/react-components/src/components/RevealContainer/SDKProvider.tsx b/react-components/src/components/RevealContainer/SDKProvider.tsx index c2bb38ce180..f51cd582335 100644 --- a/react-components/src/components/RevealContainer/SDKProvider.tsx +++ b/react-components/src/components/RevealContainer/SDKProvider.tsx @@ -1,12 +1,22 @@ -import React, { useContext, createContext} from 'react'; +import React, { useContext, createContext, useMemo} from 'react'; import { CogniteClient } from '@cognite/sdk'; +import { FdmSDK } from '../../utilities/FdmSdk'; const SdkContext = createContext(null); +const FdmSdkContext = createContext(null); SdkContext.displayName = 'CogniteSdkProvider'; +FdmSdkContext.displayName = 'FdmSdkProvider'; type Props = { sdk: CogniteClient; children: any }; export function SDKProvider({ sdk, children }: Props) { - return {children}; + const fdmSdk = useMemo(() => new FdmSDK(sdk), [sdk]); + + return ( + + + {children} + + ); } export const useSDK = () => { @@ -17,4 +27,14 @@ export const useSDK = () => { ); } return sdk; +}; + +export const useFdmSdk = () => { + const fdmSdk = useContext(FdmSdkContext); + if (!fdmSdk) { + throw new Error( + `FdmSdkContext not found, add '' around your component/app` + ); + } + return fdmSdk; }; \ No newline at end of file diff --git a/react-components/src/hooks/useFdmAssetMappings.tsx b/react-components/src/hooks/useFdmAssetMappings.tsx new file mode 100644 index 00000000000..42cef17fc43 --- /dev/null +++ b/react-components/src/hooks/useFdmAssetMappings.tsx @@ -0,0 +1,64 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { CogniteExternalId } from '@cognite/sdk'; +import { useFdmSdk } from '../components/RevealContainer/SDKProvider'; +import { Source } from '../utilities/FdmSdk'; + +export type FdmAssetMappingsConfig = { + source: Source; + assetFdmSpace: string; +} + +export type ThreeDModelMappings = { modelId: number, revisionId: number, mappings: { nodeId: number, externalId: string }[] }; + +/** + * This hook fetches the list of FDM asset mappings for the given external ids + */ +export const useFdmAssetMappings = (fdmAssetExternalIds: CogniteExternalId[], fdmConfig: FdmAssetMappingsConfig): + Promise => { + const fdmSdk = useFdmSdk(); + + if (!fdmSdk || fdmAssetExternalIds?.length === 0) { + return new Promise(() => []); + } + + const fdmAssetMappingFilter = { + in: { + property: ["edge", "startNode"], + values: fdmAssetExternalIds.map((externalId) => ({ space: fdmConfig.assetFdmSpace, externalId })) + } + }; + + const modelMappings = useMemo(async () => { + const instances = await fdmSdk.filterInstances(fdmAssetMappingFilter, "edge", fdmConfig.source); + + const modelMappingsTemp: ThreeDModelMappings[] = []; + + instances.edges.forEach((instance) => { + const mappingProperty = instance.properties[fdmConfig.source.space][`${fdmConfig.source.externalId}/${fdmConfig.source.version}`]; + + const modelId = Number.parseInt(instance.endNode.externalId.slice(9)); + const revisionId = mappingProperty.RevisionId; + + const isAdded = modelMappingsTemp.some((mapping) => mapping.modelId === modelId && mapping.revisionId === revisionId); + + if (!isAdded) { + modelMappingsTemp.push({ + modelId, + revisionId, + mappings: [{ nodeId: mappingProperty.NodeId, externalId: instance.startNode.externalId }] + }); + } else { + const modelMapping = modelMappingsTemp.find((mapping) => mapping.modelId === modelId && mapping.revisionId === revisionId); + + modelMapping?.mappings.push({ nodeId: mappingProperty.NodeId, externalId: instance.startNode.externalId }); + } + + }); + + return modelMappingsTemp; + + }, [fdmAssetExternalIds]); + + return modelMappings; + +}; \ No newline at end of file diff --git a/react-components/src/utilities/FdmSDK.ts b/react-components/src/utilities/FdmSDK.ts new file mode 100644 index 00000000000..d5cba810c8a --- /dev/null +++ b/react-components/src/utilities/FdmSDK.ts @@ -0,0 +1,73 @@ +/*! + * Copyright 2023 Cognite AS + */ + +import { CogniteClient } from '@cognite/sdk'; + +type InstanceType = 'node' | 'edge'; + +export type Item = { + instanceType: InstanceType; +} & DmsUniqueIdentifier; + +export type Source = { + type: 'view'; + version: string; +} & DmsUniqueIdentifier; + +export type DmsUniqueIdentifier = { + space: string; + externalId: string; +}; + +export type EdgeItem = { + startNode: DmsUniqueIdentifier; + endNode: DmsUniqueIdentifier; + properties: PropertiesType; +}; + +export class FdmSDK { + private readonly _sdk: CogniteClient; + private readonly _byIdsEndpoint: string; + private readonly _listEndpoint: string; + + constructor(sdk: CogniteClient) { + const baseUrl = sdk.getBaseUrl(); + const project = sdk.project; + + this._listEndpoint = `${baseUrl}/api/v1/projects/${project}/models/instances/list`; + this._byIdsEndpoint = `${baseUrl}/api/v1/projects/${project}/models/instances/byids`; + + this._sdk = sdk; + } + + public async getInstancesByExternalIds(items: Item[], source: Source): Promise { + const result = await this._sdk.post(this._byIdsEndpoint, { data: { items, sources: [{ source }] } }); + + if (result.status === 200) { + return result.data.items; + } + throw new Error(`Failed to fetch instances. Status: ${result.status}`); + } + + public async filterInstances( + filter: any, + instanceType: InstanceType, + source?: Source, + cursor?: string + ): Promise<{ edges: EdgeItem[]; nextCursor?: string }> { + const data: any = { filter, instanceType }; + if (source) { + data.sources = [{ source }]; + } + if (cursor) { + data.cursor = cursor; + } + + const result = await this._sdk.post(this._listEndpoint, { data }); + if (result.status === 200) { + return { edges: result.data.items as EdgeItem[], nextCursor: result.data.nextCursor }; + } + throw new Error(`Failed to fetch instances. Status: ${result.status}`); + } +} \ No newline at end of file diff --git a/react-components/stories/Reveal3DResources.stories.tsx b/react-components/stories/Reveal3DResources.stories.tsx index cf1820c7748..52461db5fb4 100644 --- a/react-components/stories/Reveal3DResources.stories.tsx +++ b/react-components/stories/Reveal3DResources.stories.tsx @@ -10,7 +10,75 @@ import { createSdkByUrlToken } from './utilities/createSdkByUrlToken'; const meta = { title: 'Example/Reveal3DResources', component: Reveal3DResources, - tags: ['autodocs'] + tags: ['autodocs'], + argTypes: { + styling: { + description: 'Styling of all models', + options: ['RedCad', 'GreenPointCloud', 'BlueCrane', 'GreenRedAssetMapped', 'None'], + control: { + type: 'radio', + + }, + label: 'Styling of models', + mapping: { + RedCad: { + defaultStyle: { + cad: { color: new Color('red') } + } + }, + GreenPointCloud: { + defaultStyle: { + pointCloud: { color: new Color('green') } + } + }, + BlueCrane: { + groups: [ + { + fdmAssetExternalIds: [ + '23de4d93f9f482f307272f4924b83bd9cdc71e33e06003c7ec0b540135e13c24', // Rotating crane + ], + style: { + cad: { + color: new Color('blue') + } + } + } + ] + }, + GreenRedAssetMapped: { + defaultStyle: { + cad: { color: new Color('white') } + }, + groups: [ + { + fdmAssetExternalIds: [ + '23de4d93f9f482f307272f4924b83bd9cdc71e33e06003c7ec0b540135e13c24', // Rotating crane + 'ca020a82b244eed433ca598a7410169fc21543d6192eebd74fba70a5af984db7', // 1 Pipe in the middle + ], + style: { + cad: { + color: new Color('green') + } + } + }, + { + fdmAssetExternalIds: [ + '783fe42d9b24229e1873a49a0ce189fc27c0741f6739f82b29e765b835de17f2', // Big tank on the side + 'e39746a8d819f863a92ef37edc1b5d99e89d2e990c1a5951adfe9835f90de34c', // 2 Pipe in the middle + '1db4e31c8f68acee9ff62a098a103cd49e5cea0320d7aed8aa345e99c6b2663d', // 3 Pipe in the middle + ], + style: { + cad: { + color: new Color('red') + } + } + } + ] + }, + None: {} + } + } + } } satisfies Meta; export default meta; @@ -23,12 +91,13 @@ export const Main: Story = { resources: [ { modelId: 1791160622840317, - revisionId: 498427137020189 + revisionId: 498427137020189, + transform: new Matrix4().makeTranslation(40, 0, 0) }, { modelId: 1791160622840317, revisionId: 498427137020189, - transform: new Matrix4().makeTranslation(0, 10, 0) + transform: new Matrix4().makeTranslation(40, 10, 0) }, { siteId: 'c_RC_2' @@ -36,6 +105,11 @@ export const Main: Story = { { modelId: 3865289545346058, revisionId: 4160448151596909 + }, + { + modelId: 2551525377383868, + revisionId: 2143672450453400, + transform: new Matrix4().makeTranslation(-340, -480, 80) } ], styling: { @@ -46,7 +120,23 @@ export const Main: Story = { pointcloud: { color: new Color('blue') } - } + }, + groups: [ + { + fdmAssetExternalIds: [ + '23de4d93f9f482f307272f4924b83bd9cdc71e33e06003c7ec0b540135e13c24', // Rotating crane + '783fe42d9b24229e1873a49a0ce189fc27c0741f6739f82b29e765b835de17f2', // Big tank on the side + 'ca020a82b244eed433ca598a7410169fc21543d6192eebd74fba70a5af984db7', // 1 Pipe in the middle + 'e39746a8d819f863a92ef37edc1b5d99e89d2e990c1a5951adfe9835f90de34c', // 2 Pipe in the middle + '1db4e31c8f68acee9ff62a098a103cd49e5cea0320d7aed8aa345e99c6b2663d', // 3 Pipe in the middle + ], + style: { + cad: { + color: new Color('red') + } + } + } + ] } }, render: ({ resources, styling}) => ( @@ -64,6 +154,10 @@ export const Main: Story = { initialFitCamera={{ to: 'allModels' }} + cameraControlsOptions={{ + changeCameraTargetOnClick: true, + mouseWheelAction: 'zoomToCursor' + }} /> ) From 552c8c4330f270f185accfe41450b79aef362c44 Mon Sep 17 00:00:00 2001 From: Savelii Novikov Date: Tue, 11 Jul 2023 17:29:33 +0200 Subject: [PATCH 12/22] Working styling component --- react-components/package.json | 2 + .../CadModelContainer/CadModelContainer.tsx | 8 +- .../Reveal3DResources/Reveal3DResources.tsx | 57 ++++++------ .../RevealContainer/RevealContainer.tsx | 26 +++--- .../src/hooks/useFdmAssetMappings.tsx | 18 ++-- .../stories/CadModelContainer.stories.tsx | 21 ++++- .../stories/Reveal3DResources.stories.tsx | 2 +- react-components/yarn.lock | 86 +++++++++++++++++++ 8 files changed, 162 insertions(+), 58 deletions(-) diff --git a/react-components/package.json b/react-components/package.json index 0054aecfd1a..516717b7e1a 100644 --- a/react-components/package.json +++ b/react-components/package.json @@ -36,6 +36,7 @@ "@storybook/react": "7.0.24", "@storybook/react-webpack5": "7.0.24", "@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", @@ -69,6 +70,7 @@ "dist" ], "dependencies": { + "@tanstack/react-query": "^4.29.19", "lodash": "^4.17.21", "style-loader": "^3.3.3" } diff --git a/react-components/src/components/CadModelContainer/CadModelContainer.tsx b/react-components/src/components/CadModelContainer/CadModelContainer.tsx index 6364456ae50..4d6eb097b21 100644 --- a/react-components/src/components/CadModelContainer/CadModelContainer.tsx +++ b/react-components/src/components/CadModelContainer/CadModelContainer.tsx @@ -10,12 +10,12 @@ import { CogniteClient } from '@cognite/sdk'; export type NodeStylingGroup = { nodeIds: number[]; - style: NodeAppearance; + style?: NodeAppearance; }; export type TreeIndexStylingGroup = { treeIndices: number[]; - style: NodeAppearance; + style?: NodeAppearance; }; export type CadModelStyling = { @@ -97,10 +97,10 @@ function applyStyling(sdk: CogniteClient, model: CogniteCadModel, styling?: CadM if (styling.groups !== undefined) { for (const group of styling.groups) { - if ('treeIndices' in group) { + if ('treeIndices' in group && group.style !== undefined) { const nodes = new TreeIndexNodeCollection(group.treeIndices); model.assignStyledNodeCollection(nodes, group.style); - } else if ('nodeIds' in group) { + } else if ('nodeIds' in group && group.style !== undefined) { const nodes = new NodeIdNodeCollection(sdk, model); nodes.executeFilter(group.nodeIds); model.assignStyledNodeCollection(nodes, group.style); diff --git a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx index 3113f3c703d..8100c648bd7 100644 --- a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx +++ b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx @@ -4,7 +4,7 @@ import { useRef, type ReactElement, useContext, useState, useEffect, useMemo } from 'react'; import { NodeAppearance, type Cognite3DViewer, PointCloudAppearance, CogniteCadModel, CognitePointCloudModel, Image360Collection } from '@cognite/reveal'; import { ModelsLoadingStateContext } from './ModelsLoadingContext'; -import { CadModelContainer, CadModelStyling } from '../CadModelContainer/CadModelContainer'; +import { CadModelContainer, CadModelStyling, NodeStylingGroup } from '../CadModelContainer/CadModelContainer'; import { PointCloudContainer, PointCloudModelStyling } from '../PointCloudContainer/PointCloudContainer'; import { Image360CollectionContainer } from '../Image360CollectionContainer/Image360CollectionContainer'; import { useReveal } from '../RevealContainer/RevealContext'; @@ -53,22 +53,43 @@ export const Reveal3DResources = ({ resources, styling }: Reveal3DResourcesProps assetFdmSpace: 'bark-corporation' }; - const mappings = useFdmAssetMappings(stylingExternalIds, fdmAssetMappingSource); + const { data: mappings } = useFdmAssetMappings(stylingExternalIds, fdmAssetMappingSource); useEffect(() => { getTypedModels(resources, viewer).then(setReveal3DModels).catch(console.error); }, [resources, viewer]); - useEffect(() => { if (styling === undefined || reveal3DModels === undefined) return; const modelsStyling = reveal3DModels.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 ? [] : 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 }; break; case 'pointcloud': @@ -81,38 +102,12 @@ export const Reveal3DResources = ({ resources, styling }: Reveal3DResourcesProps console.warn(`Unknown model type: ${model.type}`); break; } - return modelStyling; }); - mappings.then((modelMappings) => { - const newModelsStyling = modelsStyling.map((modelStyling, index) => { - const model = reveal3DModels[index]; - if (model.type === 'cad') { - const modelNodeMappings = modelMappings.find((mapping) => mapping.modelId === model.modelId && mapping.revisionId === model.revisionId); - const nodeStyling = styling?.groups?.find((group) => - group.fdmAssetExternalIds.filter((externalId) => - modelNodeMappings?.mappings.some((modelNodeMapping) => - modelNodeMapping.externalId === externalId)) - .length > 0 - ); - - if (modelNodeMappings && styling?.groups) { - (modelStyling as CadModelStyling).groups = [...((modelStyling as CadModelStyling)?.groups ?? []), {style: nodeStyling?.style?.cad!, nodeIds: modelNodeMappings.mappings.map((mapping) => mapping.nodeId)}] - } - - return modelStyling; - } else if (model.type === 'pointcloud') { - return modelStyling; - } - - return modelStyling; - }); - - setReveal3DModelsStyling(newModelsStyling); - }); + setReveal3DModelsStyling(modelsStyling); - }, [mappings, styling, reveal3DModels]); + }, [mappings, styling, reveal3DModels, mappings]); const image360CollectionAddOptions = resources.filter( (resource): resource is AddImageCollection360Options => diff --git a/react-components/src/components/RevealContainer/RevealContainer.tsx b/react-components/src/components/RevealContainer/RevealContainer.tsx index af2e162ab7e..547464ca14d 100644 --- a/react-components/src/components/RevealContainer/RevealContainer.tsx +++ b/react-components/src/components/RevealContainer/RevealContainer.tsx @@ -8,6 +8,8 @@ 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 { ReactQueryDevtools } from '@tanstack/react-query-devtools'; type RevealContainerProps = { color?: Color; @@ -25,6 +27,8 @@ type RevealContainerProps = { >; }; +const queryClient = new QueryClient(); + export function RevealContainer({ children, sdk, @@ -40,22 +44,24 @@ export function RevealContainer({ }, []); return ( -
- {mountChildren()} -
+ + +
+ {mountChildren()} +
+
+
); function mountChildren(): ReactElement { if (viewer === undefined) return <>; return ( <> - - - - {children} - - - + + + {children} + + ); } diff --git a/react-components/src/hooks/useFdmAssetMappings.tsx b/react-components/src/hooks/useFdmAssetMappings.tsx index 42cef17fc43..45313ebf693 100644 --- a/react-components/src/hooks/useFdmAssetMappings.tsx +++ b/react-components/src/hooks/useFdmAssetMappings.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { CogniteExternalId } from '@cognite/sdk'; import { useFdmSdk } from '../components/RevealContainer/SDKProvider'; import { Source } from '../utilities/FdmSdk'; +import { UseQueryResult, useQuery } from '@tanstack/react-query'; export type FdmAssetMappingsConfig = { source: Source; @@ -14,13 +15,9 @@ export type ThreeDModelMappings = { modelId: number, revisionId: number, mapping * This hook fetches the list of FDM asset mappings for the given external ids */ export const useFdmAssetMappings = (fdmAssetExternalIds: CogniteExternalId[], fdmConfig: FdmAssetMappingsConfig): - Promise => { + UseQueryResult => { const fdmSdk = useFdmSdk(); - if (!fdmSdk || fdmAssetExternalIds?.length === 0) { - return new Promise(() => []); - } - const fdmAssetMappingFilter = { in: { property: ["edge", "startNode"], @@ -28,7 +25,10 @@ export const useFdmAssetMappings = (fdmAssetExternalIds: CogniteExternalId[], fd } }; - const modelMappings = useMemo(async () => { + return useQuery(['reveal','react-components', fdmAssetExternalIds], async () => { + if (fdmAssetExternalIds?.length === 0) + return []; + const instances = await fdmSdk.filterInstances(fdmAssetMappingFilter, "edge", fdmConfig.source); const modelMappingsTemp: ThreeDModelMappings[] = []; @@ -56,9 +56,5 @@ export const useFdmAssetMappings = (fdmAssetExternalIds: CogniteExternalId[], fd }); return modelMappingsTemp; - - }, [fdmAssetExternalIds]); - - return modelMappings; - + }, {staleTime: 600000}); }; \ No newline at end of file diff --git a/react-components/stories/CadModelContainer.stories.tsx b/react-components/stories/CadModelContainer.stories.tsx index 7e9a9e065a1..8683c0fecd4 100644 --- a/react-components/stories/CadModelContainer.stories.tsx +++ b/react-components/stories/CadModelContainer.stories.tsx @@ -13,7 +13,10 @@ const meta = { argTypes: { styling: { description: 'Styling of the first model', - options: ['FullRed', 'HalfGreen', 'SomeBlue', 'None'], + options: ['FullRed', 'HalfGreen', 'SomeBlue', 'Colorful', 'None'], + control: { + type: 'radio', + }, label: 'Styling of the first model', mapping: { FullRed: { @@ -35,6 +38,22 @@ const meta = { } ] }, + Colorful: { + groups: [ + { + treeIndices: new NumericRange(0, 40), + style: { color: new Color('red') } + }, + { + nodeIds: [1903632131225149, 8923105504012177], + style: { color: new Color('green') } + }, + { + nodeIds: [8757509474262596, 2712303310330098, 3709428615074138 ], + style: { color: new Color('blue') } + }, + ] + }, None: {} } } diff --git a/react-components/stories/Reveal3DResources.stories.tsx b/react-components/stories/Reveal3DResources.stories.tsx index 52461db5fb4..761bbe0a0ef 100644 --- a/react-components/stories/Reveal3DResources.stories.tsx +++ b/react-components/stories/Reveal3DResources.stories.tsx @@ -28,7 +28,7 @@ const meta = { }, GreenPointCloud: { defaultStyle: { - pointCloud: { color: new Color('green') } + pointcloud: { color: new Color('green') } } }, BlueCrane: { diff --git a/react-components/yarn.lock b/react-components/yarn.lock index c9f5c06d99f..ce619f6439a 100644 --- a/react-components/yarn.lock +++ b/react-components/yarn.lock @@ -2102,6 +2102,8 @@ __metadata: "@storybook/react": 7.0.24 "@storybook/react-webpack5": 7.0.24 "@storybook/testing-library": 0.2.0 + "@tanstack/react-query": ^4.29.19 + "@tanstack/react-query-devtools": ^4.29.19 "@types/lodash": ^4.14.190 "@types/react": 18.2.7 "@types/styled-components": 5.1.26 @@ -4374,6 +4376,56 @@ __metadata: languageName: node linkType: hard +"@tanstack/match-sorter-utils@npm:^8.7.0": + version: 8.8.4 + resolution: "@tanstack/match-sorter-utils@npm:8.8.4" + dependencies: + remove-accents: 0.4.2 + checksum: d005f500754f52ef94966cbbe4217f26e7e3c07291faa2578b06bca9a5abe01689569994c37a1d01c6e783addf5ffbb28fa82eba7961d36eabf43ec43d1e496b + languageName: node + linkType: hard + +"@tanstack/query-core@npm:4.29.19": + version: 4.29.19 + resolution: "@tanstack/query-core@npm:4.29.19" + checksum: 91a79dbebebc139d118542a5fe83fb54a6f6448bbe1563db1a0bed0c6d44e1d023613efde3c4ac747c72d067110013ac56f90611bf8affc390eda1ac3f99066e + languageName: node + linkType: hard + +"@tanstack/react-query-devtools@npm:^4.29.19": + version: 4.29.19 + resolution: "@tanstack/react-query-devtools@npm:4.29.19" + dependencies: + "@tanstack/match-sorter-utils": ^8.7.0 + superjson: ^1.10.0 + use-sync-external-store: ^1.2.0 + peerDependencies: + "@tanstack/react-query": 4.29.19 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: f077028c109da225b8e8e907a8fb4ad47d3049f6f5d16e5613adb135c531abf62c8975384edbb4d502bb064c98c3c513ee4f3df7c686198e644808311145e79c + languageName: node + linkType: hard + +"@tanstack/react-query@npm:^4.29.19": + version: 4.29.19 + resolution: "@tanstack/react-query@npm:4.29.19" + dependencies: + "@tanstack/query-core": 4.29.19 + use-sync-external-store: ^1.2.0 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-native: "*" + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: 2780ac35b4d1dabcd3e708b95891376e7e609109270d40442b33f4747c508c23e46bcadc692f997f81d38f62b5ab31f8e42bc9a785dfa4afe0c390adb6f37e52 + languageName: node + linkType: hard + "@testing-library/dom@npm:^9.0.0": version: 9.3.1 resolution: "@testing-library/dom@npm:9.3.1" @@ -6666,6 +6718,15 @@ __metadata: languageName: node linkType: hard +"copy-anything@npm:^3.0.2": + version: 3.0.5 + resolution: "copy-anything@npm:3.0.5" + dependencies: + is-what: ^4.1.8 + checksum: d39f6601c16b7cbd81cdb1c1f40f2bf0f2ca0297601cf7bfbb4ef1d85374a6a89c559502329f5bada36604464df17623e111fe19a9bb0c3f6b1c92fe2cbe972f + languageName: node + linkType: hard + "core-js-compat@npm:^3.25.1, core-js-compat@npm:^3.30.1, core-js-compat@npm:^3.30.2": version: 3.30.2 resolution: "core-js-compat@npm:3.30.2" @@ -9735,6 +9796,13 @@ __metadata: languageName: node linkType: hard +"is-what@npm:^4.1.8": + version: 4.1.15 + resolution: "is-what@npm:4.1.15" + checksum: fe27f6cd4af41be59a60caf46ec09e3071bcc69b9b12a7c871c90f54360edb6d0bc7240cb944a251fb0afa3d35635d1cecea9e70709876b368a8285128d70a89 + languageName: node + linkType: hard + "is-wsl@npm:^2.1.1, is-wsl@npm:^2.2.0": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" @@ -13289,6 +13357,15 @@ __metadata: languageName: node linkType: hard +"superjson@npm:^1.10.0": + version: 1.12.4 + resolution: "superjson@npm:1.12.4" + dependencies: + copy-anything: ^3.0.2 + checksum: fcf37714f734cd51af018b78fd7f630952a0682fc3cb060d00b824e5d0cd49f0206b8e23829469c99972fd7fcd6e468b9492abb1c4e258ea5ec7f45c345a56f2 + languageName: node + linkType: hard + "supports-color@npm:^5.3.0, supports-color@npm:^5.5.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -13909,6 +13986,15 @@ __metadata: languageName: node linkType: hard +"use-sync-external-store@npm:^1.2.0": + version: 1.2.0 + resolution: "use-sync-external-store@npm:1.2.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 5c639e0f8da3521d605f59ce5be9e094ca772bd44a4ce7322b055a6f58eeed8dda3c94cabd90c7a41fb6fa852210092008afe48f7038792fd47501f33299116a + languageName: node + linkType: hard + "util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" From 7277d22db803ba3dada06acad40d18f79050f537 Mon Sep 17 00:00:00 2001 From: Savelii Novikov Date: Wed, 12 Jul 2023 10:10:37 +0200 Subject: [PATCH 13/22] Removed annotation styling scaffolding --- .../Image360CollectionContainer.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx b/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx index 3377c686962..b6f5f92d2c9 100644 --- a/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx +++ b/react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx @@ -3,21 +3,15 @@ */ import { type ReactElement, useEffect, useRef } from 'react'; import { useReveal } from '../RevealContainer/RevealContext'; -import { type Image360Collection, type Image360AnnotationAppearance } from '@cognite/reveal'; - -type Image360CollectionStyling = { - defaultStyle?: Image360AnnotationAppearance; -}; +import { type Image360Collection } from '@cognite/reveal'; type Image360CollectionContainerProps = { siteId: string; - styling?: Image360CollectionStyling; onLoad?: (image360: Image360Collection) => void; }; export function Image360CollectionContainer({ siteId, - styling, onLoad }: Image360CollectionContainerProps): ReactElement { const modelRef = useRef(); From 4ebcccf82c49f5a85a44cd7ea314c3ddf47d1fc9 Mon Sep 17 00:00:00 2001 From: Savelii Novikov Date: Wed, 12 Jul 2023 10:47:36 +0200 Subject: [PATCH 14/22] Fixing lint issues --- .../CadModelContainer/CadModelContainer.tsx | 29 +++-- .../CameraController/CameraController.tsx | 13 +- .../PointCloudContainer.tsx | 10 +- .../Reveal3DResources/Reveal3DResources.tsx | 104 ++++++++++------ .../RevealContainer/RevealContainer.tsx | 11 +- .../RevealContainer/SDKProvider.tsx | 44 +++---- .../src/hooks/useFdmAssetMappings.tsx | 115 +++++++++++------- react-components/src/utilities/FdmSDK.ts | 26 ++-- .../stories/CadModelContainer.stories.tsx | 17 +-- .../stories/PointCloudContainer.stories.tsx | 27 ++-- .../stories/Reveal3DResources.stories.tsx | 35 +----- 11 files changed, 253 insertions(+), 178 deletions(-) diff --git a/react-components/src/components/CadModelContainer/CadModelContainer.tsx b/react-components/src/components/CadModelContainer/CadModelContainer.tsx index 4d6eb097b21..c55beaf43a2 100644 --- a/react-components/src/components/CadModelContainer/CadModelContainer.tsx +++ b/react-components/src/components/CadModelContainer/CadModelContainer.tsx @@ -1,12 +1,19 @@ /*! * Copyright 2023 Cognite AS */ -import { type ReactElement, useEffect, useRef, useState } from 'react'; -import { NodeAppearance, type AddModelOptions, type CogniteCadModel, TreeIndexNodeCollection, NodeIdNodeCollection, DefaultNodeAppearance} 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 { CogniteClient } from '@cognite/sdk'; +import { type CogniteClient } from '@cognite/sdk'; export type NodeStylingGroup = { nodeIds: number[]; @@ -20,7 +27,7 @@ export type TreeIndexStylingGroup = { export type CadModelStyling = { defaultStyle?: NodeAppearance; - groups?: (NodeStylingGroup | TreeIndexStylingGroup) [] + groups?: Array; }; type CogniteCadModelProps = { @@ -55,8 +62,8 @@ export function CadModelContainer({ useEffect(() => { if (model === undefined || styling === undefined) return; - applyStyling(sdk, model, styling); - + applyStyling(sdk, model, styling).catch(console.error); + return () => { model.removeAllStyledNodeCollections(); model.setDefaultNodeAppearance(DefaultNodeAppearance.Default); @@ -88,13 +95,17 @@ export function CadModelContainer({ } } -function applyStyling(sdk: CogniteClient, model: CogniteCadModel, styling?: CadModelStyling): void { +async function applyStyling( + sdk: CogniteClient, + model: CogniteCadModel, + styling?: CadModelStyling +): Promise { 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) { @@ -102,7 +113,7 @@ function applyStyling(sdk: CogniteClient, model: CogniteCadModel, styling?: CadM model.assignStyledNodeCollection(nodes, group.style); } else if ('nodeIds' in group && group.style !== undefined) { const nodes = new NodeIdNodeCollection(sdk, model); - nodes.executeFilter(group.nodeIds); + await nodes.executeFilter(group.nodeIds); model.assignStyledNodeCollection(nodes, group.style); } } diff --git a/react-components/src/components/CameraController/CameraController.tsx b/react-components/src/components/CameraController/CameraController.tsx index 45b14a780a2..1b92df1bd4d 100644 --- a/react-components/src/components/CameraController/CameraController.tsx +++ b/react-components/src/components/CameraController/CameraController.tsx @@ -4,7 +4,11 @@ import { type ReactElement, useEffect, useContext, useRef } from 'react'; import { useReveal } from '../RevealContainer/RevealContext'; import { ModelsLoadingStateContext } from '../Reveal3DResources/ModelsLoadingContext'; -import { DefaultCameraManager, type CameraControlsOptions, type CameraState } from '@cognite/reveal'; +import { + DefaultCameraManager, + type CameraControlsOptions, + type CameraState +} from '@cognite/reveal'; export type CameraControllerProps = { initialFitCamera?: FittingStrategy; @@ -16,7 +20,10 @@ type FittingStrategy = | { to: 'allModels' } | { to: 'none' }; -export function CameraController({ initialFitCamera, cameraControlsOptions }: CameraControllerProps): ReactElement { +export function CameraController({ + initialFitCamera, + cameraControlsOptions +}: CameraControllerProps): ReactElement { const initialCameraSet = useRef(false); const viewer = useReveal(); const { modelsAdded } = useContext(ModelsLoadingStateContext); @@ -28,7 +35,7 @@ export function CameraController({ initialFitCamera, cameraControlsOptions }: Ca if (!(viewer.cameraManager instanceof DefaultCameraManager)) throw new Error('CameraControlsOptions can be set only on default CameraManager'); - + viewer.cameraManager.setCameraControlsOptions(cameraControlsOptions); }, [cameraControlsOptions]); diff --git a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx index 10ac601d253..f5feb0d0efa 100644 --- a/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx +++ b/react-components/src/components/PointCloudContainer/PointCloudContainer.tsx @@ -1,7 +1,13 @@ /*! * Copyright 2023 Cognite AS */ -import { type CognitePointCloudModel, type AddModelOptions, PointCloudAppearance, DefaultPointCloudAppearance, AnnotationIdPointCloudObjectCollection } from '@cognite/reveal'; +import { + type CognitePointCloudModel, + type AddModelOptions, + type PointCloudAppearance, + DefaultPointCloudAppearance, + AnnotationIdPointCloudObjectCollection +} from '@cognite/reveal'; import { useEffect, type ReactElement, useState } from 'react'; import { type Matrix4 } from 'three'; @@ -68,7 +74,7 @@ export function PointCloudContainer({ function removeModel(): void { if (model === undefined || !viewer.models.includes(model)) return; - + viewer.removeModel(model); setModel(undefined); } diff --git a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx index 8100c648bd7..e37e8e69575 100644 --- a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx +++ b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx @@ -2,10 +2,21 @@ * Copyright 2023 Cognite AS */ import { useRef, type ReactElement, useContext, useState, useEffect, useMemo } from 'react'; -import { NodeAppearance, type Cognite3DViewer, PointCloudAppearance, CogniteCadModel, CognitePointCloudModel, Image360Collection } from '@cognite/reveal'; +import { + type NodeAppearance, + type Cognite3DViewer, + type PointCloudAppearance +} from '@cognite/reveal'; import { ModelsLoadingStateContext } from './ModelsLoadingContext'; -import { CadModelContainer, CadModelStyling, NodeStylingGroup } from '../CadModelContainer/CadModelContainer'; -import { PointCloudContainer, PointCloudModelStyling } from '../PointCloudContainer/PointCloudContainer'; +import { + CadModelContainer, + type CadModelStyling, + type NodeStylingGroup +} from '../CadModelContainer/CadModelContainer'; +import { + PointCloudContainer, + type PointCloudModelStyling +} from '../PointCloudContainer/PointCloudContainer'; import { Image360CollectionContainer } from '../Image360CollectionContainer/Image360CollectionContainer'; import { useReveal } from '../RevealContainer/RevealContext'; import { @@ -14,19 +25,17 @@ import { type TypedReveal3DModel, type AddResourceOptions } from './types'; -import { CogniteExternalId } from '@cognite/sdk'; -import { useSDK } from '../RevealContainer/SDKProvider'; -import { FdmAssetMappingsConfig, useFdmAssetMappings } from '../../hooks/useFdmAssetMappings'; - +import { type CogniteExternalId } from '@cognite/sdk'; +import { type FdmAssetMappingsConfig, useFdmAssetMappings } from '../../hooks/useFdmAssetMappings'; export type FdmAssetStylingGroup = { fdmAssetExternalIds: CogniteExternalId[]; style: { cad?: NodeAppearance; pointcloud?: PointCloudAppearance }; -} +}; export type Reveal3DResourcesStyling = { - defaultStyle?: { cad?: NodeAppearance, pointcloud?: PointCloudAppearance } - groups?: FdmAssetStylingGroup[], + defaultStyle?: { cad?: NodeAppearance; pointcloud?: PointCloudAppearance }; + groups?: FdmAssetStylingGroup[]; }; export type Reveal3DResourcesProps = { @@ -36,77 +45,92 @@ export type Reveal3DResourcesProps = { export const Reveal3DResources = ({ resources, styling }: Reveal3DResourcesProps): ReactElement => { const [reveal3DModels, setReveal3DModels] = useState([]); - const [reveal3DModelsStyling, setReveal3DModelsStyling] = useState<(PointCloudModelStyling | CadModelStyling)[]>([]); + const [reveal3DModelsStyling, setReveal3DModelsStyling] = useState< + Array + >([]); const { setModelsAdded } = useContext(ModelsLoadingStateContext); const viewer = useReveal(); const numModelsLoaded = useRef(0); - const stylingExternalIds = useMemo(() => styling?.groups?.flatMap((group) => group.fdmAssetExternalIds) ?? [], [styling]); + const stylingExternalIds = useMemo( + () => styling?.groups?.flatMap((group) => group.fdmAssetExternalIds) ?? [], + [styling] + ); const fdmAssetMappingSource: FdmAssetMappingsConfig = { source: { space: 'fdm-3d-test-savelii', version: '1', type: 'view', - externalId: 'CDF_3D_Connection_Data', + externalId: 'CDF_3D_Connection_Data' }, assetFdmSpace: 'bark-corporation' }; - + const { data: mappings } = useFdmAssetMappings(stylingExternalIds, fdmAssetMappingSource); useEffect(() => { getTypedModels(resources, viewer).then(setReveal3DModels).catch(console.error); }, [resources, viewer]); - + useEffect(() => { - if (styling === undefined || reveal3DModels === undefined) return; + if (styling === undefined || reveal3DModels === undefined) return; const modelsStyling = reveal3DModels.map((model) => { let modelStyling: PointCloudModelStyling | CadModelStyling; switch (model.type) { - case 'cad': - const modelNodeMappings = mappings?.find((mapping) => mapping.modelId === model.modelId && mapping.revisionId === model.revisionId); + case 'cad': { + const modelNodeMappings = mappings?.find( + (mapping) => + mapping.modelId === model.modelId && mapping.revisionId === model.revisionId + ); + + const newStylingGroups: NodeStylingGroup[] | undefined = + styling.groups !== null ? [] : undefined; - const newStylingGroups: NodeStylingGroup[] | undefined = styling.groups ? [] : undefined; - styling.groups?.forEach((group) => { const connectedExternalIds = group.fdmAssetExternalIds.filter((externalId) => - modelNodeMappings?.mappings.some((modelNodeMapping) => - modelNodeMapping.externalId === 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); + 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 }; break; - case 'pointcloud': + } + case 'pointcloud': { modelStyling = { - defaultStyle: styling.defaultStyle?.pointcloud, + defaultStyle: styling.defaultStyle?.pointcloud }; break; - default: + } + default: { modelStyling = {}; console.warn(`Unknown model type: ${model.type}`); break; + } } return modelStyling; }); setReveal3DModelsStyling(modelsStyling); - }, [mappings, styling, reveal3DModels, mappings]); const image360CollectionAddOptions = resources.filter( @@ -125,7 +149,10 @@ export const Reveal3DResources = ({ resources, styling }: Reveal3DResourcesProps return ( <> {reveal3DModels - .map((modelData, index) => ({...modelData, styling: reveal3DModelsStyling[index] as CadModelStyling})) + .map((modelData, index) => ({ + ...modelData, + styling: reveal3DModelsStyling[index] as CadModelStyling + })) .filter(({ type }) => type === 'cad') .map((modelData, index) => { return ( @@ -139,7 +166,10 @@ export const Reveal3DResources = ({ resources, styling }: Reveal3DResourcesProps ); })} {reveal3DModels - .map((modelData, index) => ({...modelData, styling: reveal3DModelsStyling[index] as PointCloudModelStyling})) + .map((modelData, index) => ({ + ...modelData, + styling: reveal3DModelsStyling[index] as PointCloudModelStyling + })) .filter(({ type }) => type === 'pointcloud') .map((modelData, index) => { return ( diff --git a/react-components/src/components/RevealContainer/RevealContainer.tsx b/react-components/src/components/RevealContainer/RevealContainer.tsx index 547464ca14d..2f7b2ae3944 100644 --- a/react-components/src/components/RevealContainer/RevealContainer.tsx +++ b/react-components/src/components/RevealContainer/RevealContainer.tsx @@ -9,7 +9,6 @@ import { type Color } from 'three'; import { ModelsLoadingStateContext } from '../Reveal3DResources/ModelsLoadingContext'; import { SDKProvider } from './SDKProvider'; import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; type RevealContainerProps = { color?: Color; @@ -46,9 +45,9 @@ export function RevealContainer({ return ( -
- {mountChildren()} -
+
+ {mountChildren()} +
); @@ -58,9 +57,7 @@ export function RevealContainer({ return ( <> - - {children} - + {children} ); diff --git a/react-components/src/components/RevealContainer/SDKProvider.tsx b/react-components/src/components/RevealContainer/SDKProvider.tsx index f51cd582335..2e28fde5a45 100644 --- a/react-components/src/components/RevealContainer/SDKProvider.tsx +++ b/react-components/src/components/RevealContainer/SDKProvider.tsx @@ -1,5 +1,8 @@ -import React, { useContext, createContext, useMemo} from 'react'; -import { CogniteClient } from '@cognite/sdk'; +/*! + * Copyright 2023 Cognite AS + */ +import React, { useContext, createContext, useMemo } from 'react'; +import { type CogniteClient } from '@cognite/sdk'; import { FdmSDK } from '../../utilities/FdmSdk'; const SdkContext = createContext(null); @@ -8,20 +11,19 @@ SdkContext.displayName = 'CogniteSdkProvider'; FdmSdkContext.displayName = 'FdmSdkProvider'; type Props = { sdk: CogniteClient; children: any }; -export function SDKProvider({ sdk, children }: Props) { - const fdmSdk = useMemo(() => new FdmSDK(sdk), [sdk]); +export function SDKProvider({ sdk, children }: Props): React.ReactElement { + const fdmSdk = useMemo(() => new FdmSDK(sdk), [sdk]); - return ( - - - {children} - - ); + return ( + + {children} + + ); } -export const useSDK = () => { +export const useSDK = (): CogniteClient => { const sdk = useContext(SdkContext); - if (!sdk) { + if (sdk === null) { throw new Error( `SdkContext not found, add '' around your component/app` ); @@ -29,12 +31,12 @@ export const useSDK = () => { return sdk; }; -export const useFdmSdk = () => { - const fdmSdk = useContext(FdmSdkContext); - if (!fdmSdk) { - throw new Error( - `FdmSdkContext not found, add '' around your component/app` - ); - } - return fdmSdk; -}; \ No newline at end of file +export const useFdmSdk = (): FdmSDK => { + const fdmSdk = useContext(FdmSdkContext); + if (fdmSdk === null) { + throw new Error( + `FdmSdkContext not found, add '' around your component/app` + ); + } + return fdmSdk; +}; diff --git a/react-components/src/hooks/useFdmAssetMappings.tsx b/react-components/src/hooks/useFdmAssetMappings.tsx index 45313ebf693..95ddf25eb3c 100644 --- a/react-components/src/hooks/useFdmAssetMappings.tsx +++ b/react-components/src/hooks/useFdmAssetMappings.tsx @@ -1,60 +1,89 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { CogniteExternalId } from '@cognite/sdk'; +/*! + * Copyright 2023 Cognite AS + */ +import { type CogniteExternalId } from '@cognite/sdk'; import { useFdmSdk } from '../components/RevealContainer/SDKProvider'; -import { Source } from '../utilities/FdmSdk'; -import { UseQueryResult, useQuery } from '@tanstack/react-query'; +import { type Source } from '../utilities/FdmSdk'; +import { type UseQueryResult, useQuery } from '@tanstack/react-query'; export type FdmAssetMappingsConfig = { - source: Source; - assetFdmSpace: string; -} + source: Source; + assetFdmSpace: string; +}; -export type ThreeDModelMappings = { modelId: number, revisionId: number, mappings: { nodeId: number, externalId: string }[] }; +export type ThreeDModelMappings = { + modelId: number; + revisionId: number; + mappings: Array<{ nodeId: number; externalId: string }>; +}; /** * This hook fetches the list of FDM asset mappings for the given external ids */ -export const useFdmAssetMappings = (fdmAssetExternalIds: CogniteExternalId[], fdmConfig: FdmAssetMappingsConfig): - UseQueryResult => { - const fdmSdk = useFdmSdk(); - - const fdmAssetMappingFilter = { - in: { - property: ["edge", "startNode"], - values: fdmAssetExternalIds.map((externalId) => ({ space: fdmConfig.assetFdmSpace, externalId })) - } - }; - - return useQuery(['reveal','react-components', fdmAssetExternalIds], async () => { - if (fdmAssetExternalIds?.length === 0) - return []; +export const useFdmAssetMappings = ( + fdmAssetExternalIds: CogniteExternalId[], + fdmConfig: FdmAssetMappingsConfig +): UseQueryResult => { + const fdmSdk = useFdmSdk(); - const instances = await fdmSdk.filterInstances(fdmAssetMappingFilter, "edge", fdmConfig.source); + const fdmAssetMappingFilter = { + in: { + property: ['edge', 'startNode'], + values: fdmAssetExternalIds.map((externalId) => ({ + space: fdmConfig.assetFdmSpace, + externalId + })) + } + }; - const modelMappingsTemp: ThreeDModelMappings[] = []; + return useQuery( + ['reveal', 'react-components', fdmAssetExternalIds], + async () => { + if (fdmAssetExternalIds?.length === 0) return []; - instances.edges.forEach((instance) => { - const mappingProperty = instance.properties[fdmConfig.source.space][`${fdmConfig.source.externalId}/${fdmConfig.source.version}`]; + const instances = await fdmSdk.filterInstances( + fdmAssetMappingFilter, + 'edge', + fdmConfig.source + ); - const modelId = Number.parseInt(instance.endNode.externalId.slice(9)); - const revisionId = mappingProperty.RevisionId; + const modelMappingsTemp: ThreeDModelMappings[] = []; - const isAdded = modelMappingsTemp.some((mapping) => mapping.modelId === modelId && mapping.revisionId === revisionId); + instances.edges.forEach((instance) => { + const mappingProperty = + instance.properties[fdmConfig.source.space][ + `${fdmConfig.source.externalId}/${fdmConfig.source.version}` + ]; - if (!isAdded) { - modelMappingsTemp.push({ - modelId, - revisionId, - mappings: [{ nodeId: mappingProperty.NodeId, externalId: instance.startNode.externalId }] - }); - } else { - const modelMapping = modelMappingsTemp.find((mapping) => mapping.modelId === modelId && mapping.revisionId === revisionId); + const modelId = Number.parseInt(instance.endNode.externalId.slice(9)); + const revisionId = mappingProperty.RevisionId; - modelMapping?.mappings.push({ nodeId: mappingProperty.NodeId, externalId: instance.startNode.externalId }); - } + const isAdded = modelMappingsTemp.some( + (mapping) => mapping.modelId === modelId && mapping.revisionId === revisionId + ); - }); + if (!isAdded) { + modelMappingsTemp.push({ + modelId, + revisionId, + mappings: [ + { nodeId: mappingProperty.NodeId, externalId: instance.startNode.externalId } + ] + }); + } else { + const modelMapping = modelMappingsTemp.find( + (mapping) => mapping.modelId === modelId && mapping.revisionId === revisionId + ); + + modelMapping?.mappings.push({ + nodeId: mappingProperty.NodeId, + externalId: instance.startNode.externalId + }); + } + }); - return modelMappingsTemp; - }, {staleTime: 600000}); -}; \ No newline at end of file + return modelMappingsTemp; + }, + { staleTime: 600000 } + ); +}; diff --git a/react-components/src/utilities/FdmSDK.ts b/react-components/src/utilities/FdmSDK.ts index d5cba810c8a..44197ad3d89 100644 --- a/react-components/src/utilities/FdmSDK.ts +++ b/react-components/src/utilities/FdmSDK.ts @@ -2,7 +2,7 @@ * Copyright 2023 Cognite AS */ -import { CogniteClient } from '@cognite/sdk'; +import { type CogniteClient } from '@cognite/sdk'; type InstanceType = 'node' | 'edge'; @@ -41,8 +41,13 @@ export class FdmSDK { this._sdk = sdk; } - public async getInstancesByExternalIds(items: Item[], source: Source): Promise { - const result = await this._sdk.post(this._byIdsEndpoint, { data: { items, sources: [{ source }] } }); + public async getInstancesByExternalIds>( + items: Item[], + source: Source + ): Promise { + const result = await this._sdk.post(this._byIdsEndpoint, { + data: { items, sources: [{ source }] } + }); if (result.status === 200) { return result.data.items; @@ -50,24 +55,27 @@ export class FdmSDK { throw new Error(`Failed to fetch instances. Status: ${result.status}`); } - public async filterInstances( + public async filterInstances>( filter: any, instanceType: InstanceType, source?: Source, cursor?: string - ): Promise<{ edges: EdgeItem[]; nextCursor?: string }> { + ): Promise<{ edges: Array>; nextCursor?: string }> { const data: any = { filter, instanceType }; - if (source) { + if (source !== null) { data.sources = [{ source }]; } - if (cursor) { + if (cursor !== undefined) { data.cursor = cursor; } const result = await this._sdk.post(this._listEndpoint, { data }); if (result.status === 200) { - return { edges: result.data.items as EdgeItem[], nextCursor: result.data.nextCursor }; + return { + edges: result.data.items as Array>, + nextCursor: result.data.nextCursor + }; } throw new Error(`Failed to fetch instances. Status: ${result.status}`); } -} \ No newline at end of file +} diff --git a/react-components/stories/CadModelContainer.stories.tsx b/react-components/stories/CadModelContainer.stories.tsx index 8683c0fecd4..a22df1c12a8 100644 --- a/react-components/stories/CadModelContainer.stories.tsx +++ b/react-components/stories/CadModelContainer.stories.tsx @@ -15,7 +15,7 @@ const meta = { description: 'Styling of the first model', options: ['FullRed', 'HalfGreen', 'SomeBlue', 'Colorful', 'None'], control: { - type: 'radio', + type: 'radio' }, label: 'Styling of the first model', mapping: { @@ -33,7 +33,10 @@ const meta = { SomeBlue: { groups: [ { - nodeIds: [8757509474262596, 2712303310330098, 1903632131225149, 8923105504012177, 3709428615074138], + nodeIds: [ + 8757509474262596, 2712303310330098, 1903632131225149, 8923105504012177, + 3709428615074138 + ], style: { color: new Color('blue') } } ] @@ -49,9 +52,9 @@ const meta = { style: { color: new Color('green') } }, { - nodeIds: [8757509474262596, 2712303310330098, 3709428615074138 ], + nodeIds: [8757509474262596, 2712303310330098, 3709428615074138], style: { color: new Color('blue') } - }, + } ] }, None: {} @@ -77,14 +80,14 @@ export const Main: Story = { }, transform: new Matrix4().makeTranslation(0, 10, 0) }, - render: ({ addModelOptions, transform, styling}) => ( + render: ({ addModelOptions, transform, styling }) => ( - + diff --git a/react-components/stories/PointCloudContainer.stories.tsx b/react-components/stories/PointCloudContainer.stories.tsx index 8228d34bfce..24049316fcb 100644 --- a/react-components/stories/PointCloudContainer.stories.tsx +++ b/react-components/stories/PointCloudContainer.stories.tsx @@ -46,18 +46,25 @@ export const Main: Story = { modelId: 3865289545346058, revisionId: 4160448151596909 }, - styling: { - }, - transform: new Matrix4(), + styling: {}, + transform: new Matrix4() }, - render: ({ addModelOptions, transform, styling}) => ( + render: ({ addModelOptions, transform, styling }) => ( - - + + ) }; diff --git a/react-components/stories/Reveal3DResources.stories.tsx b/react-components/stories/Reveal3DResources.stories.tsx index 761bbe0a0ef..23df119e45c 100644 --- a/react-components/stories/Reveal3DResources.stories.tsx +++ b/react-components/stories/Reveal3DResources.stories.tsx @@ -16,8 +16,7 @@ const meta = { description: 'Styling of all models', options: ['RedCad', 'GreenPointCloud', 'BlueCrane', 'GreenRedAssetMapped', 'None'], control: { - type: 'radio', - + type: 'radio' }, label: 'Styling of models', mapping: { @@ -35,7 +34,7 @@ const meta = { groups: [ { fdmAssetExternalIds: [ - '23de4d93f9f482f307272f4924b83bd9cdc71e33e06003c7ec0b540135e13c24', // Rotating crane + '23de4d93f9f482f307272f4924b83bd9cdc71e33e06003c7ec0b540135e13c24' // Rotating crane ], style: { cad: { @@ -53,7 +52,7 @@ const meta = { { fdmAssetExternalIds: [ '23de4d93f9f482f307272f4924b83bd9cdc71e33e06003c7ec0b540135e13c24', // Rotating crane - 'ca020a82b244eed433ca598a7410169fc21543d6192eebd74fba70a5af984db7', // 1 Pipe in the middle + 'ca020a82b244eed433ca598a7410169fc21543d6192eebd74fba70a5af984db7' // 1 Pipe in the middle ], style: { cad: { @@ -65,7 +64,7 @@ const meta = { fdmAssetExternalIds: [ '783fe42d9b24229e1873a49a0ce189fc27c0741f6739f82b29e765b835de17f2', // Big tank on the side 'e39746a8d819f863a92ef37edc1b5d99e89d2e990c1a5951adfe9835f90de34c', // 2 Pipe in the middle - '1db4e31c8f68acee9ff62a098a103cd49e5cea0320d7aed8aa345e99c6b2663d', // 3 Pipe in the middle + '1db4e31c8f68acee9ff62a098a103cd49e5cea0320d7aed8aa345e99c6b2663d' // 3 Pipe in the middle ], style: { cad: { @@ -113,33 +112,9 @@ export const Main: Story = { } ], styling: { - defaultStyle: { - cad: { - color: new Color('yellow') - }, - pointcloud: { - color: new Color('blue') - } - }, - groups: [ - { - fdmAssetExternalIds: [ - '23de4d93f9f482f307272f4924b83bd9cdc71e33e06003c7ec0b540135e13c24', // Rotating crane - '783fe42d9b24229e1873a49a0ce189fc27c0741f6739f82b29e765b835de17f2', // Big tank on the side - 'ca020a82b244eed433ca598a7410169fc21543d6192eebd74fba70a5af984db7', // 1 Pipe in the middle - 'e39746a8d819f863a92ef37edc1b5d99e89d2e990c1a5951adfe9835f90de34c', // 2 Pipe in the middle - '1db4e31c8f68acee9ff62a098a103cd49e5cea0320d7aed8aa345e99c6b2663d', // 3 Pipe in the middle - ], - style: { - cad: { - color: new Color('red') - } - } - } - ] } }, - render: ({ resources, styling}) => ( + render: ({ resources, styling }) => ( Date: Wed, 12 Jul 2023 11:51:12 +0200 Subject: [PATCH 15/22] Added dev tools --- .../src/components/RevealContainer/RevealContainer.tsx | 3 +++ .../src/components/RevealContainer/SDKProvider.tsx | 2 +- react-components/stories/Reveal3DResources.stories.tsx | 5 +++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/react-components/src/components/RevealContainer/RevealContainer.tsx b/react-components/src/components/RevealContainer/RevealContainer.tsx index 2f7b2ae3944..ea0229cf6db 100644 --- a/react-components/src/components/RevealContainer/RevealContainer.tsx +++ b/react-components/src/components/RevealContainer/RevealContainer.tsx @@ -13,6 +13,7 @@ import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; type RevealContainerProps = { color?: Color; sdk: CogniteClient; + uiElements?: ReactNode; children?: ReactNode; viewerOptions?: Pick< Cognite3DViewerOptions, @@ -31,6 +32,7 @@ const queryClient = new QueryClient(); export function RevealContainer({ children, sdk, + uiElements, color, viewerOptions }: RevealContainerProps): ReactElement { @@ -48,6 +50,7 @@ export function RevealContainer({
{mountChildren()}
+ {uiElements}
); diff --git a/react-components/src/components/RevealContainer/SDKProvider.tsx b/react-components/src/components/RevealContainer/SDKProvider.tsx index 2e28fde5a45..5308c4d88cd 100644 --- a/react-components/src/components/RevealContainer/SDKProvider.tsx +++ b/react-components/src/components/RevealContainer/SDKProvider.tsx @@ -3,7 +3,7 @@ */ import React, { useContext, createContext, useMemo } from 'react'; import { type CogniteClient } from '@cognite/sdk'; -import { FdmSDK } from '../../utilities/FdmSdk'; +import { FdmSDK } from '../../utilities/FdmSDK'; const SdkContext = createContext(null); const FdmSdkContext = createContext(null); diff --git a/react-components/stories/Reveal3DResources.stories.tsx b/react-components/stories/Reveal3DResources.stories.tsx index 23df119e45c..c693d8b5543 100644 --- a/react-components/stories/Reveal3DResources.stories.tsx +++ b/react-components/stories/Reveal3DResources.stories.tsx @@ -6,6 +6,7 @@ import { Reveal3DResources, RevealContainer } from '../src'; import { Color, Matrix4 } from 'three'; import { CameraController } from '../src/'; import { createSdkByUrlToken } from './utilities/createSdkByUrlToken'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; const meta = { title: 'Example/Reveal3DResources', @@ -111,13 +112,13 @@ export const Main: Story = { transform: new Matrix4().makeTranslation(-340, -480, 80) } ], - styling: { - } + styling: {} }, render: ({ resources, styling }) => ( } viewerOptions={{ loadingIndicatorStyle: { opacity: 1, From f46bf0edb3ddeb2a7d8f2d6d30ee9cc39d9f2d62 Mon Sep 17 00:00:00 2001 From: Savelii Novikov Date: Wed, 12 Jul 2023 12:30:02 +0200 Subject: [PATCH 16/22] Added FDM config --- .../Reveal3DResources/Reveal3DResources.tsx | 18 +++++++---------- .../src/hooks/useFdmAssetMappings.tsx | 20 +++++++++++++++++-- react-components/src/index.ts | 18 ++++++++++++++--- .../stories/Reveal3DResources.stories.tsx | 19 +++++++++++++++--- 4 files changed, 56 insertions(+), 19 deletions(-) diff --git a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx index e37e8e69575..63eaaf0e25f 100644 --- a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx +++ b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx @@ -40,10 +40,15 @@ export type Reveal3DResourcesStyling = { export type Reveal3DResourcesProps = { resources: AddResourceOptions[]; + fdmAssetMappingConfig?: FdmAssetMappingsConfig; styling?: Reveal3DResourcesStyling; }; -export const Reveal3DResources = ({ resources, styling }: Reveal3DResourcesProps): ReactElement => { +export const Reveal3DResources = ({ + resources, + styling, + fdmAssetMappingConfig +}: Reveal3DResourcesProps): ReactElement => { const [reveal3DModels, setReveal3DModels] = useState([]); const [reveal3DModelsStyling, setReveal3DModelsStyling] = useState< Array @@ -57,17 +62,8 @@ export const Reveal3DResources = ({ resources, styling }: Reveal3DResourcesProps () => styling?.groups?.flatMap((group) => group.fdmAssetExternalIds) ?? [], [styling] ); - const fdmAssetMappingSource: FdmAssetMappingsConfig = { - source: { - space: 'fdm-3d-test-savelii', - version: '1', - type: 'view', - externalId: 'CDF_3D_Connection_Data' - }, - assetFdmSpace: 'bark-corporation' - }; - const { data: mappings } = useFdmAssetMappings(stylingExternalIds, fdmAssetMappingSource); + const { data: mappings } = useFdmAssetMappings(stylingExternalIds, fdmAssetMappingConfig); useEffect(() => { getTypedModels(resources, viewer).then(setReveal3DModels).catch(console.error); diff --git a/react-components/src/hooks/useFdmAssetMappings.tsx b/react-components/src/hooks/useFdmAssetMappings.tsx index 95ddf25eb3c..b30df667192 100644 --- a/react-components/src/hooks/useFdmAssetMappings.tsx +++ b/react-components/src/hooks/useFdmAssetMappings.tsx @@ -3,11 +3,17 @@ */ import { type CogniteExternalId } from '@cognite/sdk'; import { useFdmSdk } from '../components/RevealContainer/SDKProvider'; -import { type Source } from '../utilities/FdmSdk'; +import { type Source } from '../utilities/FdmSDK'; import { type UseQueryResult, useQuery } from '@tanstack/react-query'; export type FdmAssetMappingsConfig = { + /** + * 3D Data model source + */ source: Source; + /* + * FDM space where model assets are located + */ assetFdmSpace: string; }; @@ -17,12 +23,22 @@ export type ThreeDModelMappings = { mappings: Array<{ nodeId: number; externalId: string }>; }; +const DefaultFdmConfig: FdmAssetMappingsConfig = { + source: { + space: 'fdm-3d-test-savelii', + version: '1', + type: 'view', + externalId: 'CDF_3D_Connection_Data' + }, + assetFdmSpace: 'bark-corporation' +}; + /** * This hook fetches the list of FDM asset mappings for the given external ids */ export const useFdmAssetMappings = ( fdmAssetExternalIds: CogniteExternalId[], - fdmConfig: FdmAssetMappingsConfig + fdmConfig: FdmAssetMappingsConfig = DefaultFdmConfig ): UseQueryResult => { const fdmSdk = useFdmSdk(); diff --git a/react-components/src/index.ts b/react-components/src/index.ts index 5e90ed3340a..557d2895c62 100644 --- a/react-components/src/index.ts +++ b/react-components/src/index.ts @@ -3,13 +3,24 @@ */ import '@cognite/cogs.js/dist/cogs.css'; export { RevealContainer } from './components/RevealContainer/RevealContainer'; -export { PointCloudContainer } from './components/PointCloudContainer/PointCloudContainer'; -export { CadModelContainer } from './components/CadModelContainer/CadModelContainer'; +export { + PointCloudContainer, + type PointCloudModelStyling, + type AnnotationIdStylingGroup +} from './components/PointCloudContainer/PointCloudContainer'; +export { + CadModelContainer, + type CadModelStyling, + type TreeIndexStylingGroup, + type NodeStylingGroup +} from './components/CadModelContainer/CadModelContainer'; export { Image360CollectionContainer } from './components/Image360CollectionContainer/Image360CollectionContainer'; export { Image360HistoricalDetails } from './components/Image360HistoricalDetails/Image360HistoricalDetails'; export { Reveal3DResources, - type Reveal3DResourcesProps + type Reveal3DResourcesProps, + type Reveal3DResourcesStyling, + type FdmAssetStylingGroup } from './components/Reveal3DResources/Reveal3DResources'; export { CameraController } from './components/CameraController/CameraController'; export type { @@ -18,3 +29,4 @@ export type { AddReveal3DModelOptions } from './components/Reveal3DResources/types'; export { RevealToolbar } from './components/RevealToolbar/RevealToolbar'; +export { useFdmAssetMappings, type FdmAssetMappingsConfig } from './hooks/useFdmAssetMappings'; diff --git a/react-components/stories/Reveal3DResources.stories.tsx b/react-components/stories/Reveal3DResources.stories.tsx index c693d8b5543..019f2dcc04e 100644 --- a/react-components/stories/Reveal3DResources.stories.tsx +++ b/react-components/stories/Reveal3DResources.stories.tsx @@ -112,9 +112,18 @@ export const Main: Story = { transform: new Matrix4().makeTranslation(-340, -480, 80) } ], - styling: {} + styling: {}, + fdmAssetMappingConfig: { + source: { + space: 'fdm-3d-test-savelii', + version: '1', + type: 'view', + externalId: 'CDF_3D_Connection_Data' + }, + assetFdmSpace: 'bark-corporation' + } }, - render: ({ resources, styling }) => ( + render: ({ resources, styling, fdmAssetMappingConfig }) => ( - + Date: Wed, 12 Jul 2023 13:06:46 +0200 Subject: [PATCH 17/22] Fixed yarn.lock --- react-components/yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-components/yarn.lock b/react-components/yarn.lock index 70694190b69..4017925f632 100644 --- a/react-components/yarn.lock +++ b/react-components/yarn.lock @@ -6819,7 +6819,7 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.25.1, core-js-compat@npm:^3.30.1, core-js-compat@npm:^3.30.2": +"core-js-compat@npm:^3.25.1": version: 3.30.2 resolution: "core-js-compat@npm:3.30.2" dependencies: From bc8ef9fde442beeb16335254909cb60feb04c511 Mon Sep 17 00:00:00 2001 From: Savelii Novikov Date: Wed, 12 Jul 2023 13:07:20 +0200 Subject: [PATCH 18/22] Examples lint --- examples/src/pages/Viewer.tsx | 42 ++++++++++--------- .../src/utils/PointCloudObjectStylingUI.ts | 6 ++- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/examples/src/pages/Viewer.tsx b/examples/src/pages/Viewer.tsx index ca09506e9ea..f65325992e4 100644 --- a/examples/src/pages/Viewer.tsx +++ b/examples/src/pages/Viewer.tsx @@ -474,29 +474,33 @@ export function Viewer() { sphere.position.copy(point); viewer.addObject3D(sphere); - + if (pointCloudObjectsUi.createAnnotationsOnClick) { 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, + 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); } diff --git a/examples/src/utils/PointCloudObjectStylingUI.ts b/examples/src/utils/PointCloudObjectStylingUI.ts index e80c551ebd7..bcf98613187 100644 --- a/examples/src/utils/PointCloudObjectStylingUI.ts +++ b/examples/src/utils/PointCloudObjectStylingUI.ts @@ -60,7 +60,9 @@ export class PointCloudObjectStylingUI { const actions = { deleteAllAnnotations: async () => { - const annotations = await client.annotations.list({ filter: { annotatedResourceIds: [{ id: model.modelId }], annotatedResourceType: 'threedmodel' } }).autoPagingToArray({ limit: 1000 }); + 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 }))); @@ -84,7 +86,7 @@ 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 colors for objects'); From 5aee57ed7f96bdebed84da7fb4d90d08cc62bdec Mon Sep 17 00:00:00 2001 From: Savelii Novikov Date: Wed, 12 Jul 2023 16:44:17 +0200 Subject: [PATCH 19/22] Refactored annotation creation --- examples/src/pages/Viewer.tsx | 30 +----------------- .../src/utils/PointCloudObjectStylingUI.ts | 31 +++++++++++++++++-- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/examples/src/pages/Viewer.tsx b/examples/src/pages/Viewer.tsx index f65325992e4..8ce60c90795 100644 --- a/examples/src/pages/Viewer.tsx +++ b/examples/src/pages/Viewer.tsx @@ -475,35 +475,7 @@ export function Viewer() { sphere.position.copy(point); viewer.addObject3D(sphere); - if (pointCloudObjectsUi.createAnnotationsOnClick) { - 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); - } + pointCloudObjectsUi.createModelAnnotation(point, model); } } break; diff --git a/examples/src/utils/PointCloudObjectStylingUI.ts b/examples/src/utils/PointCloudObjectStylingUI.ts index bcf98613187..bb5ffd97493 100644 --- a/examples/src/utils/PointCloudObjectStylingUI.ts +++ b/examples/src/utils/PointCloudObjectStylingUI.ts @@ -100,8 +100,35 @@ export class PointCloudObjectStylingUI { .onChange((value: boolean) => (this._createAnnotationsOnClick = value)); } - get createAnnotationsOnClick() { - return this._createAnnotationsOnClick; + async createModelAnnotation(position: THREE.Vector3, model: CognitePointCloudModel) { + if (!this._createAnnotationsOnClick) return; + + const cdfPosition = position.clone().applyMatrix4(model.getCdfToDefaultModelTransformation().invert()); + + const annotation = await this._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); } async updateSelectedAnnotation(annotationId: number | undefined) { From edd14200e4fb90f55b4c48edd0b931b3109bcd3d Mon Sep 17 00:00:00 2001 From: Savelii Novikov Date: Wed, 12 Jul 2023 16:55:17 +0200 Subject: [PATCH 20/22] Added types for hooks --- .../Reveal3DResources/Reveal3DResources.tsx | 3 ++- react-components/src/hooks/types.ts | 18 ++++++++++++++++++ .../src/hooks/useFdmAssetMappings.tsx | 19 +------------------ react-components/src/index.ts | 3 ++- 4 files changed, 23 insertions(+), 20 deletions(-) create mode 100644 react-components/src/hooks/types.ts diff --git a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx index 63eaaf0e25f..86124e11a5a 100644 --- a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx +++ b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx @@ -26,7 +26,8 @@ import { type AddResourceOptions } from './types'; import { type CogniteExternalId } from '@cognite/sdk'; -import { type FdmAssetMappingsConfig, useFdmAssetMappings } from '../../hooks/useFdmAssetMappings'; +import { useFdmAssetMappings } from '../../hooks/useFdmAssetMappings'; +import { type FdmAssetMappingsConfig } from '../../hooks/types'; export type FdmAssetStylingGroup = { fdmAssetExternalIds: CogniteExternalId[]; diff --git a/react-components/src/hooks/types.ts b/react-components/src/hooks/types.ts new file mode 100644 index 00000000000..a416fc55eb5 --- /dev/null +++ b/react-components/src/hooks/types.ts @@ -0,0 +1,18 @@ +import { type Source } from '../utilities/FdmSDK'; + +export type FdmAssetMappingsConfig = { + /** + * 3D Data model source + */ + source: Source; + /* + * FDM space where model assets are located + */ + assetFdmSpace: string; + }; + + export type ThreeDModelMappings = { + modelId: number; + revisionId: number; + mappings: Array<{ nodeId: number; externalId: string }>; + }; \ No newline at end of file diff --git a/react-components/src/hooks/useFdmAssetMappings.tsx b/react-components/src/hooks/useFdmAssetMappings.tsx index b30df667192..c3d6dd3c53c 100644 --- a/react-components/src/hooks/useFdmAssetMappings.tsx +++ b/react-components/src/hooks/useFdmAssetMappings.tsx @@ -3,25 +3,8 @@ */ import { type CogniteExternalId } from '@cognite/sdk'; import { useFdmSdk } from '../components/RevealContainer/SDKProvider'; -import { type Source } from '../utilities/FdmSDK'; import { type UseQueryResult, useQuery } from '@tanstack/react-query'; - -export type FdmAssetMappingsConfig = { - /** - * 3D Data model source - */ - source: Source; - /* - * FDM space where model assets are located - */ - assetFdmSpace: string; -}; - -export type ThreeDModelMappings = { - modelId: number; - revisionId: number; - mappings: Array<{ nodeId: number; externalId: string }>; -}; +import { type FdmAssetMappingsConfig, type ThreeDModelMappings } from './types'; const DefaultFdmConfig: FdmAssetMappingsConfig = { source: { diff --git a/react-components/src/index.ts b/react-components/src/index.ts index 557d2895c62..855a901fa2a 100644 --- a/react-components/src/index.ts +++ b/react-components/src/index.ts @@ -29,4 +29,5 @@ export type { AddReveal3DModelOptions } from './components/Reveal3DResources/types'; export { RevealToolbar } from './components/RevealToolbar/RevealToolbar'; -export { useFdmAssetMappings, type FdmAssetMappingsConfig } from './hooks/useFdmAssetMappings'; +export { useFdmAssetMappings } from './hooks/useFdmAssetMappings'; +export { type FdmAssetMappingsConfig } from './hooks/types'; From 11fde8ee29717cd0f482df6471808b087d15a0a0 Mon Sep 17 00:00:00 2001 From: Savelii Novikov Date: Wed, 12 Jul 2023 16:58:28 +0200 Subject: [PATCH 21/22] Added staleTime constant --- react-components/src/hooks/useFdmAssetMappings.tsx | 3 ++- react-components/src/utilities/constants.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 react-components/src/utilities/constants.ts diff --git a/react-components/src/hooks/useFdmAssetMappings.tsx b/react-components/src/hooks/useFdmAssetMappings.tsx index c3d6dd3c53c..123b274f00d 100644 --- a/react-components/src/hooks/useFdmAssetMappings.tsx +++ b/react-components/src/hooks/useFdmAssetMappings.tsx @@ -5,6 +5,7 @@ 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 { DEFAULT_QUERY_STALE_TIME } from '../utilities/constants'; const DefaultFdmConfig: FdmAssetMappingsConfig = { source: { @@ -83,6 +84,6 @@ export const useFdmAssetMappings = ( return modelMappingsTemp; }, - { staleTime: 600000 } + { staleTime: DEFAULT_QUERY_STALE_TIME } ); }; diff --git a/react-components/src/utilities/constants.ts b/react-components/src/utilities/constants.ts new file mode 100644 index 00000000000..2ce263bd7a9 --- /dev/null +++ b/react-components/src/utilities/constants.ts @@ -0,0 +1 @@ +export const DEFAULT_QUERY_STALE_TIME = 1000 * 60 * 10 ; // 10 minutes \ No newline at end of file From 69b0165f0dfd4ad30ca93424567522fc2f45b3fd Mon Sep 17 00:00:00 2001 From: Savelii Novikov Date: Wed, 12 Jul 2023 17:26:12 +0200 Subject: [PATCH 22/22] Styling extracted as a hook --- .../Reveal3DResources/Reveal3DResources.tsx | 77 ++------------- react-components/src/hooks/types.ts | 33 ++++--- .../src/hooks/useCalculateModelsStyling.tsx | 95 +++++++++++++++++++ react-components/src/utilities/FdmSDK.ts | 14 --- react-components/src/utilities/constants.ts | 5 +- 5 files changed, 123 insertions(+), 101 deletions(-) create mode 100644 react-components/src/hooks/useCalculateModelsStyling.tsx diff --git a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx index 86124e11a5a..30a1b329f07 100644 --- a/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx +++ b/react-components/src/components/Reveal3DResources/Reveal3DResources.tsx @@ -1,18 +1,14 @@ /*! * Copyright 2023 Cognite AS */ -import { useRef, type ReactElement, useContext, useState, useEffect, useMemo } from 'react'; +import { useRef, type ReactElement, useContext, useState, useEffect } from 'react'; import { type NodeAppearance, type Cognite3DViewer, type PointCloudAppearance } from '@cognite/reveal'; import { ModelsLoadingStateContext } from './ModelsLoadingContext'; -import { - CadModelContainer, - type CadModelStyling, - type NodeStylingGroup -} from '../CadModelContainer/CadModelContainer'; +import { CadModelContainer, type CadModelStyling } from '../CadModelContainer/CadModelContainer'; import { PointCloudContainer, type PointCloudModelStyling @@ -26,8 +22,8 @@ import { type AddResourceOptions } from './types'; import { type CogniteExternalId } from '@cognite/sdk'; -import { useFdmAssetMappings } from '../../hooks/useFdmAssetMappings'; import { type FdmAssetMappingsConfig } from '../../hooks/types'; +import { useCalculateModelsStyling } from '../../hooks/useCalculateModelsStyling'; export type FdmAssetStylingGroup = { fdmAssetExternalIds: CogniteExternalId[]; @@ -59,76 +55,15 @@ export const Reveal3DResources = ({ const viewer = useReveal(); const numModelsLoaded = useRef(0); - const stylingExternalIds = useMemo( - () => styling?.groups?.flatMap((group) => group.fdmAssetExternalIds) ?? [], - [styling] - ); - - const { data: mappings } = useFdmAssetMappings(stylingExternalIds, fdmAssetMappingConfig); - useEffect(() => { getTypedModels(resources, viewer).then(setReveal3DModels).catch(console.error); }, [resources, viewer]); - useEffect(() => { - if (styling === undefined || reveal3DModels === undefined) return; - - const modelsStyling = reveal3DModels.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 - }; - break; - } - case 'pointcloud': { - modelStyling = { - defaultStyle: styling.defaultStyle?.pointcloud - }; - break; - } - default: { - modelStyling = {}; - console.warn(`Unknown model type: ${model.type}`); - break; - } - } - return modelStyling; - }); + const modelsStyling = useCalculateModelsStyling(reveal3DModels, styling, fdmAssetMappingConfig); + useEffect(() => { setReveal3DModelsStyling(modelsStyling); - }, [mappings, styling, reveal3DModels, mappings]); + }, [modelsStyling]); const image360CollectionAddOptions = resources.filter( (resource): resource is AddImageCollection360Options => diff --git a/react-components/src/hooks/types.ts b/react-components/src/hooks/types.ts index a416fc55eb5..feb77073956 100644 --- a/react-components/src/hooks/types.ts +++ b/react-components/src/hooks/types.ts @@ -1,18 +1,21 @@ +/*! + * Copyright 2023 Cognite AS + */ import { type Source } from '../utilities/FdmSDK'; export type FdmAssetMappingsConfig = { - /** - * 3D Data model source - */ - source: Source; - /* - * FDM space where model assets are located - */ - assetFdmSpace: string; - }; - - export type ThreeDModelMappings = { - modelId: number; - revisionId: number; - mappings: Array<{ nodeId: number; externalId: string }>; - }; \ No newline at end of file + /** + * 3D Data model source + */ + source: Source; + /* + * FDM space where model assets are located + */ + assetFdmSpace: string; +}; + +export type ThreeDModelMappings = { + modelId: number; + revisionId: number; + mappings: Array<{ nodeId: number; externalId: string }>; +}; diff --git a/react-components/src/hooks/useCalculateModelsStyling.tsx b/react-components/src/hooks/useCalculateModelsStyling.tsx new file mode 100644 index 00000000000..9fd61fc668d --- /dev/null +++ b/react-components/src/hooks/useCalculateModelsStyling.tsx @@ -0,0 +1,95 @@ +/*! + * Copyright 2023 Cognite AS + */ +import { useMemo } from 'react'; +import { 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'; + +/** + * Calculates the styling for the models based on the styling configuration and the mappings. + * @param models Models to calculate styling for. + * @param styling Styling configuration. + * @param fdmAssetMappingConfig Configuration for the FDM asset mappings. + * @returns + */ +export const useCalculateModelsStyling = ( + models?: TypedReveal3DModel[], + styling?: Reveal3DResourcesStyling, + fdmAssetMappingConfig?: FdmAssetMappingsConfig +): Array => { + const stylingExternalIds = useMemo( + () => styling?.groups?.flatMap((group) => group.fdmAssetExternalIds) ?? [], + [styling] + ); + + const { data: mappings } = useFdmAssetMappings(stylingExternalIds, fdmAssetMappingConfig); + + const modelsStyling = useMemo(() => { + if (styling === undefined || models === undefined) return []; + + 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 + }; + break; + } + case 'pointcloud': { + modelStyling = { + defaultStyle: styling.defaultStyle?.pointcloud + }; + break; + } + default: { + modelStyling = {}; + console.warn(`Unknown model type: ${model.type}`); + break; + } + } + return modelStyling; + }); + + return internalModelsStyling; + }, [mappings, styling, models, mappings]); + + return modelsStyling; +}; diff --git a/react-components/src/utilities/FdmSDK.ts b/react-components/src/utilities/FdmSDK.ts index 44197ad3d89..edfd866b161 100644 --- a/react-components/src/utilities/FdmSDK.ts +++ b/react-components/src/utilities/FdmSDK.ts @@ -41,20 +41,6 @@ export class FdmSDK { this._sdk = sdk; } - public async getInstancesByExternalIds>( - items: Item[], - source: Source - ): Promise { - const result = await this._sdk.post(this._byIdsEndpoint, { - data: { items, sources: [{ source }] } - }); - - if (result.status === 200) { - return result.data.items; - } - throw new Error(`Failed to fetch instances. Status: ${result.status}`); - } - public async filterInstances>( filter: any, instanceType: InstanceType, diff --git a/react-components/src/utilities/constants.ts b/react-components/src/utilities/constants.ts index 2ce263bd7a9..142f2605c02 100644 --- a/react-components/src/utilities/constants.ts +++ b/react-components/src/utilities/constants.ts @@ -1 +1,4 @@ -export const DEFAULT_QUERY_STALE_TIME = 1000 * 60 * 10 ; // 10 minutes \ No newline at end of file +/*! + * Copyright 2023 Cognite AS + */ +export const DEFAULT_QUERY_STALE_TIME = 1000 * 60 * 10; // 10 minutes