Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(react-components): don't remove models automatically on switching viewer #4589

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const Reveal3DResources = ({
.map((options) => ({ ...image360Settings, ...options }));
}, [resources, image360Settings]);

useRemoveNonReferencedModels(reveal3DModels, image360CollectionAddOptions, viewer);
useRemoveNonReferencedModels(resources, viewer);

const cadModelOptions = useMemo(
() => reveal3DModels.filter((model): model is CadModelOptions => model.type === 'cad'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
* Copyright 2024 Cognite AS
*/
import { type Cognite3DViewer, type CogniteModel, type Image360Collection } from '@cognite/reveal';
import { type AddImage360CollectionOptions, type TypedReveal3DModel } from './types';
import { type AddResourceOptions } from './types';
import {
is360ImageAddOptions,
is360ImageDataModelAddOptions,
is360ImageEventsAddOptions,
is3dModelOptions
} from './typeGuards';
import { useEffect } from 'react';
import { isSameCadModel, isSamePointCloudModel } from '../../utilities/isSameModel';
import { isSame3dModel } from '../../utilities/isSameModel';

export function useRemoveNonReferencedModels(
addOptions: TypedReveal3DModel[],
image360CollectionAddOptions: AddImage360CollectionOptions[],
addOptions: AddResourceOptions[],
viewer: Cognite3DViewer
): void {
useEffect(() => {
Expand All @@ -23,10 +23,7 @@ export function useRemoveNonReferencedModels(
viewer.removeModel(model);
});

const nonReferencedCollections = findNonReferencedCollections(
image360CollectionAddOptions,
viewer
);
const nonReferencedCollections = findNonReferencedCollections(addOptions, viewer);

nonReferencedCollections.forEach((collection) => {
viewer.remove360ImageSet(collection);
Expand All @@ -35,11 +32,11 @@ export function useRemoveNonReferencedModels(
}

function findNonReferencedModels(
addOptions: TypedReveal3DModel[],
addOptions: AddResourceOptions[],
viewer: Cognite3DViewer
): CogniteModel[] {
const models = viewer.models;
const addOptionsSet = new Set(addOptions);
const addOptionsSet = new Set(addOptions.filter(is3dModelOptions));

return models.filter((model) => {
const correspondingAddOptions = (() => {
Expand All @@ -48,25 +45,13 @@ function findNonReferencedModels(
continue;
}

const isCadAndSame =
options.type === 'cad' &&
isSameCadModel(options, {
type: 'cad',
modelId: model.modelId,
revisionId: model.revisionId,
transform: model.getModelTransformation()
});

const isPointCloudAndSame =
options.type === 'pointcloud' &&
isSamePointCloudModel(options, {
type: 'pointcloud',
modelId: model.modelId,
revisionId: model.revisionId,
transform: model.getModelTransformation()
});

if (isCadAndSame || isPointCloudAndSame) {
const isSameModel = isSame3dModel(options, {
modelId: model.modelId,
revisionId: model.revisionId,
transform: model.getModelTransformation()
});

if (isSameModel) {
return options;
}
}
Expand All @@ -83,9 +68,11 @@ function findNonReferencedModels(
}

function findNonReferencedCollections(
image360CollectionAddOptions: AddImage360CollectionOptions[],
addOptions: AddResourceOptions[],
viewer: Cognite3DViewer
): Image360Collection[] {
const image360CollectionAddOptions = addOptions.filter(is360ImageAddOptions);

const collections = viewer.get360ImageCollections();
const collectionAddOptionsSet = new Set(image360CollectionAddOptions);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { useMemo } from 'react';
import { type AnnotationIdStylingGroup } from '../components/PointCloudContainer/useApplyPointCloudStyling';
import { useQuery } from '@tanstack/react-query';
import { isSamePointCloudModel } from '../utilities/isSameModel';
import { isSame3dModel } from '../utilities/isSameModel';
import {
usePointCloudAnnotationMappingsForModels,
usePointCloudAnnotationIdsForModels
Expand Down Expand Up @@ -168,7 +168,7 @@ function groupStyleGroupByModel(

return styleGroup.reduce<StyledPointCloudModel[]>((accumulatedGroups, currentGroup) => {
const existingGroupWithModel = accumulatedGroups.find((group) =>
isSamePointCloudModel(group.model, currentGroup.model)
isSame3dModel(group.model, currentGroup.model)
);
existingGroupWithModel?.styleGroups.push(...currentGroup.styleGroups);
return accumulatedGroups;
Expand Down
10 changes: 5 additions & 5 deletions react-components/src/utilities/isSameModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
*/
import { type GeometryFilter } from '@cognite/reveal';
import {
type PointCloudModelOptions,
type CadModelOptions
type CadModelOptions,
type AddReveal3DModelOptions
} from '../components/Reveal3DResources/types';
import { Matrix4 } from 'three';

Expand Down Expand Up @@ -43,9 +43,9 @@ function isSameGeometryFilter(
);
}

export function isSamePointCloudModel(
model0: PointCloudModelOptions,
model1: PointCloudModelOptions
export function isSame3dModel(
model0: AddReveal3DModelOptions,
model1: AddReveal3DModelOptions
): boolean {
return (
model0.modelId === model1.modelId &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { describe, expect, test, vi, beforeEach } from 'vitest';

import { Mock } from 'moq.ts';

import { renderHook } from '@testing-library/react';

import { useRemoveNonReferencedModels } from '../../../../src/components/Reveal3DResources/useRemoveNonReferencedModels';

import {
viewerImage360CollectionsMock,
viewerMock,
viewerModelsMock,
viewerRemoveModelsMock
} from '../../fixtures/viewer';
import { cadMock, cadModelOptions } from '../../fixtures/cadModel';
import { pointCloudMock, pointCloudModelOptions } from '../../fixtures/pointCloud';
import { image360Mock, image360Options } from '../../fixtures/image360';

describe(useRemoveNonReferencedModels.name, () => {
beforeEach(() => {
vi.resetAllMocks();
});

test('does not crash when no models are added', () => {
viewerModelsMock.mockReturnValue([]);
viewerImage360CollectionsMock.mockReturnValue([]);
expect(() => renderHook(() => useRemoveNonReferencedModels([], viewerMock))).not.toThrow();
});

test('removes models when empty ', () => {
viewerModelsMock.mockReturnValue([cadMock]);
viewerImage360CollectionsMock.mockReturnValue([]);
renderHook(() => useRemoveNonReferencedModels([], viewerMock));
expect(viewerRemoveModelsMock).toHaveBeenCalledOnce();
});

test('does not remove models when in addOptions', () => {
viewerModelsMock.mockReturnValue([pointCloudMock, cadMock]);
viewerImage360CollectionsMock.mockReturnValue([image360Mock]);
renderHook(() =>
useRemoveNonReferencedModels(
[cadModelOptions, pointCloudModelOptions, image360Options],
viewerMock
)
);
expect(viewerRemoveModelsMock).not.toHaveBeenCalled();
});

test('removes only relevant model', () => {
viewerModelsMock.mockReturnValue([pointCloudMock, cadMock]);
viewerImage360CollectionsMock.mockReturnValue([image360Mock]);
renderHook(() => useRemoveNonReferencedModels([cadModelOptions, image360Options], viewerMock));
expect(viewerRemoveModelsMock).toHaveBeenCalledWith(pointCloudMock);
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, test } from 'vitest';
import { render, screen } from '@testing-library/react';
import { RevealCanvas, RevealContext } from '../../../src';
import { It, Mock } from 'moq.ts';
import { Mock } from 'moq.ts';
import { type CogniteClient } from '@cognite/sdk';
import { RevealKeepAliveContext } from '../../../src/components/RevealKeepAlive/RevealKeepAliveContext';
import { type FC, useRef } from 'react';
Expand All @@ -11,7 +11,7 @@ import { type PointCloudAnnotationCache } from '../../../src/components/CachePro
import { type Image360AnnotationCache } from '../../../src/components/CacheProvider/Image360AnnotationCache';
import { type SceneIdentifiers } from '../../../src/components/SceneContainer/sceneTypes';
import { type RevealRenderTarget } from '../../../src/architecture/base/renderTarget/RevealRenderTarget';
import { Cognite3DViewer } from '@cognite/reveal';
import { viewerMock } from '../fixtures/viewer';

describe(RevealCanvas.name, () => {
test('Mounting reveal container will mount a canvas to the DOM', () => {
Expand All @@ -22,19 +22,10 @@ describe(RevealCanvas.name, () => {
.setup((p) => p.project)
.returns('test');

const domElement = document
.createElement('div')
.appendChild(document.createElement('canvas'));

// This object is not created as a Mock because it seems to interact badly with
const renderTargetRef = useRef<RevealRenderTarget>({
get viewer() {
return new Mock<Cognite3DViewer>()
.setup((viewer) => viewer.setBackgroundColor(It.IsAny()))
.returns()
.setup((viewer) => viewer.domElement)
.returns(domElement)
.object();
return viewerMock;
},
initialize(): void {}
} as unknown as RevealRenderTarget);
Expand Down
17 changes: 17 additions & 0 deletions react-components/tests/unit-tests/fixtures/cadModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { CogniteCadModel } from '@cognite/reveal';
import { Mock } from 'moq.ts';
import { Matrix4 } from 'three';

export const cadModelOptions = {
modelId: 123,
revisionId: 456
};

export const cadMock = new Mock<CogniteCadModel>()
.setup((p) => p.modelId)
.returns(cadModelOptions.modelId)
.setup((p) => p.revisionId)
.returns(cadModelOptions.revisionId)
.setup((p) => p.getModelTransformation())
.returns(new Matrix4())
.object();
11 changes: 11 additions & 0 deletions react-components/tests/unit-tests/fixtures/image360.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Image360Collection } from '@cognite/reveal';
import { Mock } from 'moq.ts';

export const image360Options = {
siteId: 'siteId'
};

export const image360Mock = new Mock<Image360Collection>()
.setup((p) => p.id)
.returns(image360Options.siteId)
.object();
17 changes: 17 additions & 0 deletions react-components/tests/unit-tests/fixtures/pointCloud.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { CognitePointCloudModel } from '@cognite/reveal';
import { Mock } from 'moq.ts';
import { Matrix4 } from 'three';

export const pointCloudModelOptions = {
modelId: 321,
revisionId: 654
};

export const pointCloudMock = new Mock<CognitePointCloudModel>()
.setup((p) => p.modelId)
.returns(pointCloudModelOptions.modelId)
.setup((p) => p.revisionId)
.returns(pointCloudModelOptions.revisionId)
.setup((p) => p.getModelTransformation())
.returns(new Matrix4())
.object();
23 changes: 23 additions & 0 deletions react-components/tests/unit-tests/fixtures/viewer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { vi } from 'vitest';

import { Cognite3DViewer, CogniteModel, Image360Collection } from '@cognite/reveal';
import { Mock, It } from 'moq.ts';

const domElement = document.createElement('div').appendChild(document.createElement('canvas'));

export const viewerModelsMock = vi.fn<[], CogniteModel[]>();
export const viewerRemoveModelsMock = vi.fn<[CogniteModel], void>();
export const viewerImage360CollectionsMock = vi.fn<[], Image360Collection[]>();

export const viewerMock = new Mock<Cognite3DViewer>()
.setup((viewer) => viewer.setBackgroundColor(It.IsAny()))
.returns()
.setup((viewer) => viewer.domElement)
.returns(domElement)
.setup((p) => p.models)
.callback(viewerModelsMock)
.setup((p) => p.get360ImageCollections())
.callback(viewerImage360CollectionsMock)
.setup((p) => p.removeModel)
.returns(viewerRemoveModelsMock)
.object();
Loading