251 lines
8.1 KiB
TypeScript
251 lines
8.1 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import type { DMInstanceRef } from '@cognite/reveal';
|
|
import type { ViewDefinition, ViewReference } from '@cognite/sdk';
|
|
import { useRevealContext } from './useRevealContext';
|
|
import {
|
|
COGNITE_VISUALIZABLE_VIEW,
|
|
COGNITE_3D_OBJECT_VIEW,
|
|
} from '../utils/views';
|
|
|
|
type DmsUniqueIdentifier = {
|
|
space: string;
|
|
externalId: string;
|
|
};
|
|
|
|
export function use3dRelatedEdgeConnections(instance: DMInstanceRef) {
|
|
const { sdk } = useRevealContext();
|
|
return useQuery({
|
|
queryKey: ['3d-related-edges', instance.space, instance.externalId],
|
|
queryFn: async (): Promise<DMInstanceRef[]> => {
|
|
try {
|
|
// Query for nodes connected via edges that have 3D data
|
|
const response = await sdk.instances.query({
|
|
with: {
|
|
start_instance: {
|
|
nodes: {
|
|
filter: {
|
|
and: [
|
|
{
|
|
equals: {
|
|
property: ['node', 'externalId'],
|
|
value: instance.externalId,
|
|
},
|
|
},
|
|
{
|
|
equals: {
|
|
property: ['node', 'space'],
|
|
value: instance.space,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
limit: 1,
|
|
},
|
|
start_to_object_edges: {
|
|
edges: {
|
|
from: 'start_instance',
|
|
maxDistance: 1,
|
|
direction: 'outwards',
|
|
},
|
|
limit: 1000,
|
|
},
|
|
objects_connected_with_3d: {
|
|
nodes: {
|
|
from: 'start_to_object_edges',
|
|
chainTo: 'destination',
|
|
filter: {
|
|
exists: {
|
|
property: [
|
|
COGNITE_VISUALIZABLE_VIEW.space,
|
|
COGNITE_VISUALIZABLE_VIEW.externalId,
|
|
'object3D',
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
object_3ds: {
|
|
nodes: {
|
|
from: 'objects_connected_with_3d',
|
|
through: {
|
|
view: { type: 'view', ...COGNITE_VISUALIZABLE_VIEW },
|
|
identifier: 'object3D',
|
|
},
|
|
},
|
|
limit: 1000,
|
|
},
|
|
},
|
|
select: {
|
|
start_instance: {},
|
|
start_to_object_edges: {},
|
|
objects_connected_with_3d: {},
|
|
object_3ds: {},
|
|
},
|
|
});
|
|
|
|
// Return the instances that are connected to 3D objects
|
|
return (
|
|
response.items?.objects_connected_with_3d?.map((node) => ({
|
|
space: node.space,
|
|
externalId: node.externalId,
|
|
})) ?? []
|
|
);
|
|
} catch (error) {
|
|
console.error('Error fetching related edge connections:', error);
|
|
return [];
|
|
}
|
|
},
|
|
enabled: !!sdk,
|
|
});
|
|
}
|
|
|
|
export function use3dRelatedDirectConnections(instance: DMInstanceRef) {
|
|
const { sdk } = useRevealContext();
|
|
return useQuery({
|
|
queryKey: ['3d-related-direct', instance.space, instance.externalId],
|
|
queryFn: async (): Promise<DMInstanceRef[]> => {
|
|
try {
|
|
// Step 1: Inspect the instance to find its views
|
|
const views = await sdk.instances.inspect({
|
|
inspectionOperations: { involvedViews: {} },
|
|
items: [
|
|
{
|
|
instanceType: 'node',
|
|
externalId: instance.externalId,
|
|
space: instance.space,
|
|
},
|
|
],
|
|
});
|
|
const view = views.items[0]?.inspectionResults?.involvedViews?.[0];
|
|
|
|
// Step 2: Get the instance content with its views
|
|
const instanceResponse = await sdk.instances.retrieve({
|
|
items: [
|
|
{
|
|
instanceType: 'node',
|
|
externalId: instance.externalId,
|
|
space: instance.space,
|
|
},
|
|
],
|
|
sources: view ? [{ source: view }] : undefined,
|
|
});
|
|
|
|
const instanceContent = instanceResponse.items[0];
|
|
if (!instanceContent?.properties) {
|
|
return [];
|
|
}
|
|
|
|
// Step 3: Extract all direct relation properties
|
|
const directlyRelatedObjects = Object.values(
|
|
instanceContent.properties
|
|
).flatMap((spaceScope) => {
|
|
if (typeof spaceScope !== 'object' || !spaceScope) return [];
|
|
return Object.values(spaceScope).flatMap((fieldValues) => {
|
|
if (typeof fieldValues !== 'object' || !fieldValues) return [];
|
|
return Object.values(fieldValues).filter(
|
|
(value): value is DmsUniqueIdentifier =>
|
|
typeof value === 'object' &&
|
|
'externalId' in value &&
|
|
'space' in value &&
|
|
typeof value.externalId === 'string' &&
|
|
typeof value.space === 'string'
|
|
);
|
|
});
|
|
});
|
|
|
|
if (directlyRelatedObjects.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
// Step 4: Inspect all related objects to get their views
|
|
const relatedObjectInspectionsResult = await sdk.instances.inspect({
|
|
inspectionOperations: { involvedViews: {} },
|
|
items: directlyRelatedObjects.map((fdmId) => ({
|
|
...fdmId,
|
|
instanceType: 'node',
|
|
})),
|
|
});
|
|
|
|
const relatedObjectsViewLists =
|
|
relatedObjectInspectionsResult.items.map(
|
|
(item) => item.inspectionResults?.involvedViews ?? []
|
|
);
|
|
|
|
// Step 5: Create a mapping of object index to views
|
|
const relatedObjectViewsWithObjectIndex = relatedObjectsViewLists
|
|
.map((viewList, idx) => viewList.map((view) => [idx, view] as const))
|
|
.flat();
|
|
|
|
// Step 6: Deduplicate views and fetch their definitions
|
|
const [deduplicatedViews, viewToDeduplicatedIndexMap] =
|
|
createDeduplicatedViewToIndexMap(relatedObjectViewsWithObjectIndex);
|
|
|
|
const viewProps = await sdk.views.retrieve(
|
|
deduplicatedViews.map((view) => {
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const { type, ...viewWithoutType } = view;
|
|
return viewWithoutType;
|
|
}),
|
|
{ includeInheritedProperties: true }
|
|
);
|
|
|
|
// Step 7: Filter to only 3D-related views
|
|
const threeDRelatedViews = relatedObjectViewsWithObjectIndex.filter(
|
|
([, view]) => {
|
|
const viewResultIndex = viewToDeduplicatedIndexMap.get(
|
|
createViewKey(view)
|
|
);
|
|
if (viewResultIndex === undefined) return false;
|
|
|
|
const propsForView = viewProps.items[viewResultIndex];
|
|
return is3dView(propsForView);
|
|
}
|
|
);
|
|
|
|
// Step 8: Return the 3D-related instances
|
|
return threeDRelatedViews.map(([idx]) => directlyRelatedObjects[idx]);
|
|
} catch (error) {
|
|
console.error('Error fetching related direct connections:', error);
|
|
return [];
|
|
}
|
|
},
|
|
enabled: !!sdk,
|
|
});
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
type ViewKey = `${string}/${string}/${string}`;
|
|
|
|
function createViewKey(source: ViewReference): ViewKey {
|
|
return `${source.externalId}/${source.space}/${source.version}`;
|
|
}
|
|
|
|
function createDeduplicatedViewToIndexMap(
|
|
viewsWithObjectIndex: Array<readonly [number, ViewReference]>
|
|
): [Array<ViewReference>, Map<ViewKey, number>] {
|
|
const deduplicatedViews: Array<ViewReference> = [];
|
|
const viewToDeduplicatedIndexMap = new Map<ViewKey, number>();
|
|
viewsWithObjectIndex.forEach(([, view]) => {
|
|
const viewKey = createViewKey(view);
|
|
if (!viewToDeduplicatedIndexMap.has(viewKey)) {
|
|
viewToDeduplicatedIndexMap.set(viewKey, deduplicatedViews.length);
|
|
deduplicatedViews.push(view);
|
|
}
|
|
});
|
|
return [deduplicatedViews, viewToDeduplicatedIndexMap];
|
|
}
|
|
|
|
/**
|
|
* Check if a view is 3D-related by checking if it implements Cognite3DObject
|
|
*/
|
|
function is3dView(view: ViewDefinition): boolean {
|
|
return (view.implements ?? []).some(
|
|
(type) =>
|
|
type.externalId === COGNITE_3D_OBJECT_VIEW.externalId &&
|
|
type.space === COGNITE_3D_OBJECT_VIEW.space &&
|
|
type.version === COGNITE_3D_OBJECT_VIEW.version
|
|
);
|
|
}
|