-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(react-components): new hook for getting scenes data from FDM (#4013
- Loading branch information
Showing
6 changed files
with
331 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
/*! | ||
* Copyright 2023 Cognite AS | ||
*/ | ||
|
||
import { type QueryFunction, useQuery, type UseQueryResult } from '@tanstack/react-query'; | ||
import { useSDK } from '../components/RevealContainer/SDKProvider'; | ||
import { type CogniteClient } from '@cognite/sdk'; | ||
import { useMemo } from 'react'; | ||
import { type EdgeItem, FdmSDK, type Query } from '../utilities/FdmSDK'; | ||
import { type AddReveal3DModelOptions } from '..'; | ||
import { type Cdf3dRevisionProperties } from './types'; | ||
import { Euler, Matrix4, Quaternion, Vector3 } from 'three'; | ||
import { CDF_TO_VIEWER_TRANSFORMATION } from '@cognite/reveal'; | ||
|
||
export const use3dScenes = ( | ||
userSdk?: CogniteClient | ||
): UseQueryResult<Record<string, AddReveal3DModelOptions[]>> => { | ||
const sdk = useSDK(userSdk); | ||
|
||
const fdmSdk = useMemo(() => new FdmSDK(sdk), [sdk]); | ||
|
||
const queryFunction: QueryFunction<Record<string, AddReveal3DModelOptions[]>> = async () => { | ||
const scenesQuery = createGetScenesQuery(); | ||
|
||
try { | ||
const scenesQueryResult = await fdmSdk.queryNodesAndEdges(scenesQuery); | ||
|
||
const scenesMap: Record<string, AddReveal3DModelOptions[]> = | ||
scenesQueryResult.items.sceneModels.reduce( | ||
(acc, item) => { | ||
const edge = item as EdgeItem; | ||
|
||
const { externalId } = edge.startNode; | ||
|
||
const properties = Object.values( | ||
Object.values(edge.properties)[0] as Record<string, unknown> | ||
)[0] as Cdf3dRevisionProperties; | ||
const sceneModels = acc[externalId]; | ||
|
||
const newModelId = Number(edge.endNode.externalId); | ||
const newModelRevisionId = Number(properties?.revisionId); | ||
|
||
if (isNaN(newModelId) || isNaN(newModelRevisionId)) { | ||
return acc; | ||
} | ||
|
||
const newModel = { | ||
modelId: newModelId, | ||
revisionId: newModelRevisionId, | ||
transform: new Matrix4() | ||
.compose( | ||
new Vector3( | ||
properties.translationX, | ||
properties.translationY, | ||
properties.translationZ | ||
), | ||
new Quaternion().setFromEuler( | ||
new Euler( | ||
properties.eulerRotationX, | ||
properties.eulerRotationY, | ||
properties.eulerRotationZ | ||
) | ||
), | ||
new Vector3(properties.scaleX, properties.scaleY, properties.scaleZ) | ||
) | ||
.premultiply(CDF_TO_VIEWER_TRANSFORMATION) | ||
}; | ||
|
||
if (sceneModels !== undefined) { | ||
sceneModels.push(newModel); | ||
} else { | ||
acc[externalId] = [newModel]; | ||
} | ||
|
||
return acc; | ||
}, | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
{} as Record<string, AddReveal3DModelOptions[]> | ||
); | ||
|
||
return scenesMap; | ||
} catch (error) { | ||
console.warn("Scene space doesn't exist or has no scenes with 3D models"); | ||
return {}; | ||
} | ||
}; | ||
|
||
return useQuery<Record<string, AddReveal3DModelOptions[]>>( | ||
['cdf', '3d', 'scenes'], | ||
queryFunction | ||
); | ||
}; | ||
|
||
function createGetScenesQuery(limit: number = 100): Query { | ||
return { | ||
with: { | ||
scenes: { | ||
nodes: { | ||
filter: { | ||
hasData: [ | ||
{ | ||
type: 'view', | ||
space: 'scene_space', | ||
externalId: 'SceneConfiguration', | ||
version: 'v4' | ||
} | ||
] | ||
} | ||
}, | ||
limit | ||
}, | ||
sceneModels: { | ||
edges: { | ||
from: 'scenes', | ||
maxDistance: 1, | ||
direction: 'outwards', | ||
filter: { | ||
equals: { | ||
property: ['edge', 'type'], | ||
value: { | ||
space: 'scene_space', | ||
externalId: 'SceneConfiguration.cdf3dModels' | ||
} | ||
} | ||
} | ||
}, | ||
limit | ||
} | ||
}, | ||
select: { | ||
scenes: { | ||
sources: [ | ||
{ | ||
source: { | ||
type: 'view', | ||
space: 'scene_space', | ||
externalId: 'SceneConfiguration', | ||
version: 'v4' | ||
}, | ||
properties: ['*'] | ||
} | ||
] | ||
}, | ||
sceneModels: { | ||
sources: [ | ||
{ | ||
source: { | ||
type: 'view', | ||
space: 'scene_space', | ||
externalId: 'Cdf3dRevisionProperties', | ||
version: '2190c9b6f5cb82' | ||
}, | ||
properties: ['*'] | ||
} | ||
] | ||
} | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
/*! | ||
* Copyright 2023 Cognite AS | ||
*/ | ||
|
||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { | ||
CadModelContainer, | ||
RevealToolbar, | ||
withSuppressRevealEvents, | ||
withCameraStateUrlParam, | ||
useGetCameraStateFromUrlParam, | ||
useCameraNavigation, | ||
Reveal3DResources, | ||
type AddReveal3DModelOptions | ||
} from '../src'; | ||
import { Color } from 'three'; | ||
import styled from 'styled-components'; | ||
import { Button, Dropdown, Menu, ToolBar } from '@cognite/cogs.js'; | ||
import { type ReactElement, useEffect, useState } from 'react'; | ||
import { signalStoryReadyForScreenshot } from './utilities/signalStoryReadyForScreenshot'; | ||
import { RevealStoryContainer } from './utilities/RevealStoryContainer'; | ||
import { getAddModelOptionsFromUrl } from './utilities/getAddModelOptionsFromUrl'; | ||
import { use3dScenes } from '../src'; | ||
|
||
const meta = { | ||
title: 'Example/SceneSelector', | ||
component: CadModelContainer, | ||
tags: ['autodocs'] | ||
} satisfies Meta<typeof CadModelContainer>; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
const MyCustomToolbar = styled(withSuppressRevealEvents(withCameraStateUrlParam(ToolBar)))` | ||
position: absolute; | ||
left: 20px; | ||
top: 70px; | ||
`; | ||
|
||
const SceneButton = ({ | ||
setResources | ||
}: { | ||
setResources: (value: AddReveal3DModelOptions[]) => void; | ||
}): ReactElement => { | ||
const { data } = use3dScenes(); | ||
const [selectedScene, setSelectedScene] = useState(Object.keys(data ?? {})[0]); | ||
|
||
useEffect(() => { | ||
if (data === undefined) return; | ||
|
||
setResources(data[selectedScene]); | ||
|
||
return () => { | ||
setResources([]); | ||
}; | ||
}, [selectedScene]); | ||
|
||
return ( | ||
<Dropdown | ||
placement="right-start" | ||
content={ | ||
<Menu> | ||
<Menu.Header>Select 3D location</Menu.Header> | ||
{Object.keys(data ?? {}).map((scene) => { | ||
if (data === undefined) return <></>; | ||
|
||
return ( | ||
<Menu.Item | ||
key={scene} | ||
toggled={selectedScene === scene} | ||
onClick={() => { | ||
setSelectedScene(scene); | ||
}}> | ||
{scene} | ||
</Menu.Item> | ||
); | ||
})} | ||
</Menu> | ||
}> | ||
<Button icon="World" type="ghost" /> | ||
</Dropdown> | ||
); | ||
}; | ||
|
||
export const Main: Story = { | ||
args: { | ||
addModelOptions: getAddModelOptionsFromUrl('/primitives') | ||
}, | ||
render: () => { | ||
const [resources, setResources] = useState<AddReveal3DModelOptions[]>([]); | ||
|
||
return ( | ||
<RevealStoryContainer color={new Color(0x4a4a4a)}> | ||
<FitToUrlCameraState /> | ||
<MyCustomToolbar> | ||
<SceneButton setResources={setResources} /> | ||
<RevealToolbar.FitModelsButton /> | ||
<RevealToolbar.SlicerButton /> | ||
</MyCustomToolbar> | ||
<Reveal3DResources resources={resources} /> | ||
</RevealStoryContainer> | ||
); | ||
} | ||
}; | ||
|
||
function FitToUrlCameraState(): ReactElement { | ||
const getCameraState = useGetCameraStateFromUrlParam(); | ||
const cameraNavigation = useCameraNavigation(); | ||
|
||
useEffect(() => { | ||
signalStoryReadyForScreenshot(); | ||
const currentCameraState = getCameraState(); | ||
if (currentCameraState === undefined) return; | ||
cameraNavigation.fitCameraToState(currentCameraState); | ||
}, []); | ||
|
||
return <></>; | ||
} |