Skip to content

Commit

Permalink
Merge pull request #67 from MetaCell/feature/CELE-109
Browse files Browse the repository at this point in the history
CELE-109 Fixes screen flash with 3D viewer
  • Loading branch information
ddelpiano authored Oct 23, 2024
2 parents f899197 + bcdfd52 commit 1a2ae58
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 1a2ae58

Please sign in to comment.