diff --git a/optuna_dashboard/ts/components/ModelViewer.tsx b/optuna_dashboard/ts/components/ModelViewer.tsx index 5f0d00bc..d1fc234d 100644 --- a/optuna_dashboard/ts/components/ModelViewer.tsx +++ b/optuna_dashboard/ts/components/ModelViewer.tsx @@ -1,5 +1,5 @@ import * as THREE from "three" -import React, { useState } from "react" +import React, { useEffect, useState } from "react" import { Canvas } from "@react-three/fiber" import { GizmoHelper, GizmoViewport, OrbitControls } from "@react-three/drei" import { STLLoader } from "three/examples/jsm/loaders/STLLoader" @@ -25,51 +25,55 @@ function CustomGizmoHelper(): JSX.Element { ) } +const calculateBoundingBox = (geometries: THREE.BufferGeometry[]) => { + const boundingBox = new THREE.Box3() + geometries.forEach((geometry) => { + const mesh = new THREE.Mesh(geometry) + boundingBox.expandByObject(mesh) + }) + return boundingBox +} + export function ModelViewer(props: ModelViewerProps): JSX.Element { const [geometry, setGeometry] = useState([]) - const [modelSize, setModelSize] = useState() + const [modelSize, setModelSize] = useState( + new THREE.Vector3(10, 10, 10) + ) - React.useEffect(() => { + function handleLoadedGeometries(geometries: THREE.BufferGeometry[]) { + setGeometry(geometries) + const boundingBox = calculateBoundingBox(geometries) + if (boundingBox !== null) { + const size = boundingBox.getSize(new THREE.Vector3()) + setModelSize(size) + } + } + + useEffect(() => { if ("stl" === props.filetype) { const stlLoader = new STLLoader() - stlLoader.load(props.src, (stlMesh: THREE.BufferGeometry) => { - if (stlMesh) { - setGeometry([stlMesh]) - stlMesh.computeBoundingBox() - if (stlMesh.boundingBox === null) { - setModelSize(new THREE.Vector3(10, 10, 10)) - } else { - const size = stlMesh.boundingBox.getSize(new THREE.Vector3()) - setModelSize(size) - } + stlLoader.load(props.src, (stlGeometries: THREE.BufferGeometry) => { + if (stlGeometries) { + handleLoadedGeometries([stlGeometries]) } }) } else if ("3dm" === props.filetype) { const loader = new Rhino3dmLoader() loader.setLibraryPath("https://cdn.jsdelivr.net/npm/rhino3dm@7.15.0/") loader.load(props.src, (object: THREE.Object3D) => { - object.traverse(function (child) { - // rotate to y-up - child.rotateX(-Math.PI / 4) - }) const meshes = object.children as THREE.Mesh[] - const rhinoMeshes = meshes.map((mesh) => mesh.geometry) - if (rhinoMeshes.length > 0) { - setGeometry(rhinoMeshes) - rhinoMeshes[0].computeBoundingBox() - if (rhinoMeshes[0].boundingBox === null) { - setModelSize(new THREE.Vector3(10, 10, 10)) - } else { - const size = rhinoMeshes[0].boundingBox.getSize(new THREE.Vector3()) - setModelSize(size) - } + const rhinoGeometries = meshes.map((mesh) => mesh.geometry) + if (rhinoGeometries.length > 0) { + rhinoGeometries.forEach((rGeometry) => { + rGeometry.rotateX(-Math.PI / 4) + }) + handleLoadedGeometries(rhinoGeometries) } }) } }, []) - const cameraPosition = modelSize - ? [modelSize.x * 1.5, modelSize.y * 1.5, modelSize.z * 1.5] - : [10, 10, 10] + const maxModelSize = Math.max(modelSize.x, modelSize.y, modelSize.z) + const cameraPosition = [maxModelSize * 2, maxModelSize * 2, maxModelSize * 2] const cameraSettings: PerspectiveCamera = { fov: modelSize ? Math.min(45, Math.atan(modelSize.y / modelSize.z) * (180 / Math.PI) * 2) @@ -87,9 +91,7 @@ export function ModelViewer(props: ModelViewerProps): JSX.Element { > - + {props.hasGizmo && } {geometry.length > 0 &&