Skip to content

Commit

Permalink
CELE-109 Fixes screen flash with 3D viewer
Browse files Browse the repository at this point in the history
The problem of flash came from the fact that, depending on the dataset,
the data couldn't be fetch. When that was the case, none of the scene
was rendered, even if only one file couldn't be loaded. This commit
changes the way the 3D images are loaded, and shows an error message to
inform the user if one of the 3D images cannot be displayed (the other
images for the requested neurons are displayed if they exists in the
bucket).
  • Loading branch information
aranega committed Oct 23, 2024
1 parent f899197 commit bcdfd52
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const DatasetPicker: React.FC<DatasetPickerProps> = ({ datasets, selectedDataset
onChange={(newValue) => onDatasetChange(newValue)}
getOptionLabel={(option: Dataset) => option.name}
renderOption={(props, option) => (
<li {...props}>
<li {...props} key={`3Dviewer_dataset_${option.name}`}>
<CheckIcon />
<Typography>{option.name}</Typography>
</li>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Center } from "@react-three/drei";
import { useLoader } from "@react-three/fiber";
import type { FC } from "react";
import type { BufferGeometry } from "three";
import { type FC, useMemo, useState } from "react";
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";
import { useGlobalContext } from "../../../contexts/GlobalContext.tsx";
import { GlobalError } from "../../../models/Error.ts";
import STLMesh from "./STLMesh.tsx";
import type { Instance } from "./ThreeDViewer.tsx";

Expand All @@ -12,12 +12,48 @@ interface Props {
}

const STLViewer: FC<Props> = ({ instances, isWireframe }) => {
// TODO: Check if useLoader caches or do we need to do it ourselves
// @ts-expect-error Argument type STLLoader is not assignable to parameter type LoaderProto<T>
const stlObjects = useLoader<STLLoader, BufferGeometry[]>(
STLLoader,
instances.map((i) => i.url),
);
const { handleErrors } = useGlobalContext();
const [stlObjects, setSTLObjects] = useState([]);

useMemo(() => {
const loader = new STLLoader();

const errorFiles = [];

// Load all STL files in parallel
const loadSTLFiles = async () => {
const loadPromises = instances.map(
(instance) =>
new Promise((resolve, _) => {
loader.load(
instance.url,
(geometry) => resolve(geometry.center()),
undefined,
(error) => {
console.error(`Error loading ${instance.url}:`, error);
errorFiles.push(instance);
resolve(null);
},
);
}),
);
// Wait for all promises to finish
const results = await Promise.allSettled(loadPromises);

// We filter now all the promises that didn't finish properly
// @ts-expect-error
const successfulModels = results.filter((result) => result.status === "fulfilled" && result.value).map((result) => result.value);

setSTLObjects(successfulModels);

// If there is some error, we display a message to inform the user
if (errorFiles.length > 0) {
handleErrors(new GlobalError(`Couldn't fetch 3D representation for ${errorFiles.map((e) => e.id)}`));
}
};

loadSTLFiles();
}, [instances]);

return (
<Center>
Expand All @@ -26,7 +62,6 @@ const STLViewer: FC<Props> = ({ instances, isWireframe }) => {
<STLMesh
key={instances[idx].id}
id={instances[idx].id}
// @ts-expect-error Type 'ConditionalType<LoaderReturnType<T, L>, GLTFLike, LoaderReturnType<T, L> & ObjectMap, LoaderReturnType<T, L>>' is not assignable to type 'BufferGeometry<NormalBufferAttributes>'.
stl={stl}
opacity={instances[idx].opacity}
color={instances[idx].color}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Suspense, useEffect, useMemo, useRef, useState } from "react";
import type * as THREE from "three";
import { useSelectedWorkspace } from "../../../hooks/useSelectedWorkspace.ts";
import { ViewerType, getNeuronUrlForDataset } from "../../../models/models.ts";
import { type Dataset, OpenAPI } from "../../../rest";
import type { Dataset } from "../../../rest";
import {
CAMERA_FAR,
CAMERA_FOV,
Expand Down Expand Up @@ -61,12 +61,15 @@ function ThreeDViewer() {
const viewerData = workspace.visibilities[neuronId]?.[ViewerType.ThreeD];
const urls = getNeuronUrlForDataset(neuron, selectedDataset.id);

return urls.map((url, index) => ({
id: `${neuronId}-${index}`,
url: `${OpenAPI.BASE}/${url}`,
color: viewerData?.color || "#FFFFFF",
opacity: 1,
}));
return urls.map((url) => {
const neuronName = url.match(/([^/]+)(\.[^/]*)?$/)?.[1].replace(/(\.[^/]*)?$/, "");
return {
id: `${neuronName}`,
url,
color: viewerData?.color || "#FFFFFF",
opacity: 1,
};
});
});

setInstances(newInstances);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import ErrorAlert from "../components/ErrorAlert.tsx";
import ErrorBoundary from "../components/ErrorBoundary.tsx";
import { ViewMode } from "../models";
import { Workspace } from "../models";
import { GlobalError } from "../models/Error.ts";
import { type Dataset, DatasetsService } from "../rest";
import type { SerializedGlobalContext } from "./SerializedContext.tsx";

Expand Down Expand Up @@ -103,10 +102,10 @@ export const GlobalContextProvider: React.FC<GlobalContextProviderProps> = ({ ch
};

const handleErrors = (error: Error) => {
if (error instanceof GlobalError) {
setErrorMessage(error.message);
setOpenErrorAlert(true);
}
// if (error instanceof GlobalError) {
setErrorMessage(error.message);
setOpenErrorAlert(true);
// }
};

const serializeGlobalContext = () => {
Expand Down

0 comments on commit bcdfd52

Please sign in to comment.