From 3472f353edb8de0accc3490eccc86bbf2a33fa07 Mon Sep 17 00:00:00 2001 From: hrntsm Date: Mon, 7 Aug 2023 11:29:07 +0900 Subject: [PATCH 01/45] Add 3d model view --- .../ts/components/ModelViewer.tsx | 81 +++++++++++++++++++ optuna_dashboard/ts/components/TrialList.tsx | 75 +++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 optuna_dashboard/ts/components/ModelViewer.tsx diff --git a/optuna_dashboard/ts/components/ModelViewer.tsx b/optuna_dashboard/ts/components/ModelViewer.tsx new file mode 100644 index 00000000..db5caee2 --- /dev/null +++ b/optuna_dashboard/ts/components/ModelViewer.tsx @@ -0,0 +1,81 @@ +import * as THREE from "three" +import React, { 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" +import { PerspectiveCamera } from "three" + +interface ModelViewerProps { + src: string + alt: string + width: string + height: string + hasGizmo: boolean +} + +function CustomGizmoHelper(): JSX.Element { + return ( + + + + ) +} + +export function ModelViewer(props: ModelViewerProps): JSX.Element { + const [geometry, setGeometry] = useState() + const [modelSize, setModelSize] = useState() + + React.useEffect(() => { + const loader = new STLLoader() + loader.load(props.src, (geometry: THREE.BufferGeometry) => { + if (geometry) { + setGeometry(geometry) + geometry.computeBoundingBox() + if (geometry.boundingBox === null) { + setModelSize(new THREE.Vector3(10, 10, 10)) + } else { + const size = geometry.boundingBox.getSize(new THREE.Vector3()) + setModelSize(size) + } + } + }) + }, []) + const cameraPosition = modelSize + ? [modelSize.x * 1.5, modelSize.y * 1.5, modelSize.z * 1.5] + : [10, 10, 10] + const cameraSettings: PerspectiveCamera = { + fov: modelSize + ? Math.min(45, Math.atan(modelSize.y / modelSize.z) * (180 / Math.PI) * 2) + : 45, + aspect: window.innerWidth / window.innerHeight, + near: 0.1, + far: 1000, + position: cameraPosition, + } + + const viewerWidth = `${parseInt(props.width) * 2}px` + + return ( + + + + + {props.hasGizmo && } + + + {geometry && ( + + + + )} + + ) +} diff --git a/optuna_dashboard/ts/components/TrialList.tsx b/optuna_dashboard/ts/components/TrialList.tsx index c2c75dd3..165fe9d0 100644 --- a/optuna_dashboard/ts/components/TrialList.tsx +++ b/optuna_dashboard/ts/components/TrialList.tsx @@ -45,6 +45,7 @@ import { artifactIsAvailable } from "../state" import { actionCreator } from "../action" import { useDeleteArtifactDialog } from "./DeleteArtifactDialog" import { TrialFormWidgets } from "./TrialFormWidgets" +import { ModelViewer } from "./ModelViewer" const states: TrialState[] = [ "Complete", @@ -437,6 +438,80 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { ) + } else if (a.filename.endsWith(".stl")) { + return ( + + + + + + + {a.filename} + + { + openDeleteArtifactDialog( + trial.study_id, + trial.trial_id, + a + ) + }} + > + + + + + + + + ) } else if (a.mimetype.startsWith("audio")) { return ( Date: Thu, 10 Aug 2023 15:48:33 +0900 Subject: [PATCH 02/45] Remove system_attrs attributes from API response --- optuna_dashboard/_serializer.py | 4 ---- optuna_dashboard/ts/apiClient.ts | 8 -------- optuna_dashboard/ts/types/index.d.ts | 2 -- 3 files changed, 14 deletions(-) diff --git a/optuna_dashboard/_serializer.py b/optuna_dashboard/_serializer.py index 53052427..7fae23ea 100644 --- a/optuna_dashboard/_serializer.py +++ b/optuna_dashboard/_serializer.py @@ -107,7 +107,6 @@ def serialize_study_summary(summary: StudySummary) -> dict[str, Any]: "study_name": summary.study_name, "directions": [d.name.lower() for d in summary.directions], "user_attrs": serialize_attrs(summary.user_attrs), - "system_attrs": serialize_attrs(getattr(summary, "system_attrs", {})), } if summary.datetime_start is not None: @@ -183,9 +182,6 @@ def serialize_frozen_trial( for param_name in fixed_params ], "user_attrs": serialize_attrs(trial.user_attrs), - "system_attrs": serialize_attrs( - {k: trial_system_attrs[k] for k in trial_system_attrs if not k.startswith("dashboard")} - ), "note": note.get_note_from_system_attrs(study_system_attrs, trial._trial_id), "artifacts": list_trial_artifacts(study_system_attrs, trial), "constraints": trial_system_attrs.get(CONSTRAINTS_KEY, []), diff --git a/optuna_dashboard/ts/apiClient.ts b/optuna_dashboard/ts/apiClient.ts index fc75b942..07c825d0 100644 --- a/optuna_dashboard/ts/apiClient.ts +++ b/optuna_dashboard/ts/apiClient.ts @@ -27,7 +27,6 @@ interface TrialResponse { param_external_value: string }[] user_attrs: Attribute[] - system_attrs: Attribute[] note: Note artifacts: Artifact[] constraints: number[] @@ -50,7 +49,6 @@ const convertTrialResponse = (res: TrialResponse): Trial => { params: res.params, fixed_params: res.fixed_params, user_attrs: res.user_attrs, - system_attrs: res.system_attrs, note: res.note, artifacts: res.artifacts, constraints: res.constraints, @@ -113,7 +111,6 @@ interface StudySummariesResponse { study_name: string directions: StudyDirection[] user_attrs: Attribute[] - system_attrs: Attribute[] datetime_start?: string }[] } @@ -128,7 +125,6 @@ export const getStudySummariesAPI = (): Promise => { study_name: study.study_name, directions: study.directions, user_attrs: study.user_attrs, - system_attrs: study.system_attrs, datetime_start: study.datetime_start ? new Date(study.datetime_start) : undefined, @@ -143,7 +139,6 @@ interface CreateNewStudyResponse { study_name: string directions: StudyDirection[] user_attrs: Attribute[] - system_attrs: Attribute[] datetime_start?: string } } @@ -165,7 +160,6 @@ export const createNewStudyAPI = ( directions: study_summary.directions, // best_trial: undefined, user_attrs: study_summary.user_attrs, - system_attrs: study_summary.system_attrs, datetime_start: study_summary.datetime_start ? new Date(study_summary.datetime_start) : undefined, @@ -184,7 +178,6 @@ type RenameStudyResponse = { study_name: string directions: StudyDirection[] user_attrs: Attribute[] - system_attrs: Attribute[] datetime_start?: string } @@ -202,7 +195,6 @@ export const renameStudyAPI = ( study_name: res.data.study_name, directions: res.data.directions, user_attrs: res.data.user_attrs, - system_attrs: res.data.system_attrs, datetime_start: res.data.datetime_start ? new Date(res.data.datetime_start) : undefined, diff --git a/optuna_dashboard/ts/types/index.d.ts b/optuna_dashboard/ts/types/index.d.ts index 040a1fc3..b27be14e 100644 --- a/optuna_dashboard/ts/types/index.d.ts +++ b/optuna_dashboard/ts/types/index.d.ts @@ -111,7 +111,6 @@ type Trial = { param_external_value: string }[] user_attrs: Attribute[] - system_attrs: Attribute[] constraints: number[] note: Note artifacts: Artifact[] @@ -122,7 +121,6 @@ type StudySummary = { study_name: string directions: StudyDirection[] user_attrs: Attribute[] - system_attrs: Attribute[] datetime_start?: Date } From 915effd4fcb70c076c4d1e0194b6c475944886f2 Mon Sep 17 00:00:00 2001 From: hrntsm Date: Fri, 11 Aug 2023 17:27:29 +0900 Subject: [PATCH 03/45] Add modal button to model viewer --- .../ts/components/ModelViewer.tsx | 6 +-- optuna_dashboard/ts/components/TrialList.tsx | 44 +++++++++++++++++-- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/optuna_dashboard/ts/components/ModelViewer.tsx b/optuna_dashboard/ts/components/ModelViewer.tsx index db5caee2..9695f139 100644 --- a/optuna_dashboard/ts/components/ModelViewer.tsx +++ b/optuna_dashboard/ts/components/ModelViewer.tsx @@ -7,7 +7,6 @@ import { PerspectiveCamera } from "three" interface ModelViewerProps { src: string - alt: string width: string height: string hasGizmo: boolean @@ -56,12 +55,10 @@ export function ModelViewer(props: ModelViewerProps): JSX.Element { position: cameraPosition, } - const viewerWidth = `${parseInt(props.width) * 2}px` - return ( @@ -70,7 +67,6 @@ export function ModelViewer(props: ModelViewerProps): JSX.Element { /> {props.hasGizmo && } - {geometry && ( diff --git a/optuna_dashboard/ts/components/TrialList.tsx b/optuna_dashboard/ts/components/TrialList.tsx index 165fe9d0..744db8d4 100644 --- a/optuna_dashboard/ts/components/TrialList.tsx +++ b/optuna_dashboard/ts/components/TrialList.tsx @@ -20,6 +20,7 @@ import { CardContent, CardMedia, CardActionArea, + Modal, } from "@mui/material" import Chip from "@mui/material/Chip" import Divider from "@mui/material/Divider" @@ -34,6 +35,7 @@ import CheckBoxIcon from "@mui/icons-material/CheckBox" import UploadFileIcon from "@mui/icons-material/UploadFile" import DownloadIcon from "@mui/icons-material/Download" import DeleteIcon from "@mui/icons-material/Delete" +import OpenWithIcon from "@mui/icons-material/OpenWith" import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile" import StopCircleIcon from "@mui/icons-material/StopCircle" @@ -328,6 +330,7 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { const [openDeleteArtifactDialog, renderDeleteArtifactDialog] = useDeleteArtifactDialog() const [dragOver, setDragOver] = useState(false) + const [open3dModelViewer, setOpen3dModelViewer] = useState(false) const width = "200px" const height = "150px" @@ -367,6 +370,7 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { e.dataTransfer.dropEffect = "copy" setDragOver(false) } + return ( <> = ({ trial }) => { marginBottom: theme.spacing(2), display: "flex", flexDirection: "column", - width: `${parseInt(width) * 2}px`, + width: width, minHeight: "100%", margin: theme.spacing(0, 1, 1, 0), }} @@ -461,10 +465,9 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { > = ({ trial }) => { > {a.filename} + { + setOpen3dModelViewer(true) + }} + > + + + { + setOpen3dModelViewer(false) + }} + > + + + + Date: Fri, 11 Aug 2023 17:30:08 +0900 Subject: [PATCH 04/45] Add r3f & drei to package.json --- package-lock.json | 1088 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 6 +- 2 files changed, 1089 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index a482c6f1..60e7ef8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,9 @@ "@mui/icons-material": "^5.11.6", "@mui/lab": "^5.0.0-alpha.128", "@mui/material": "^5.12.1", + "@react-three/drei": "^9.80.0", + "@react-three/fiber": "^8.13.6", + "@types/three": "^0.154.0", "axios": "^1.2.1", "notistack": "^3.0.1", "plotly.js-dist-min": "^2.22.0", @@ -26,7 +29,8 @@ "rehype-mathjax": "^4.0.2", "rehype-raw": "^6.1.1", "remark-gfm": "^3.0.1", - "remark-math": "^5.1.1" + "remark-math": "^5.1.1", + "three": "^0.155.0" }, "devDependencies": { "@babel/core": "^7.14.3", @@ -1703,6 +1707,35 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", + "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", + "dependencies": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", + "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", + "dependencies": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/types": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", + "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==" + }, + "node_modules/@chevrotain/utils": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", + "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==" + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -2926,6 +2959,11 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@mediapipe/tasks-vision": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.2.tgz", + "integrity": "sha512-d8Q9uRK89ZRWmED2JLI9/blpJcfdbh0iEUuMo8TgkMzNfQBY1/GC0FEJWrairTwHkxIf6Oud1vFBP+aHicWqJA==" + }, "node_modules/@mui/base": { "version": "5.0.0-alpha.127", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.127.tgz", @@ -3252,6 +3290,163 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@react-spring/animated": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.6.1.tgz", + "integrity": "sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ==", + "dependencies": { + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/core": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.6.1.tgz", + "integrity": "sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==", + "dependencies": { + "@react-spring/animated": "~9.6.1", + "@react-spring/rafz": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/rafz": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.6.1.tgz", + "integrity": "sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==" + }, + "node_modules/@react-spring/shared": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.6.1.tgz", + "integrity": "sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==", + "dependencies": { + "@react-spring/rafz": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/three": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.6.1.tgz", + "integrity": "sha512-Tyw2YhZPKJAX3t2FcqvpLRb71CyTe1GvT3V+i+xJzfALgpk10uPGdGaQQ5Xrzmok1340DAeg2pR/MCfaW7b8AA==", + "dependencies": { + "@react-spring/animated": "~9.6.1", + "@react-spring/core": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "@react-three/fiber": ">=6.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "three": ">=0.126" + } + }, + "node_modules/@react-spring/types": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.6.1.tgz", + "integrity": "sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==" + }, + "node_modules/@react-three/drei": { + "version": "9.80.0", + "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-9.80.0.tgz", + "integrity": "sha512-quBD0Ap7ygf9Yg94keIQpQNnveNSKoZUge9ybHd6UgE9QTdpfkPF3YgzTJLbDEoZKepCTE9ngpwUgFy+cpL9Pg==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@mediapipe/tasks-vision": "0.10.2", + "@react-spring/three": "~9.6.1", + "@use-gesture/react": "^10.2.24", + "camera-controls": "^2.4.2", + "detect-gpu": "^5.0.28", + "glsl-noise": "^0.0.0", + "lodash.clamp": "^4.0.3", + "lodash.omit": "^4.5.0", + "lodash.pick": "^4.4.0", + "maath": "^0.6.0", + "meshline": "^3.1.6", + "react-composer": "^5.0.3", + "react-merge-refs": "^1.1.0", + "stats-gl": "^1.0.4", + "stats.js": "^0.17.0", + "suspend-react": "^0.1.3", + "three-mesh-bvh": "^0.6.0", + "three-stdlib": "^2.23.9", + "troika-three-text": "^0.47.2", + "utility-types": "^3.10.0", + "zustand": "^3.5.13" + }, + "peerDependencies": { + "@react-three/fiber": ">=8.0", + "react": ">=18.0", + "react-dom": ">=18.0", + "three": ">=0.137" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/@react-three/fiber": { + "version": "8.13.6", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.13.6.tgz", + "integrity": "sha512-V49lldHcbsC7PMnnf4aYrMpHPQe8R7hJYL0AEFjqwioY0nkwga9A+Jx6lCLVG02DF03xwCfJZv5cjZCChffsWg==", + "dependencies": { + "@babel/runtime": "^7.17.8", + "@types/react-reconciler": "^0.26.7", + "its-fine": "^1.0.6", + "react-reconciler": "^0.27.0", + "react-use-measure": "^2.1.1", + "scheduler": "^0.21.0", + "suspend-react": "^0.1.3", + "zustand": "^3.7.1" + }, + "peerDependencies": { + "expo": ">=43.0", + "expo-asset": ">=8.4", + "expo-gl": ">=11.0", + "react": ">=18.0", + "react-dom": ">=18.0", + "react-native": ">=0.64", + "three": ">=0.133" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "expo-asset": { + "optional": true + }, + "expo-gl": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@react-three/fiber/node_modules/scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/@remix-run/router": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.0.tgz", @@ -3399,6 +3594,11 @@ "node": ">= 10" } }, + "node_modules/@tweenjs/tween.js": { + "version": "18.6.4", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", + "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==" + }, "node_modules/@types/aria-query": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", @@ -3454,6 +3654,11 @@ "@types/ms": "*" } }, + "node_modules/@types/draco3d": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.2.tgz", + "integrity": "sha512-goh23EGr6CLV6aKPwN1p8kBD/7tT5V/bLpToSbarKrwVejqNrspVrv8DhliteYkkhZYrlq/fwKZRRUzH4XN88w==" + }, "node_modules/@types/eslint": { "version": "8.4.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", @@ -3621,6 +3826,11 @@ "integrity": "sha512-FgD3NtTAKvyMmD44T07zz2fEf+OKwutgBCEVM8GcvMGVGaDktiLNTDvPwC/LUe3PinMW+X6CuLOF2Ui1mAlSXg==", "dev": true }, + "node_modules/@types/offscreencanvas": { + "version": "2019.7.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", + "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==" + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -3685,6 +3895,14 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-reconciler": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", + "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-router": { "version": "5.1.19", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.19.tgz", @@ -3734,6 +3952,24 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-9w+a7bR8PeB0dCT/HBULU2fMqf6BAzvKbxFboYhmDtDkKPiyXYbjoe2auwsXlEFI7CFNMF1dCv3dFH5Poy9R1w==" + }, + "node_modules/@types/three": { + "version": "0.154.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.154.0.tgz", + "integrity": "sha512-IioqpGhch6FdLDh4zazRn3rXHj6Vn2nVOziJdXVbJFi9CaI65LtP9qqUtpzbsHK2Ezlox8NtsLNHSw3AQzucjA==", + "dependencies": { + "@tweenjs/tween.js": "~18.6.4", + "@types/stats.js": "*", + "@types/webxr": "*", + "fflate": "~0.6.9", + "lil-gui": "~0.17.0", + "meshoptimizer": "~0.18.1" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", @@ -3750,6 +3986,11 @@ "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.46.tgz", "integrity": "sha512-ki0OmbjSdAEfvmy5AYWFpMkRsPW+6h4ibQ4tzk8SJsS9dkrrD3B/U1eVvdNNWxAzntjq6o2sjSia6UBCoPH+Yg==" }, + "node_modules/@types/webxr": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.2.tgz", + "integrity": "sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==" + }, "node_modules/@types/yargs": { "version": "17.0.19", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.19.tgz", @@ -3952,6 +4193,22 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@use-gesture/core": { + "version": "10.2.27", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.2.27.tgz", + "integrity": "sha512-V4XV7hn9GAD2MYu8yBBVi5iuWBsAMfjPRMsEVzoTNGYH72tf0kFP+OKqGKc8YJFQIJx6yj+AOqxmEHOmx2/MEA==" + }, + "node_modules/@use-gesture/react": { + "version": "10.2.27", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.2.27.tgz", + "integrity": "sha512-7E5vnWCxeslWlxwZ8uKIcnUZVMTRMZ8cvSnLLKF1NkyNb3PnNiAzoXM4G1vTKJKRhgOTeI6wK1YsEpwo9ABV5w==", + "dependencies": { + "@use-gesture/core": "10.2.27" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -4490,6 +4747,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bidi-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.2.tgz", + "integrity": "sha512-rzSy/k7WdX5zOyeHHCOixGXbCHkyogkxPKL2r8QtzHmVQDiWCXUWa18bLdMWT9CYMLOYTjWpTHawuev2ouYJVw==", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -4611,6 +4876,14 @@ "node": ">=6" } }, + "node_modules/camera-controls": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-2.7.0.tgz", + "integrity": "sha512-HONMoMYHieOCQOoweS639bdWHP/P/fvVGR08imnECGVUp04mqGfsX/zp1ZufLeiAA5hA6i1JhP6SrnOwh01C0w==", + "peerDependencies": { + "three": ">=0.126.1" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001439", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz", @@ -4685,6 +4958,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chevrotain": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", + "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", + "dependencies": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -4924,6 +5210,11 @@ "node": ">=12" } }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -5035,6 +5326,14 @@ "node": ">=6" } }, + "node_modules/detect-gpu": { + "version": "5.0.34", + "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.34.tgz", + "integrity": "sha512-iHYDY2iy6OVvJVmTXvMrp5+OROP0Q62qTlrsC7wl3kZmm6yVAoOhQx5cIIxTVEEIZe1M1aPVI3RgsbG/Z6/7PQ==", + "dependencies": { + "webgl-constants": "^1.1.1" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -5111,6 +5410,11 @@ "node": ">=12" } }, + "node_modules/draco3d": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.6.tgz", + "integrity": "sha512-+3NaRjWktb5r61ZFoDejlykPEFKT5N/LkbXsaddlw6xNSXBanUYpFc2AXXpbJDilPHazcSreU/DpQIaxfX0NfQ==" + }, "node_modules/electron-to-chromium": { "version": "1.4.284", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", @@ -6195,6 +6499,11 @@ "bser": "2.1.1" } }, + "node_modules/fflate": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -6465,6 +6774,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/glsl-noise": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz", + "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==" + }, "node_modules/goober": { "version": "2.1.13", "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.13.tgz", @@ -7419,6 +7733,25 @@ "node": ">=8" } }, + "node_modules/its-fine": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.1.1.tgz", + "integrity": "sha512-v1Ia1xl20KbuSGlwoaGsW0oxsw8Be+TrXweidxD9oT/1lAh6O3K3/GIM95Tt6WCiv6W+h2M7RB1TwdoAjQyyKw==", + "dependencies": { + "@types/react-reconciler": "^0.28.0" + }, + "peerDependencies": { + "react": ">=18.0" + } + }, + "node_modules/its-fine/node_modules/@types/react-reconciler": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.2.tgz", + "integrity": "sha512-8tu6lHzEgYPlfDf/J6GOQdIc+gs+S2yAqlby3zTsB3SP2svlqTYe5fwZNtZyfactP74ShooP2vvi1BOp9ZemWw==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/jest": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", @@ -10019,6 +10352,11 @@ "node": ">=6" } }, + "node_modules/ktx-parse": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/ktx-parse/-/ktx-parse-0.4.5.tgz", + "integrity": "sha512-MK3FOody4TXbFf8Yqv7EBbySw7aPvEcPX++Ipt6Sox+/YMFvR5xaTyhfNSk1AEmMy+RYIw81ctN4IMxCB8OAlg==" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -10041,6 +10379,11 @@ "node": ">= 0.8.0" } }, + "node_modules/lil-gui": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz", + "integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -10081,6 +10424,16 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.clamp": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash.clamp/-/lodash.clamp-4.0.3.tgz", + "integrity": "sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -10099,6 +10452,16 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.omit": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", + "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==" + }, + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==" + }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -10159,6 +10522,15 @@ "lz-string": "bin/bin.js" } }, + "node_modules/maath": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/maath/-/maath-0.6.0.tgz", + "integrity": "sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw==", + "peerDependencies": { + "@types/three": ">=0.144.0", + "three": ">=0.144.0" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -10435,6 +10807,19 @@ "node": ">= 8" } }, + "node_modules/meshline": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.1.6.tgz", + "integrity": "sha512-8JZJOdaL5oz3PI/upG8JvP/5FfzYUOhrkJ8np/WKvXzl0/PZ2V9pqTvCIjSKv+w9ccg2xb+yyBhXAwt6ier3ug==", + "peerDependencies": { + "three": ">=0.137" + } + }, + "node_modules/meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==" + }, "node_modules/mhchemparser": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.1.1.tgz", @@ -11076,6 +11461,11 @@ "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==" }, + "node_modules/mmd-parser": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mmd-parser/-/mmd-parser-1.0.4.tgz", + "integrity": "sha512-Qi0VCU46t2IwfGv5KF0+D/t9cizcDug7qnNoy9Ggk7aucp0tssV8IwTMkBlDbm+VqAf3cdQHTCARKSsuS2MYFg==" + }, "node_modules/moo-color": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz", @@ -11259,6 +11649,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opentype.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz", + "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==", + "dependencies": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -11455,6 +11860,11 @@ "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-2.22.0.tgz", "integrity": "sha512-2b7w4CQI06px8HVpKpgZtfuoDjuCLA26VlgdnG71UDBrJvtCYvXb39H4ElNv+CA1bbD4S98KanpWPRqTqlxBZw==" }, + "node_modules/potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11628,6 +12038,17 @@ "node": ">=0.10.0" } }, + "node_modules/react-composer": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/react-composer/-/react-composer-5.0.3.tgz", + "integrity": "sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==", + "dependencies": { + "prop-types": "^15.6.0" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -11675,6 +12096,38 @@ "react": ">=16" } }, + "node_modules/react-merge-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz", + "integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/react-reconciler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.27.0.tgz", + "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.21.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/react-reconciler/node_modules/scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/react-router": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.11.0.tgz", @@ -11735,6 +12188,18 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-use-measure": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", + "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==", + "dependencies": { + "debounce": "^1.2.1" + }, + "peerDependencies": { + "react": ">=16.13", + "react-dom": ">=16.13" + } + }, "node_modules/rechoir": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", @@ -11820,6 +12285,11 @@ "@babel/runtime": "^7.8.4" } }, + "node_modules/regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==" + }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -12078,7 +12548,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -12516,6 +12985,16 @@ "node": ">=8" } }, + "node_modules/stats-gl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-1.0.4.tgz", + "integrity": "sha512-oxo13HHonoMWIYcrIu4xCk8IcFEFaqAOkMOMIyfvZFxNZzGy+jnW8sy0W3VfEjKQd5JX0Kp2KhePAKhtI6/TSw==" + }, + "node_modules/stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==" + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -12543,6 +13022,11 @@ "node": ">=8" } }, + "node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -12620,6 +13104,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/suspend-react": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", + "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", + "peerDependencies": { + "react": ">=17.0" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -12774,6 +13266,45 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/three": { + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.155.0.tgz", + "integrity": "sha512-sNgCYmDijnIqkD/bMfk+1pHg3YzsxW7V2ChpuP6HCQ8NiZr3RufsXQr8M3SSUMjW4hG+sUk7YbyuY0DncaDTJQ==" + }, + "node_modules/three-mesh-bvh": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.6.3.tgz", + "integrity": "sha512-xjuGLSI9nBATIsWcT/DnnNma5xXYyvBiXfUbhGLAFqItOlOKYF5JWsUOX+cuSAnSWovEoHzd5Emx23qKiByrlw==", + "peerDependencies": { + "three": ">= 0.151.0" + } + }, + "node_modules/three-stdlib": { + "version": "2.23.13", + "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.23.13.tgz", + "integrity": "sha512-WU4XHs4E6szAyoREzTs5bUAc1WFadiz5jRjNlA7aIIqf+raT4jfA320P0XmlyZz/wJwmpZnOuki4kAFsmadkTQ==", + "dependencies": { + "@types/draco3d": "^1.4.0", + "@types/offscreencanvas": "^2019.6.4", + "@types/webxr": "^0.5.2", + "chevrotain": "^10.1.2", + "draco3d": "^1.4.1", + "fflate": "^0.6.9", + "ktx-parse": "^0.4.5", + "mmd-parser": "^1.0.4", + "opentype.js": "^1.3.3", + "potpack": "^1.0.1", + "zstddec": "^0.0.2" + }, + "peerDependencies": { + "three": ">=0.128.0" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -12834,6 +13365,33 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/troika-three-text": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.47.2.tgz", + "integrity": "sha512-qylT0F+U7xGs+/PEf3ujBdJMYWbn0Qci0kLqI5BJG2kW1wdg4T1XSxneypnF05DxFqJhEzuaOR9S2SjiyknMng==", + "dependencies": { + "bidi-js": "^1.0.2", + "troika-three-utils": "^0.47.2", + "troika-worker-utils": "^0.47.2", + "webgl-sdf-generator": "1.1.1" + }, + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-three-utils": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.47.2.tgz", + "integrity": "sha512-/28plhCxfKtH7MSxEGx8e3b/OXU5A0xlwl+Sbdp0H8FXUHKZDoksduEKmjQayXYtxAyuUiCRunYIv/8Vi7aiyg==", + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-worker-utils": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.47.2.tgz", + "integrity": "sha512-mzss4MeyzUkYBppn4x5cdAqrhBHFEuVmMMgLMTyFV23x6GvQMyo+/R5E5Lsbrt7WSt5RfvewjcwD1DChRTA9lA==" + }, "node_modules/trough": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", @@ -13276,6 +13834,14 @@ "requires-port": "^1.0.0" } }, + "node_modules/utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/uvu": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", @@ -13414,6 +13980,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/webgl-constants": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", + "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==" + }, + "node_modules/webgl-sdf-generator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz", + "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==" + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -13882,6 +14458,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zstddec": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.0.2.tgz", + "integrity": "sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==" + }, + "node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -15036,6 +15633,35 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", + "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", + "requires": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "@chevrotain/gast": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", + "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", + "requires": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "@chevrotain/types": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", + "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==" + }, + "@chevrotain/utils": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", + "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==" + }, "@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -15980,6 +16606,11 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@mediapipe/tasks-vision": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.2.tgz", + "integrity": "sha512-d8Q9uRK89ZRWmED2JLI9/blpJcfdbh0iEUuMo8TgkMzNfQBY1/GC0FEJWrairTwHkxIf6Oud1vFBP+aHicWqJA==" + }, "@mui/base": { "version": "5.0.0-alpha.127", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.127.tgz", @@ -16127,6 +16758,110 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==" }, + "@react-spring/animated": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.6.1.tgz", + "integrity": "sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ==", + "requires": { + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + } + }, + "@react-spring/core": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.6.1.tgz", + "integrity": "sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==", + "requires": { + "@react-spring/animated": "~9.6.1", + "@react-spring/rafz": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + } + }, + "@react-spring/rafz": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.6.1.tgz", + "integrity": "sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==" + }, + "@react-spring/shared": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.6.1.tgz", + "integrity": "sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==", + "requires": { + "@react-spring/rafz": "~9.6.1", + "@react-spring/types": "~9.6.1" + } + }, + "@react-spring/three": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.6.1.tgz", + "integrity": "sha512-Tyw2YhZPKJAX3t2FcqvpLRb71CyTe1GvT3V+i+xJzfALgpk10uPGdGaQQ5Xrzmok1340DAeg2pR/MCfaW7b8AA==", + "requires": { + "@react-spring/animated": "~9.6.1", + "@react-spring/core": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + } + }, + "@react-spring/types": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.6.1.tgz", + "integrity": "sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==" + }, + "@react-three/drei": { + "version": "9.80.0", + "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-9.80.0.tgz", + "integrity": "sha512-quBD0Ap7ygf9Yg94keIQpQNnveNSKoZUge9ybHd6UgE9QTdpfkPF3YgzTJLbDEoZKepCTE9ngpwUgFy+cpL9Pg==", + "requires": { + "@babel/runtime": "^7.11.2", + "@mediapipe/tasks-vision": "0.10.2", + "@react-spring/three": "~9.6.1", + "@use-gesture/react": "^10.2.24", + "camera-controls": "^2.4.2", + "detect-gpu": "^5.0.28", + "glsl-noise": "^0.0.0", + "lodash.clamp": "^4.0.3", + "lodash.omit": "^4.5.0", + "lodash.pick": "^4.4.0", + "maath": "^0.6.0", + "meshline": "^3.1.6", + "react-composer": "^5.0.3", + "react-merge-refs": "^1.1.0", + "stats-gl": "^1.0.4", + "stats.js": "^0.17.0", + "suspend-react": "^0.1.3", + "three-mesh-bvh": "^0.6.0", + "three-stdlib": "^2.23.9", + "troika-three-text": "^0.47.2", + "utility-types": "^3.10.0", + "zustand": "^3.5.13" + } + }, + "@react-three/fiber": { + "version": "8.13.6", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.13.6.tgz", + "integrity": "sha512-V49lldHcbsC7PMnnf4aYrMpHPQe8R7hJYL0AEFjqwioY0nkwga9A+Jx6lCLVG02DF03xwCfJZv5cjZCChffsWg==", + "requires": { + "@babel/runtime": "^7.17.8", + "@types/react-reconciler": "^0.26.7", + "its-fine": "^1.0.6", + "react-reconciler": "^0.27.0", + "react-use-measure": "^2.1.1", + "scheduler": "^0.21.0", + "suspend-react": "^0.1.3", + "zustand": "^3.7.1" + }, + "dependencies": { + "scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "requires": { + "loose-envify": "^1.1.0" + } + } + } + }, "@remix-run/router": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.0.tgz", @@ -16239,6 +16974,11 @@ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" }, + "@tweenjs/tween.js": { + "version": "18.6.4", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", + "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==" + }, "@types/aria-query": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", @@ -16294,6 +17034,11 @@ "@types/ms": "*" } }, + "@types/draco3d": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.2.tgz", + "integrity": "sha512-goh23EGr6CLV6aKPwN1p8kBD/7tT5V/bLpToSbarKrwVejqNrspVrv8DhliteYkkhZYrlq/fwKZRRUzH4XN88w==" + }, "@types/eslint": { "version": "8.4.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", @@ -16453,6 +17198,11 @@ "integrity": "sha512-FgD3NtTAKvyMmD44T07zz2fEf+OKwutgBCEVM8GcvMGVGaDktiLNTDvPwC/LUe3PinMW+X6CuLOF2Ui1mAlSXg==", "dev": true }, + "@types/offscreencanvas": { + "version": "2019.7.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", + "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==" + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -16519,6 +17269,14 @@ } } }, + "@types/react-reconciler": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", + "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", + "requires": { + "@types/react": "*" + } + }, "@types/react-router": { "version": "5.1.19", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.19.tgz", @@ -16568,6 +17326,24 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-9w+a7bR8PeB0dCT/HBULU2fMqf6BAzvKbxFboYhmDtDkKPiyXYbjoe2auwsXlEFI7CFNMF1dCv3dFH5Poy9R1w==" + }, + "@types/three": { + "version": "0.154.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.154.0.tgz", + "integrity": "sha512-IioqpGhch6FdLDh4zazRn3rXHj6Vn2nVOziJdXVbJFi9CaI65LtP9qqUtpzbsHK2Ezlox8NtsLNHSw3AQzucjA==", + "requires": { + "@tweenjs/tween.js": "~18.6.4", + "@types/stats.js": "*", + "@types/webxr": "*", + "fflate": "~0.6.9", + "lil-gui": "~0.17.0", + "meshoptimizer": "~0.18.1" + } + }, "@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", @@ -16584,6 +17360,11 @@ "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.46.tgz", "integrity": "sha512-ki0OmbjSdAEfvmy5AYWFpMkRsPW+6h4ibQ4tzk8SJsS9dkrrD3B/U1eVvdNNWxAzntjq6o2sjSia6UBCoPH+Yg==" }, + "@types/webxr": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.2.tgz", + "integrity": "sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==" + }, "@types/yargs": { "version": "17.0.19", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.19.tgz", @@ -16704,6 +17485,19 @@ "eslint-visitor-keys": "^2.0.0" } }, + "@use-gesture/core": { + "version": "10.2.27", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.2.27.tgz", + "integrity": "sha512-V4XV7hn9GAD2MYu8yBBVi5iuWBsAMfjPRMsEVzoTNGYH72tf0kFP+OKqGKc8YJFQIJx6yj+AOqxmEHOmx2/MEA==" + }, + "@use-gesture/react": { + "version": "10.2.27", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.2.27.tgz", + "integrity": "sha512-7E5vnWCxeslWlxwZ8uKIcnUZVMTRMZ8cvSnLLKF1NkyNb3PnNiAzoXM4G1vTKJKRhgOTeI6wK1YsEpwo9ABV5w==", + "requires": { + "@use-gesture/core": "10.2.27" + } + }, "@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -17146,6 +17940,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "bidi-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.2.tgz", + "integrity": "sha512-rzSy/k7WdX5zOyeHHCOixGXbCHkyogkxPKL2r8QtzHmVQDiWCXUWa18bLdMWT9CYMLOYTjWpTHawuev2ouYJVw==", + "requires": { + "require-from-string": "^2.0.2" + } + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -17233,6 +18035,12 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "camera-controls": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-2.7.0.tgz", + "integrity": "sha512-HONMoMYHieOCQOoweS639bdWHP/P/fvVGR08imnECGVUp04mqGfsX/zp1ZufLeiAA5hA6i1JhP6SrnOwh01C0w==", + "requires": {} + }, "caniuse-lite": { "version": "1.0.30001439", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz", @@ -17275,6 +18083,19 @@ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" }, + "chevrotain": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", + "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", + "requires": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -17464,6 +18285,11 @@ "whatwg-url": "^11.0.0" } }, + "debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -17545,6 +18371,14 @@ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" }, + "detect-gpu": { + "version": "5.0.34", + "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.34.tgz", + "integrity": "sha512-iHYDY2iy6OVvJVmTXvMrp5+OROP0Q62qTlrsC7wl3kZmm6yVAoOhQx5cIIxTVEEIZe1M1aPVI3RgsbG/Z6/7PQ==", + "requires": { + "webgl-constants": "^1.1.1" + } + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -17603,6 +18437,11 @@ "webidl-conversions": "^7.0.0" } }, + "draco3d": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.6.tgz", + "integrity": "sha512-+3NaRjWktb5r61ZFoDejlykPEFKT5N/LkbXsaddlw6xNSXBanUYpFc2AXXpbJDilPHazcSreU/DpQIaxfX0NfQ==" + }, "electron-to-chromium": { "version": "1.4.284", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", @@ -18306,6 +19145,11 @@ "bser": "2.1.1" } }, + "fflate": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==" + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -18498,6 +19342,11 @@ "slash": "^3.0.0" } }, + "glsl-noise": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz", + "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==" + }, "goober": { "version": "2.1.13", "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.13.tgz", @@ -19164,6 +20013,24 @@ "istanbul-lib-report": "^3.0.0" } }, + "its-fine": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.1.1.tgz", + "integrity": "sha512-v1Ia1xl20KbuSGlwoaGsW0oxsw8Be+TrXweidxD9oT/1lAh6O3K3/GIM95Tt6WCiv6W+h2M7RB1TwdoAjQyyKw==", + "requires": { + "@types/react-reconciler": "^0.28.0" + }, + "dependencies": { + "@types/react-reconciler": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.2.tgz", + "integrity": "sha512-8tu6lHzEgYPlfDf/J6GOQdIc+gs+S2yAqlby3zTsB3SP2svlqTYe5fwZNtZyfactP74ShooP2vvi1BOp9ZemWw==", + "requires": { + "@types/react": "*" + } + } + } + }, "jest": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", @@ -21113,6 +21980,11 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, + "ktx-parse": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/ktx-parse/-/ktx-parse-0.4.5.tgz", + "integrity": "sha512-MK3FOody4TXbFf8Yqv7EBbySw7aPvEcPX++Ipt6Sox+/YMFvR5xaTyhfNSk1AEmMy+RYIw81ctN4IMxCB8OAlg==" + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -21129,6 +22001,11 @@ "type-check": "~0.4.0" } }, + "lil-gui": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz", + "integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==" + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -21160,6 +22037,16 @@ "p-locate": "^4.1.0" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.clamp": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash.clamp/-/lodash.clamp-4.0.3.tgz", + "integrity": "sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg==" + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -21178,6 +22065,16 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.omit": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", + "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==" + }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==" + }, "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -21221,6 +22118,12 @@ "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", "dev": true }, + "maath": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/maath/-/maath-0.6.0.tgz", + "integrity": "sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw==", + "requires": {} + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -21428,6 +22331,17 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, + "meshline": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.1.6.tgz", + "integrity": "sha512-8JZJOdaL5oz3PI/upG8JvP/5FfzYUOhrkJ8np/WKvXzl0/PZ2V9pqTvCIjSKv+w9ccg2xb+yyBhXAwt6ier3ug==", + "requires": {} + }, + "meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==" + }, "mhchemparser": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.1.1.tgz", @@ -21804,6 +22718,11 @@ "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==" }, + "mmd-parser": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mmd-parser/-/mmd-parser-1.0.4.tgz", + "integrity": "sha512-Qi0VCU46t2IwfGv5KF0+D/t9cizcDug7qnNoy9Ggk7aucp0tssV8IwTMkBlDbm+VqAf3cdQHTCARKSsuS2MYFg==" + }, "moo-color": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz", @@ -21941,6 +22860,15 @@ "mimic-fn": "^2.1.0" } }, + "opentype.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz", + "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==", + "requires": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + } + }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -22083,6 +23011,11 @@ "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-2.22.0.tgz", "integrity": "sha512-2b7w4CQI06px8HVpKpgZtfuoDjuCLA26VlgdnG71UDBrJvtCYvXb39H4ElNv+CA1bbD4S98KanpWPRqTqlxBZw==" }, + "potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==" + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -22206,6 +23139,14 @@ "loose-envify": "^1.1.0" } }, + "react-composer": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/react-composer/-/react-composer-5.0.3.tgz", + "integrity": "sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==", + "requires": { + "prop-types": "^15.6.0" + } + }, "react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -22242,6 +23183,30 @@ "vfile": "^5.0.0" } }, + "react-merge-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz", + "integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==" + }, + "react-reconciler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.27.0.tgz", + "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.21.0" + }, + "dependencies": { + "scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "requires": { + "loose-envify": "^1.1.0" + } + } + } + }, "react-router": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.11.0.tgz", @@ -22282,6 +23247,14 @@ "prop-types": "^15.6.2" } }, + "react-use-measure": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", + "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==", + "requires": { + "debounce": "^1.2.1" + } + }, "rechoir": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", @@ -22345,6 +23318,11 @@ "@babel/runtime": "^7.8.4" } }, + "regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==" + }, "regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -22535,8 +23513,7 @@ "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, "requires-port": { "version": "1.0.0", @@ -22853,6 +23830,16 @@ } } }, + "stats-gl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-1.0.4.tgz", + "integrity": "sha512-oxo13HHonoMWIYcrIu4xCk8IcFEFaqAOkMOMIyfvZFxNZzGy+jnW8sy0W3VfEjKQd5JX0Kp2KhePAKhtI6/TSw==" + }, + "stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==" + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -22874,6 +23861,11 @@ "strip-ansi": "^6.0.1" } }, + "string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -22927,6 +23919,12 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "suspend-react": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", + "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", + "requires": {} + }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -23034,6 +24032,40 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "three": { + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.155.0.tgz", + "integrity": "sha512-sNgCYmDijnIqkD/bMfk+1pHg3YzsxW7V2ChpuP6HCQ8NiZr3RufsXQr8M3SSUMjW4hG+sUk7YbyuY0DncaDTJQ==" + }, + "three-mesh-bvh": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.6.3.tgz", + "integrity": "sha512-xjuGLSI9nBATIsWcT/DnnNma5xXYyvBiXfUbhGLAFqItOlOKYF5JWsUOX+cuSAnSWovEoHzd5Emx23qKiByrlw==", + "requires": {} + }, + "three-stdlib": { + "version": "2.23.13", + "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.23.13.tgz", + "integrity": "sha512-WU4XHs4E6szAyoREzTs5bUAc1WFadiz5jRjNlA7aIIqf+raT4jfA320P0XmlyZz/wJwmpZnOuki4kAFsmadkTQ==", + "requires": { + "@types/draco3d": "^1.4.0", + "@types/offscreencanvas": "^2019.6.4", + "@types/webxr": "^0.5.2", + "chevrotain": "^10.1.2", + "draco3d": "^1.4.1", + "fflate": "^0.6.9", + "ktx-parse": "^0.4.5", + "mmd-parser": "^1.0.4", + "opentype.js": "^1.3.3", + "potpack": "^1.0.1", + "zstddec": "^0.0.2" + } + }, + "tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -23078,6 +24110,28 @@ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" }, + "troika-three-text": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.47.2.tgz", + "integrity": "sha512-qylT0F+U7xGs+/PEf3ujBdJMYWbn0Qci0kLqI5BJG2kW1wdg4T1XSxneypnF05DxFqJhEzuaOR9S2SjiyknMng==", + "requires": { + "bidi-js": "^1.0.2", + "troika-three-utils": "^0.47.2", + "troika-worker-utils": "^0.47.2", + "webgl-sdf-generator": "1.1.1" + } + }, + "troika-three-utils": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.47.2.tgz", + "integrity": "sha512-/28plhCxfKtH7MSxEGx8e3b/OXU5A0xlwl+Sbdp0H8FXUHKZDoksduEKmjQayXYtxAyuUiCRunYIv/8Vi7aiyg==", + "requires": {} + }, + "troika-worker-utils": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.47.2.tgz", + "integrity": "sha512-mzss4MeyzUkYBppn4x5cdAqrhBHFEuVmMMgLMTyFV23x6GvQMyo+/R5E5Lsbrt7WSt5RfvewjcwD1DChRTA9lA==" + }, "trough": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", @@ -23361,6 +24415,11 @@ "requires-port": "^1.0.0" } }, + "utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==" + }, "uvu": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", @@ -23466,6 +24525,16 @@ "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==" }, + "webgl-constants": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", + "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==" + }, + "webgl-sdf-generator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz", + "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==" + }, "webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -23782,6 +24851,17 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true }, + "zstddec": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.0.2.tgz", + "integrity": "sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==" + }, + "zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "requires": {} + }, "zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index f00b41e9..a3711035 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,9 @@ "@mui/icons-material": "^5.11.6", "@mui/lab": "^5.0.0-alpha.128", "@mui/material": "^5.12.1", + "@react-three/drei": "^9.80.0", + "@react-three/fiber": "^8.13.6", + "@types/three": "^0.154.0", "axios": "^1.2.1", "notistack": "^3.0.1", "plotly.js-dist-min": "^2.22.0", @@ -35,7 +38,8 @@ "rehype-mathjax": "^4.0.2", "rehype-raw": "^6.1.1", "remark-gfm": "^3.0.1", - "remark-math": "^5.1.1" + "remark-math": "^5.1.1", + "three": "^0.155.0" }, "devDependencies": { "@babel/core": "^7.14.3", From 1fe1f6bb091db22f6253143386c9a835c11a201a Mon Sep 17 00:00:00 2001 From: hrntsm Date: Fri, 11 Aug 2023 20:43:30 +0900 Subject: [PATCH 05/45] Add rhino3dm support --- .../ts/components/ModelViewer.tsx | 64 +++++++++++++------ optuna_dashboard/ts/components/TrialList.tsx | 31 +++++++-- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/optuna_dashboard/ts/components/ModelViewer.tsx b/optuna_dashboard/ts/components/ModelViewer.tsx index 9695f139..5f0d00bc 100644 --- a/optuna_dashboard/ts/components/ModelViewer.tsx +++ b/optuna_dashboard/ts/components/ModelViewer.tsx @@ -3,6 +3,7 @@ import React, { 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" +import { Rhino3dmLoader } from "three/examples/jsm/loaders/3DMLoader" import { PerspectiveCamera } from "three" interface ModelViewerProps { @@ -10,6 +11,7 @@ interface ModelViewerProps { width: string height: string hasGizmo: boolean + filetype: string | undefined } function CustomGizmoHelper(): JSX.Element { @@ -24,23 +26,46 @@ function CustomGizmoHelper(): JSX.Element { } export function ModelViewer(props: ModelViewerProps): JSX.Element { - const [geometry, setGeometry] = useState() + const [geometry, setGeometry] = useState([]) const [modelSize, setModelSize] = useState() React.useEffect(() => { - const loader = new STLLoader() - loader.load(props.src, (geometry: THREE.BufferGeometry) => { - if (geometry) { - setGeometry(geometry) - geometry.computeBoundingBox() - if (geometry.boundingBox === null) { - setModelSize(new THREE.Vector3(10, 10, 10)) - } else { - const size = geometry.boundingBox.getSize(new THREE.Vector3()) - setModelSize(size) + 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) + } } - } - }) + }) + } 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 cameraPosition = modelSize ? [modelSize.x * 1.5, modelSize.y * 1.5, modelSize.z * 1.5] @@ -51,8 +76,8 @@ export function ModelViewer(props: ModelViewerProps): JSX.Element { : 45, aspect: window.innerWidth / window.innerHeight, near: 0.1, + position: new THREE.Vector3(...cameraPosition), far: 1000, - position: cameraPosition, } return ( @@ -67,11 +92,12 @@ export function ModelViewer(props: ModelViewerProps): JSX.Element { /> {props.hasGizmo && } - {geometry && ( - - - - )} + {geometry.length > 0 && + geometry.map((geo, index) => ( + + + + ))} ) } diff --git a/optuna_dashboard/ts/components/TrialList.tsx b/optuna_dashboard/ts/components/TrialList.tsx index 744db8d4..05a7bb2a 100644 --- a/optuna_dashboard/ts/components/TrialList.tsx +++ b/optuna_dashboard/ts/components/TrialList.tsx @@ -330,7 +330,9 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { const [openDeleteArtifactDialog, renderDeleteArtifactDialog] = useDeleteArtifactDialog() const [dragOver, setDragOver] = useState(false) - const [open3dModelViewer, setOpen3dModelViewer] = useState(false) + const [open3dModelViewer, setOpen3dModelViewer] = useState<{ + [key: string]: boolean + }>({}) const width = "200px" const height = "150px" @@ -442,7 +444,10 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { ) - } else if (a.filename.endsWith(".stl")) { + } else if ( + a.filename.endsWith(".stl") || + a.filename.endsWith(".3dm") + ) { return ( = ({ trial }) => { width={width} height={height} hasGizmo={false} + filetype={a.filename.split(".").pop()} /> = ({ trial }) => { {a.filename} { - setOpen3dModelViewer(true) + setOpen3dModelViewer(() => { + const obj = { ...open3dModelViewer } + obj[a.filename] = true + return obj + }) }} > { - setOpen3dModelViewer(false) + setOpen3dModelViewer(() => { + const obj = { ...open3dModelViewer } + obj[a.filename] = false + return obj + }) }} > = ({ trial }) => { width={`${innerWidth * 0.8}px`} height={`${innerHeight * 0.8}px`} hasGizmo={true} + filetype={a.filename.split(".").pop()} /> From bdaee91f9c2b2d1510790d398f0d2705698f7fc4 Mon Sep 17 00:00:00 2001 From: hrntsm Date: Sat, 12 Aug 2023 13:34:28 +0900 Subject: [PATCH 06/45] Update modal icon to FullscreenIcon --- optuna_dashboard/ts/components/TrialList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/optuna_dashboard/ts/components/TrialList.tsx b/optuna_dashboard/ts/components/TrialList.tsx index 05a7bb2a..9a5bbae9 100644 --- a/optuna_dashboard/ts/components/TrialList.tsx +++ b/optuna_dashboard/ts/components/TrialList.tsx @@ -35,7 +35,7 @@ import CheckBoxIcon from "@mui/icons-material/CheckBox" import UploadFileIcon from "@mui/icons-material/UploadFile" import DownloadIcon from "@mui/icons-material/Download" import DeleteIcon from "@mui/icons-material/Delete" -import OpenWithIcon from "@mui/icons-material/OpenWith" +import FullscreenIcon from "@mui/icons-material/Fullscreen" import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile" import StopCircleIcon from "@mui/icons-material/StopCircle" @@ -506,7 +506,7 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { }) }} > - + Date: Sat, 12 Aug 2023 13:54:34 +0900 Subject: [PATCH 07/45] Add model viewer target ext array --- optuna_dashboard/ts/components/TrialList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/optuna_dashboard/ts/components/TrialList.tsx b/optuna_dashboard/ts/components/TrialList.tsx index 9a5bbae9..8c70d554 100644 --- a/optuna_dashboard/ts/components/TrialList.tsx +++ b/optuna_dashboard/ts/components/TrialList.tsx @@ -336,6 +336,7 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { const width = "200px" const height = "150px" + const modelViewerTargetExt = ["stl", "3dm"] const inputRef = useRef(null) const handleClick: MouseEventHandler = () => { @@ -445,8 +446,7 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { ) } else if ( - a.filename.endsWith(".stl") || - a.filename.endsWith(".3dm") + modelViewerTargetExt.includes(a.filename.split(".").pop() || "") ) { return ( Date: Sat, 12 Aug 2023 21:27:42 +0900 Subject: [PATCH 08/45] Clean code --- .../ts/components/ModelViewer.tsx | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) 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 && From bf815ebd96efc095fb300186f1ac42a46a38f8c9 Mon Sep 17 00:00:00 2001 From: hrntsm Date: Sat, 12 Aug 2023 22:04:16 +0900 Subject: [PATCH 09/45] Fix width to show 3 icon in model viewer card --- optuna_dashboard/ts/components/TrialList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optuna_dashboard/ts/components/TrialList.tsx b/optuna_dashboard/ts/components/TrialList.tsx index 8c70d554..be124871 100644 --- a/optuna_dashboard/ts/components/TrialList.tsx +++ b/optuna_dashboard/ts/components/TrialList.tsx @@ -488,7 +488,7 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { p: theme.spacing(0.5, 0), flexGrow: 1, wordWrap: "break-word", - maxWidth: `calc(100% - ${theme.spacing(8)})`, + maxWidth: `calc(100% - ${theme.spacing(12)})`, }} > {a.filename} From 26fe527d5893193cfc324c1d9e24f1125c0b9e56 Mon Sep 17 00:00:00 2001 From: hrntsm Date: Sat, 12 Aug 2023 22:20:59 +0900 Subject: [PATCH 10/45] Fix camera settings --- .../ts/components/ModelViewer.tsx | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/optuna_dashboard/ts/components/ModelViewer.tsx b/optuna_dashboard/ts/components/ModelViewer.tsx index d1fc234d..22c10f28 100644 --- a/optuna_dashboard/ts/components/ModelViewer.tsx +++ b/optuna_dashboard/ts/components/ModelViewer.tsx @@ -39,6 +39,9 @@ export function ModelViewer(props: ModelViewerProps): JSX.Element { const [modelSize, setModelSize] = useState( new THREE.Vector3(10, 10, 10) ) + const [cameraSettings, setCameraSettings] = useState( + new THREE.PerspectiveCamera() + ) function handleLoadedGeometries(geometries: THREE.BufferGeometry[]) { setGeometry(geometries) @@ -64,25 +67,26 @@ export function ModelViewer(props: ModelViewerProps): JSX.Element { const meshes = object.children as THREE.Mesh[] const rhinoGeometries = meshes.map((mesh) => mesh.geometry) if (rhinoGeometries.length > 0) { - rhinoGeometries.forEach((rGeometry) => { - rGeometry.rotateX(-Math.PI / 4) + rhinoGeometries.forEach((rhinoGeometry) => { + rhinoGeometry.rotateX(-Math.PI / 4) }) handleLoadedGeometries(rhinoGeometries) } }) } + const maxModelSize = Math.max(modelSize.x, modelSize.y, modelSize.z) + const cameraSet = new THREE.PerspectiveCamera( + modelSize + ? Math.min( + 45, + Math.atan(modelSize.y / modelSize.z) * (180 / Math.PI) * 2 + ) + : 45, + window.innerWidth / window.innerHeight + ) + cameraSet.position.set(maxModelSize * 2, maxModelSize * 2, maxModelSize * 2) + setCameraSettings(cameraSet) }, []) - 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) - : 45, - aspect: window.innerWidth / window.innerHeight, - near: 0.1, - position: new THREE.Vector3(...cameraPosition), - far: 1000, - } return ( Date: Sat, 12 Aug 2023 22:24:40 +0900 Subject: [PATCH 11/45] Update handleLoadedGeometries --- optuna_dashboard/ts/components/ModelViewer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optuna_dashboard/ts/components/ModelViewer.tsx b/optuna_dashboard/ts/components/ModelViewer.tsx index 22c10f28..9f97ada1 100644 --- a/optuna_dashboard/ts/components/ModelViewer.tsx +++ b/optuna_dashboard/ts/components/ModelViewer.tsx @@ -43,7 +43,7 @@ export function ModelViewer(props: ModelViewerProps): JSX.Element { new THREE.PerspectiveCamera() ) - function handleLoadedGeometries(geometries: THREE.BufferGeometry[]) { + const handleLoadedGeometries = (geometries: THREE.BufferGeometry[]) => { setGeometry(geometries) const boundingBox = calculateBoundingBox(geometries) if (boundingBox !== null) { From 01411d9f30d9d2c3a70908008436e8b42d60640e Mon Sep 17 00:00:00 2001 From: c-bata Date: Mon, 14 Aug 2023 20:07:09 +0900 Subject: [PATCH 12/45] Add .readthedocs.yml --- .readthedocs.yaml | 18 ++++++++++++++++++ pyproject.toml | 7 +++++++ requirements.txt | 6 +----- 3 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..e86f1127 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,18 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py + +formats: all + +python: + install: + - method: pip + path: . + extra_requirements: + - docs \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index be4a18e0..6be5efcb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,13 @@ dependencies = [ ] dynamic = ["version"] +[project.optional-dependencies] +docs = [ + "streamlit", + "sphinx", + "sphinx_rtd_theme", +] + [project.scripts] optuna-dashboard = "optuna_dashboard._cli:main" diff --git a/requirements.txt b/requirements.txt index 492d883e..59a9c586 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,8 +18,4 @@ boto3 moto[s3] # visual regression tests -pytest-playwright - -# docs -sphinx -sphinx_rtd_theme \ No newline at end of file +pytest-playwright \ No newline at end of file From 60d6406d5c0cceb055bf24d4b3e7f4fc148308e6 Mon Sep 17 00:00:00 2001 From: c-bata Date: Mon, 14 Aug 2023 20:34:36 +0900 Subject: [PATCH 13/45] Add boto3 to readthedocs deps --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 6be5efcb..d9c0082c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ dynamic = ["version"] [project.optional-dependencies] docs = [ + "boto3", "streamlit", "sphinx", "sphinx_rtd_theme", From 0603d34324c082679dc2bd4e2869dbcb3b6db6d6 Mon Sep 17 00:00:00 2001 From: hrntsm Date: Wed, 16 Aug 2023 08:51:58 +0900 Subject: [PATCH 14/45] Follow review comments --- ...delViewer.tsx => ThreejsArtifactViewer.tsx} | 8 +++++--- optuna_dashboard/ts/components/TrialList.tsx | 18 +++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) rename optuna_dashboard/ts/components/{ModelViewer.tsx => ThreejsArtifactViewer.tsx} (94%) diff --git a/optuna_dashboard/ts/components/ModelViewer.tsx b/optuna_dashboard/ts/components/ThreejsArtifactViewer.tsx similarity index 94% rename from optuna_dashboard/ts/components/ModelViewer.tsx rename to optuna_dashboard/ts/components/ThreejsArtifactViewer.tsx index 9f97ada1..536f1d91 100644 --- a/optuna_dashboard/ts/components/ModelViewer.tsx +++ b/optuna_dashboard/ts/components/ThreejsArtifactViewer.tsx @@ -6,7 +6,7 @@ import { STLLoader } from "three/examples/jsm/loaders/STLLoader" import { Rhino3dmLoader } from "three/examples/jsm/loaders/3DMLoader" import { PerspectiveCamera } from "three" -interface ModelViewerProps { +interface ThreejsArtifactViewerProps { src: string width: string height: string @@ -14,7 +14,7 @@ interface ModelViewerProps { filetype: string | undefined } -function CustomGizmoHelper(): JSX.Element { +const CustomGizmoHelper: React.FC = () => { return ( { return boundingBox } -export function ModelViewer(props: ModelViewerProps): JSX.Element { +export const ThreejsArtifactViewer: React.FC = ( + props +) => { const [geometry, setGeometry] = useState([]) const [modelSize, setModelSize] = useState( new THREE.Vector3(10, 10, 10) diff --git a/optuna_dashboard/ts/components/TrialList.tsx b/optuna_dashboard/ts/components/TrialList.tsx index be124871..eb700c13 100644 --- a/optuna_dashboard/ts/components/TrialList.tsx +++ b/optuna_dashboard/ts/components/TrialList.tsx @@ -47,7 +47,7 @@ import { artifactIsAvailable } from "../state" import { actionCreator } from "../action" import { useDeleteArtifactDialog } from "./DeleteArtifactDialog" import { TrialFormWidgets } from "./TrialFormWidgets" -import { ModelViewer } from "./ModelViewer" +import { ThreejsArtifactViewer } from "./ThreejsArtifactViewer" const states: TrialState[] = [ "Complete", @@ -336,7 +336,6 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { const width = "200px" const height = "150px" - const modelViewerTargetExt = ["stl", "3dm"] const inputRef = useRef(null) const handleClick: MouseEventHandler = () => { @@ -446,7 +445,8 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { ) } else if ( - modelViewerTargetExt.includes(a.filename.split(".").pop() || "") + a.filename.endsWith(".stl") || + a.filename.endsWith(".3dm") ) { return ( = ({ trial }) => { alignItems: "center", }} > - = ({ trial }) => { onClick={() => { setOpen3dModelViewer(() => { const obj = { ...open3dModelViewer } - obj[a.filename] = true + obj[a.artifact_id] = true return obj }) }} @@ -510,14 +510,14 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { { setOpen3dModelViewer(() => { const obj = { ...open3dModelViewer } - obj[a.filename] = false + obj[a.artifact_id] = false return obj }) }} @@ -532,7 +532,7 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { borderRadius: "15px", }} > - Date: Tue, 15 Aug 2023 11:38:17 +0900 Subject: [PATCH 15/45] update api for preferential optimization --- optuna_dashboard/_serializer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/optuna_dashboard/_serializer.py b/optuna_dashboard/_serializer.py index 7fae23ea..04190f59 100644 --- a/optuna_dashboard/_serializer.py +++ b/optuna_dashboard/_serializer.py @@ -15,6 +15,7 @@ from . import _note as note from ._form_widget import get_form_widgets_json from ._named_objectives import get_objective_names from .artifact._backend import list_trial_artifacts +from .preferential._study import _SYSTEM_ATTR_PREFERENTIAL_STUDY if TYPE_CHECKING: @@ -143,6 +144,7 @@ def serialize_study_detail( serialized["union_user_attrs"] = [{"key": a[0], "sortable": a[1]} for a in union_user_attrs] serialized["has_intermediate_values"] = has_intermediate_values serialized["note"] = note.get_note_from_system_attrs(system_attrs, None) + serialized["is_preferential"] = system_attrs.get(_SYSTEM_ATTR_PREFERENTIAL_STUDY, False) objective_names = get_objective_names(system_attrs) if objective_names: serialized["objective_names"] = objective_names From 853dc7bb159cd1c21013a3032e5c42de08677f2f Mon Sep 17 00:00:00 2001 From: c-bata Date: Wed, 16 Aug 2023 10:43:46 +0900 Subject: [PATCH 16/45] Rewrite test_serializers in pytest-based style --- python_tests/test_serializers.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/python_tests/test_serializers.py b/python_tests/test_serializers.py index 4ea8c9bd..12181538 100644 --- a/python_tests/test_serializers.py +++ b/python_tests/test_serializers.py @@ -5,15 +5,15 @@ from unittest import TestCase from optuna_dashboard._serializer import serialize_attrs -class SerializeAttrsTestCase(TestCase): - def test_serialize_bytes(self) -> None: - serialized = serialize_attrs({"bytes": b"This is a bytes object."}) - self.assertEqual(serialized[0]["value"], "") +def test_serialize_bytes() -> None: + serialized = serialize_attrs({"bytes": b"This is a bytes object."}) + assert serialized[0]["value"] == "" - def test_serialize_dict(self) -> None: - serialized = serialize_attrs( - { - "key": {"foo": "bar"}, - } - ) - self.assertLessEqual(len(serialized), 1) + +def test_serialize_dict() -> None: + serialized = serialize_attrs( + { + "key": {"foo": "bar"}, + } + ) + assert len(serialized) <= 1 From 2597a508857d478790db36733c7ff7a283543ac2 Mon Sep 17 00:00:00 2001 From: c-bata Date: Wed, 16 Aug 2023 11:06:18 +0900 Subject: [PATCH 17/45] Add serializers tests for is_preferential attr --- python_tests/test_serializers.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/python_tests/test_serializers.py b/python_tests/test_serializers.py index 12181538..c8ffe8c7 100644 --- a/python_tests/test_serializers.py +++ b/python_tests/test_serializers.py @@ -1,8 +1,11 @@ from __future__ import annotations -from unittest import TestCase - +import optuna from optuna_dashboard._serializer import serialize_attrs +from optuna_dashboard._serializer import serialize_study_detail +from optuna_dashboard.preferential import create_study + +from optuna_dashboard._storage import get_study_summaries def test_serialize_bytes() -> None: @@ -17,3 +20,25 @@ def test_serialize_dict() -> None: } ) assert len(serialized) <= 1 + + +def test_get_study_detail_is_preferential() -> None: + storage = optuna.storages.InMemoryStorage() + study = create_study(storage=storage) + study_summaries = get_study_summaries(storage) + assert len(study_summaries) == 1 + + study_summary = study_summaries[0] + study_detail = serialize_study_detail(study_summary, [], study.trials, [], [], [], False) + assert study_detail["is_preferential"] + + +def test_get_study_detail_is_not_preferential() -> None: + storage = optuna.storages.InMemoryStorage() + study = optuna.create_study(storage=storage) + study_summaries = get_study_summaries(storage) + assert len(study_summaries) == 1 + + study_summary = study_summaries[0] + study_detail = serialize_study_detail(study_summary, [], study.trials, [], [], [], False) + assert not study_detail["is_preferential"] From 63471a786877535298fbc865daca4a97a1401aef Mon Sep 17 00:00:00 2001 From: c-bata Date: Wed, 16 Aug 2023 13:12:09 +0900 Subject: [PATCH 18/45] Fix lint errors --- python_tests/test_serializers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python_tests/test_serializers.py b/python_tests/test_serializers.py index c8ffe8c7..7a038b08 100644 --- a/python_tests/test_serializers.py +++ b/python_tests/test_serializers.py @@ -3,9 +3,8 @@ from __future__ import annotations import optuna from optuna_dashboard._serializer import serialize_attrs from optuna_dashboard._serializer import serialize_study_detail -from optuna_dashboard.preferential import create_study - from optuna_dashboard._storage import get_study_summaries +from optuna_dashboard.preferential import create_study def test_serialize_bytes() -> None: From e21159d40e6539bccb6b5557e4c1c6cab55e330a Mon Sep 17 00:00:00 2001 From: i23_moririn2528 Date: Tue, 15 Aug 2023 18:56:26 +0900 Subject: [PATCH 19/45] fix preferential function --- optuna_dashboard/preferential/_study.py | 33 +++++++++----- .../preferential/_system_attrs.py | 44 +++++++++---------- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/optuna_dashboard/preferential/_study.py b/optuna_dashboard/preferential/_study.py index 9bbd6d2f..0f2c7d84 100644 --- a/optuna_dashboard/preferential/_study.py +++ b/optuna_dashboard/preferential/_study.py @@ -31,16 +31,7 @@ class PreferentialStudy: @property def best_trials(self) -> list[FrozenTrial]: - ready_trials = [ - t - for t in self._study.get_trials( - deepcopy=False, states=(TrialState.COMPLETE, TrialState.RUNNING) - ) - if t.system_attrs.get(_SYSTEM_ATTR_COMPARISON_READY) is True - ] - preferences = get_preferences(self._study, deepcopy=False) - worse_numbers = {worse.number for _, worse in preferences} - return [copy.deepcopy(t) for t in ready_trials if t.number not in worse_numbers] + return get_best_trials(self._study._study_id, self._study._storage) @property def study_name(self) -> str: @@ -80,10 +71,12 @@ class PreferentialStudy: if not isinstance(worse_trials, list): worse_trials = [worse_trials] - report_preferences(self._study, [(b, w) for b in better_trials for w in worse_trials]) + report_preferences(self._study._study_id, self._study._storage, [(b.number, w.number) for b in better_trials for w in worse_trials]) def get_preferences(self, *, deepcopy: bool = True) -> list[tuple[FrozenTrial, FrozenTrial]]: - return get_preferences(self._study, deepcopy=deepcopy) + trials = self._study.get_trials(deepcopy=deepcopy) + preferences = get_preferences(self._study, trials) + return [(trials[better], trials[worse]) for (better, worse) in preferences] def set_user_attr(self, key: str, value: Any) -> None: self._study.set_user_attr(key, value) @@ -99,6 +92,22 @@ class PreferentialStudy: else: raise RuntimeError("Unexpected trial type") storage.set_trial_system_attr(trial_id, _SYSTEM_ATTR_COMPARISON_READY, True) + + +def get_best_trials(study_id: int, storage: optuna.storages.BaseStorage) -> list[FrozenTrial]: + ready_trials = [ + t + for t in storage.get_all_trials( + study_id, + deepcopy=False, + states=(TrialState.COMPLETE, TrialState.RUNNING), + ) + if t.system_attrs.get(_SYSTEM_ATTR_COMPARISON_READY) is True + ] + preferences = get_preferences(study_id, storage) + worse_numbers = {worse for _, worse in preferences} + return [copy.deepcopy(t) for t in ready_trials if t.number not in worse_numbers] + def create_study( diff --git a/optuna_dashboard/preferential/_system_attrs.py b/optuna_dashboard/preferential/_system_attrs.py index 70567655..214d7466 100644 --- a/optuna_dashboard/preferential/_system_attrs.py +++ b/optuna_dashboard/preferential/_system_attrs.py @@ -5,42 +5,42 @@ import uuid import optuna from optuna.trial import FrozenTrial from optuna.trial import TrialState - +from optuna.storages import BaseStorage +from .._storage import get_study_summary _SYSTEM_ATTR_PREFIX_PREFERENCE = "preference:values" def report_preferences( - study: optuna.Study, - preferences: list[tuple[FrozenTrial, FrozenTrial]], + study_id: int, + storage: BaseStorage, + preferences: list[tuple[int, int]], # element is number of trail ) -> None: key = _SYSTEM_ATTR_PREFIX_PREFERENCE + str(uuid.uuid4()) - study._storage.set_study_system_attr( - study_id=study._study_id, + storage.set_study_system_attr( + study_id=study_id, key=key, - value=[(better.number, worse.number) for better, worse in preferences], + value=preferences, ) - - values = [0 for _ in study.directions] + trials = storage.get_all_trials(study_id, deepcopy=False) + directions = storage.get_study_directions(study_id) + values = [0 for _ in directions] for better, worse in preferences: - for t in (better, worse): - study.tell( - t.number, - values=values, - state=TrialState.COMPLETE, - skip_if_finished=True, - ) + for number in (better, worse): + trial_id = trials[number]._trial_id + if storage.check_trial_is_updatable(trial_id, trials[number].state): + storage.set_trial_state_values(trial_id, TrialState.COMPLETE, values) def get_preferences( - study: optuna.Study, - *, - deepcopy: bool = True, -) -> list[tuple[FrozenTrial, FrozenTrial]]: + study_id: int, + storage: BaseStorage, +) -> list[tuple[int, int]]: preferences: list[tuple[int, int]] = [] - for k, v in study.system_attrs.items(): + summary = get_study_summary(storage, study_id) + system_attrs = getattr(summary, "system_attrs", {}) + for k, v in system_attrs.items(): if not k.startswith(_SYSTEM_ATTR_PREFIX_PREFERENCE): continue preferences.extend(v) # type: ignore - trials = study.get_trials(deepcopy=deepcopy) - return [(trials[better], trials[worse]) for (better, worse) in preferences] + return preferences From 6defaa055ba9383263216cd4385c3560a5098b10 Mon Sep 17 00:00:00 2001 From: i23_moririn2528 Date: Wed, 16 Aug 2023 09:55:07 +0900 Subject: [PATCH 20/45] fix updating trial state --- optuna_dashboard/preferential/_system_attrs.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/optuna_dashboard/preferential/_system_attrs.py b/optuna_dashboard/preferential/_system_attrs.py index 214d7466..02e89680 100644 --- a/optuna_dashboard/preferential/_system_attrs.py +++ b/optuna_dashboard/preferential/_system_attrs.py @@ -25,11 +25,11 @@ def report_preferences( trials = storage.get_all_trials(study_id, deepcopy=False) directions = storage.get_study_directions(study_id) values = [0 for _ in directions] - for better, worse in preferences: - for number in (better, worse): - trial_id = trials[number]._trial_id - if storage.check_trial_is_updatable(trial_id, trials[number].state): - storage.set_trial_state_values(trial_id, TrialState.COMPLETE, values) + updated_trials = {num for tpl in preferences for num in tpl} + for number in updated_trials: + trial_id = trials[number]._trial_id + if trials[number].state != TrialState.COMPLETE: + storage.set_trial_state_values(trial_id, TrialState.COMPLETE, values) def get_preferences( From 774a11665a4d9df4fc182f85b52feeed4e319eaf Mon Sep 17 00:00:00 2001 From: i23_moririn2528 Date: Wed, 16 Aug 2023 10:05:59 +0900 Subject: [PATCH 21/45] formatting --- optuna_dashboard/preferential/_study.py | 11 +++++++---- optuna_dashboard/preferential/_system_attrs.py | 6 ++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/optuna_dashboard/preferential/_study.py b/optuna_dashboard/preferential/_study.py index 0f2c7d84..585db64d 100644 --- a/optuna_dashboard/preferential/_study.py +++ b/optuna_dashboard/preferential/_study.py @@ -71,7 +71,11 @@ class PreferentialStudy: if not isinstance(worse_trials, list): worse_trials = [worse_trials] - report_preferences(self._study._study_id, self._study._storage, [(b.number, w.number) for b in better_trials for w in worse_trials]) + report_preferences( + self._study._study_id, + self._study._storage, + [(b.number, w.number) for b in better_trials for w in worse_trials], + ) def get_preferences(self, *, deepcopy: bool = True) -> list[tuple[FrozenTrial, FrozenTrial]]: trials = self._study.get_trials(deepcopy=deepcopy) @@ -92,8 +96,8 @@ class PreferentialStudy: else: raise RuntimeError("Unexpected trial type") storage.set_trial_system_attr(trial_id, _SYSTEM_ATTR_COMPARISON_READY, True) - - + + def get_best_trials(study_id: int, storage: optuna.storages.BaseStorage) -> list[FrozenTrial]: ready_trials = [ t @@ -107,7 +111,6 @@ def get_best_trials(study_id: int, storage: optuna.storages.BaseStorage) -> list preferences = get_preferences(study_id, storage) worse_numbers = {worse for _, worse in preferences} return [copy.deepcopy(t) for t in ready_trials if t.number not in worse_numbers] - def create_study( diff --git a/optuna_dashboard/preferential/_system_attrs.py b/optuna_dashboard/preferential/_system_attrs.py index 02e89680..2be921da 100644 --- a/optuna_dashboard/preferential/_system_attrs.py +++ b/optuna_dashboard/preferential/_system_attrs.py @@ -3,18 +3,20 @@ from __future__ import annotations import uuid import optuna +from optuna.storages import BaseStorage from optuna.trial import FrozenTrial from optuna.trial import TrialState -from optuna.storages import BaseStorage + from .._storage import get_study_summary + _SYSTEM_ATTR_PREFIX_PREFERENCE = "preference:values" def report_preferences( study_id: int, storage: BaseStorage, - preferences: list[tuple[int, int]], # element is number of trail + preferences: list[tuple[int, int]], # element is number of trail ) -> None: key = _SYSTEM_ATTR_PREFIX_PREFERENCE + str(uuid.uuid4()) storage.set_study_system_attr( From a9d96218186ca1d7609423ecaea49e909466722d Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Wed, 16 Aug 2023 10:25:56 +0900 Subject: [PATCH 22/45] lint --- optuna_dashboard/preferential/_study.py | 2 +- optuna_dashboard/preferential/_system_attrs.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/optuna_dashboard/preferential/_study.py b/optuna_dashboard/preferential/_study.py index 585db64d..71c00882 100644 --- a/optuna_dashboard/preferential/_study.py +++ b/optuna_dashboard/preferential/_study.py @@ -79,7 +79,7 @@ class PreferentialStudy: def get_preferences(self, *, deepcopy: bool = True) -> list[tuple[FrozenTrial, FrozenTrial]]: trials = self._study.get_trials(deepcopy=deepcopy) - preferences = get_preferences(self._study, trials) + preferences = get_preferences(self._study._study_id, self._study._storage) return [(trials[better], trials[worse]) for (better, worse) in preferences] def set_user_attr(self, key: str, value: Any) -> None: diff --git a/optuna_dashboard/preferential/_system_attrs.py b/optuna_dashboard/preferential/_system_attrs.py index 2be921da..3b73c12b 100644 --- a/optuna_dashboard/preferential/_system_attrs.py +++ b/optuna_dashboard/preferential/_system_attrs.py @@ -2,9 +2,7 @@ from __future__ import annotations import uuid -import optuna from optuna.storages import BaseStorage -from optuna.trial import FrozenTrial from optuna.trial import TrialState from .._storage import get_study_summary From 4862faa8f01585655202970889c442820b94242c Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Wed, 16 Aug 2023 10:36:52 +0900 Subject: [PATCH 23/45] fix test --- python_tests/preferential/test_system_attrs.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/python_tests/preferential/test_system_attrs.py b/python_tests/preferential/test_system_attrs.py index b9e22e19..10448d48 100644 --- a/python_tests/preferential/test_system_attrs.py +++ b/python_tests/preferential/test_system_attrs.py @@ -17,12 +17,13 @@ def test_report_and_get_preferences(storage_supplier: Callable[[], StorageSuppli study.ask() study.ask() - assert len(get_preferences(study)) == 0 + study_id = study._study_id + assert len(get_preferences(study_id, storage)) == 0 better, worse = study.trials[0], study.trials[1] - report_preferences(study, [(better, worse)]) - assert len(get_preferences(study)) == 1 + report_preferences(study_id, storage, [(better.number, worse.number)]) + assert len(get_preferences(study_id, storage)) == 1 - actual_better, actual_worse = get_preferences(study)[0] - assert actual_better.number == better.number - assert actual_worse.number == worse.number + actual_better, actual_worse = get_preferences(study_id, storage)[0] + assert actual_better == better.number + assert actual_worse == worse.number From 852dd3d16b2679100860961b839803b7ee28a901 Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Wed, 16 Aug 2023 13:25:33 +0900 Subject: [PATCH 24/45] add API to report preference --- optuna_dashboard/_app.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index ab26a1a6..53a98df7 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -38,6 +38,9 @@ from ._storage_url import get_storage from .artifact._backend import delete_all_artifacts from .artifact._backend import register_artifact_route from .artifact._backend_to_store import to_artifact_store +from .preferential._study import _SYSTEM_ATTR_PREFERENTIAL_STUDY +from .preferential._study import get_best_trials as get_best_preferential_trials +from .preferential._system_attrs import report_preferences if typing.TYPE_CHECKING: @@ -187,8 +190,12 @@ def create_app( return {"reason": f"study_id={study_id} is not found"} trials = get_trials(storage, study_id) + system_attrs = getattr(summary, "system_attrs", {}) + is_preferential = system_attrs.get(_SYSTEM_ATTR_PREFERENTIAL_STUDY, False) # TODO(c-bata): Cache best_trials - if len(summary.directions) == 1: + if is_preferential: + best_trials = get_best_preferential_trials(study_id, storage) + elif len(summary.directions) == 1: if len([t for t in trials if t.state == TrialState.COMPLETE]) == 0: best_trials = [] else: @@ -255,6 +262,29 @@ def create_app( response.status = 204 # No content return {} + @app.post("/api/studies//preference") + @json_api_view + def post_preference(study_id: int) -> dict[str, Any]: + try: + best_trials = [int(d) for d in request.json.get("best_trials", [])] + worst_trials = [int(d) for d in request.json.get("worst_trials", [])] + except ValueError: + response.status = 400 + return {"reason": "best_trials and worst_trials must be an array of integers."} + if len(best_trials) == 0 or len(worst_trials) == 0: + response.status = 400 # Bad request + return {"reason": "You need to set best_trials and worst_trials"} + + try: + preferences = [(best, worst) for best in best_trials for worst in worst_trials] + report_preferences(study_id, storage, preferences) + except Exception as e: + response.status = 500 + return {"reason": f"Internal server error: {e}"} + + response.status = 204 + return {} + @app.post("/api/trials//tell") @json_api_view def tell_trial(trial_id: int) -> dict[str, Any]: From 3f4e118b5d3f454d04ac0d53e54b1dccccbfaedc Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Wed, 16 Aug 2023 14:49:38 +0900 Subject: [PATCH 25/45] fix by review --- optuna_dashboard/_app.py | 8 ++------ optuna_dashboard/preferential/_system_attrs.py | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index 53a98df7..7aae8864 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -275,12 +275,8 @@ def create_app( response.status = 400 # Bad request return {"reason": "You need to set best_trials and worst_trials"} - try: - preferences = [(best, worst) for best in best_trials for worst in worst_trials] - report_preferences(study_id, storage, preferences) - except Exception as e: - response.status = 500 - return {"reason": f"Internal server error: {e}"} + preferences = [(best, worst) for best in best_trials for worst in worst_trials] + report_preferences(study_id, storage, preferences) response.status = 204 return {} diff --git a/optuna_dashboard/preferential/_system_attrs.py b/optuna_dashboard/preferential/_system_attrs.py index 3b73c12b..fdd9db35 100644 --- a/optuna_dashboard/preferential/_system_attrs.py +++ b/optuna_dashboard/preferential/_system_attrs.py @@ -14,7 +14,7 @@ _SYSTEM_ATTR_PREFIX_PREFERENCE = "preference:values" def report_preferences( study_id: int, storage: BaseStorage, - preferences: list[tuple[int, int]], # element is number of trail + preferences: list[tuple[int, int]], ) -> None: key = _SYSTEM_ATTR_PREFIX_PREFERENCE + str(uuid.uuid4()) storage.set_study_system_attr( From 10e6ab3d40a8819b258fb3187ad379f3fd0908bb Mon Sep 17 00:00:00 2001 From: c-bata Date: Wed, 16 Aug 2023 14:57:41 +0900 Subject: [PATCH 26/45] Clear in memory cache every after api calls in unit tests --- python_tests/wsgi_client.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/python_tests/wsgi_client.py b/python_tests/wsgi_client.py index 3a6b078e..970cea28 100644 --- a/python_tests/wsgi_client.py +++ b/python_tests/wsgi_client.py @@ -7,11 +7,21 @@ from typing import Union from bottle import Bottle +from optuna_dashboard._storage import trials_cache +from optuna_dashboard._storage import trials_cache_lock +from optuna_dashboard._storage import trials_last_fetched_at + if typing.TYPE_CHECKING: from _typeshed.wsgi import WSGIEnvironment +def clear_inmemory_cache() -> None: + with trials_cache_lock: + trials_cache.clear() + trials_last_fetched_at.clear() + + def create_wsgi_env( path: str, method: str, @@ -66,6 +76,8 @@ def send_request( headers = headers or {} queries = queries or {} env = create_wsgi_env(path, method, content_type, bytes_body, queries, headers) + + clear_inmemory_cache() response_body = b"" iterable_body = app(env, start_response) for b in iterable_body: From 4f99c7a1a0c5b43a713d524decf4090790c8d36f Mon Sep 17 00:00:00 2001 From: c-bata Date: Wed, 16 Aug 2023 15:00:19 +0900 Subject: [PATCH 27/45] Fix isort error --- python_tests/wsgi_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python_tests/wsgi_client.py b/python_tests/wsgi_client.py index 970cea28..84fe8aac 100644 --- a/python_tests/wsgi_client.py +++ b/python_tests/wsgi_client.py @@ -6,7 +6,6 @@ from typing import Optional from typing import Union from bottle import Bottle - from optuna_dashboard._storage import trials_cache from optuna_dashboard._storage import trials_cache_lock from optuna_dashboard._storage import trials_last_fetched_at From 5a35d229db5cc9c09369705cf9d57963dfdd2ce3 Mon Sep 17 00:00:00 2001 From: c-bata Date: Wed, 16 Aug 2023 15:21:10 +0900 Subject: [PATCH 28/45] Add JSON API tests for preferential best trials --- python_tests/test_api.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/python_tests/test_api.py b/python_tests/test_api.py index 4dd2b3d2..74b08d84 100644 --- a/python_tests/test_api.py +++ b/python_tests/test_api.py @@ -8,6 +8,7 @@ from optuna import get_all_study_summaries from optuna.study import StudyDirection from optuna_dashboard._app import create_app from optuna_dashboard._app import create_new_study +from optuna_dashboard.preferential import create_study from .wsgi_client import send_request @@ -99,6 +100,29 @@ class APITestCase(TestCase): ) self.assertEqual(status, 400) + def test_get_best_trials_of_preferential_study(self) -> None: + storage = optuna.storages.InMemoryStorage() + study = create_study(storage=storage) + for _ in range(3): + trial = study.ask() + study.mark_comparison_ready(trial) + study.report_preference(study.trials[0], study.trials[1]) + + app = create_app(storage) + study_id = study._study._study_id + status, _, body = send_request( + app, + f"/api/studies/{study_id}", + "GET", + content_type="application/json", + ) + self.assertEqual(status, 200) + + best_trials = json.loads(body)["best_trials"] + assert len(best_trials) == 2 + assert best_trials[0]["number"] == 0 + assert best_trials[1]["number"] == 2 + def test_create_study(self) -> None: for name, directions, expected_status in [ ("single-objective success", ["minimize"], 201), From 43fb89675092ed2667c8aed47d551249f8576564 Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Wed, 16 Aug 2023 15:23:41 +0900 Subject: [PATCH 29/45] fix lint error --- python_tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python_tests/test_api.py b/python_tests/test_api.py index 74b08d84..ea95904b 100644 --- a/python_tests/test_api.py +++ b/python_tests/test_api.py @@ -107,7 +107,7 @@ class APITestCase(TestCase): trial = study.ask() study.mark_comparison_ready(trial) study.report_preference(study.trials[0], study.trials[1]) - + app = create_app(storage) study_id = study._study._study_id status, _, body = send_request( From bbf04b6afe7e7fb8be2cae40cf6739cc07cf57ef Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Wed, 16 Aug 2023 15:33:34 +0900 Subject: [PATCH 30/45] add test for post_preference API --- python_tests/test_api.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/python_tests/test_api.py b/python_tests/test_api.py index ea95904b..c995c0e2 100644 --- a/python_tests/test_api.py +++ b/python_tests/test_api.py @@ -123,6 +123,34 @@ class APITestCase(TestCase): assert best_trials[0]["number"] == 0 assert best_trials[1]["number"] == 2 + def test_report_preference(self) -> None: + storage = optuna.storages.InMemoryStorage() + study = create_study(storage=storage) + for _ in range(3): + trial = study.ask() + study.mark_comparison_ready(trial) + + app = create_app(storage) + study_id = study._study._study_id + status, _, _ = send_request( + app, + f"/api/studies/{study_id}/preference", + "POST", + body=json.dumps({"best_trials": [0, 2], "worst_trials": [1]}), + content_type="application/json", + ) + self.assertEqual(status, 204) + + preferences = study.get_preferences() + preferences.sort(key=lambda x: (x[0].number, x[1].number)) + assert len(preferences) == 2 + better, worse = preferences[0] + assert better.number == 0 + assert worse.number == 1 + better, worse = preferences[1] + assert better.number == 2 + assert worse.number == 1 + def test_create_study(self) -> None: for name, directions, expected_status in [ ("single-objective success", ["minimize"], 201), From b2c455ccfcb754486b9a3a8cf5409b2d489918b4 Mon Sep 17 00:00:00 2001 From: c-bata Date: Wed, 16 Aug 2023 17:48:05 +0900 Subject: [PATCH 31/45] Uninstall types/react-router-dom --- package-lock.json | 55 ----------------------------------------------- package.json | 1 - 2 files changed, 56 deletions(-) diff --git a/package-lock.json b/package-lock.json index 60e7ef8a..fc9915bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,6 @@ "@types/plotly.js": "^2.12.11", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.10", - "@types/react-router-dom": "^5.3.3", "@types/react-syntax-highlighter": "^15.5.5", "@typescript-eslint/eslint-plugin": "^4.26.1", "@typescript-eslint/parser": "^4.26.1", @@ -3702,12 +3701,6 @@ "@types/unist": "*" } }, - "node_modules/@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "dev": true - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -3903,27 +3896,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-router": { - "version": "5.1.19", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.19.tgz", - "integrity": "sha512-Fv/5kb2STAEMT3wHzdKQK2z8xKq38EDIGVrutYLmQVVLe+4orDFquU52hQrULnEHinMKv9FSA6lf9+uNT1ITtA==", - "dev": true, - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*" - } - }, - "node_modules/@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "dev": true, - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" - } - }, "node_modules/@types/react-syntax-highlighter": { "version": "15.5.5", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.5.tgz", @@ -17082,12 +17054,6 @@ "@types/unist": "*" } }, - "@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "dev": true - }, "@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -17277,27 +17243,6 @@ "@types/react": "*" } }, - "@types/react-router": { - "version": "5.1.19", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.19.tgz", - "integrity": "sha512-Fv/5kb2STAEMT3wHzdKQK2z8xKq38EDIGVrutYLmQVVLe+4orDFquU52hQrULnEHinMKv9FSA6lf9+uNT1ITtA==", - "dev": true, - "requires": { - "@types/history": "^4.7.11", - "@types/react": "*" - } - }, - "@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "dev": true, - "requires": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" - } - }, "@types/react-syntax-highlighter": { "version": "15.5.5", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.5.tgz", diff --git a/package.json b/package.json index d596373d..82d587fe 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "@types/plotly.js": "^2.12.11", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.10", - "@types/react-router-dom": "^5.3.3", "@types/react-syntax-highlighter": "^15.5.5", "@typescript-eslint/eslint-plugin": "^4.26.1", "@typescript-eslint/parser": "^4.26.1", From 7f6154fb823d6de10748eba7fb785d3c163163d3 Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Thu, 17 Aug 2023 15:23:57 +0900 Subject: [PATCH 32/45] add is_preferential flag to study summary API --- optuna_dashboard/_serializer.py | 1 + python_tests/test_serializers.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/optuna_dashboard/_serializer.py b/optuna_dashboard/_serializer.py index 04190f59..4a804af8 100644 --- a/optuna_dashboard/_serializer.py +++ b/optuna_dashboard/_serializer.py @@ -108,6 +108,7 @@ def serialize_study_summary(summary: StudySummary) -> dict[str, Any]: "study_name": summary.study_name, "directions": [d.name.lower() for d in summary.directions], "user_attrs": serialize_attrs(summary.user_attrs), + "is_preferential": summary.system_attrs.get(_SYSTEM_ATTR_PREFERENTIAL_STUDY, False), } if summary.datetime_start is not None: diff --git a/python_tests/test_serializers.py b/python_tests/test_serializers.py index 7a038b08..a75db32d 100644 --- a/python_tests/test_serializers.py +++ b/python_tests/test_serializers.py @@ -3,6 +3,7 @@ from __future__ import annotations import optuna from optuna_dashboard._serializer import serialize_attrs from optuna_dashboard._serializer import serialize_study_detail +from optuna_dashboard._serializer import serialize_study_summary from optuna_dashboard._storage import get_study_summaries from optuna_dashboard.preferential import create_study @@ -41,3 +42,22 @@ def test_get_study_detail_is_not_preferential() -> None: study_summary = study_summaries[0] study_detail = serialize_study_detail(study_summary, [], study.trials, [], [], [], False) assert not study_detail["is_preferential"] + + +def test_get_study_summary_is_preferential() -> None: + storage = optuna.storages.InMemoryStorage() + create_study(storage=storage) + study_summaries = get_study_summaries(storage) + assert len(study_summaries) == 1 + + study_summary = serialize_study_summary(study_summaries[0]) + assert study_summary["is_preferential"] + + +def test_get_study_summary_is_not_preferential() -> None: + storage = optuna.storages.InMemoryStorage() + optuna.create_study(storage=storage) + study_summaries = get_study_summaries(storage) + assert len(study_summaries) == 1 + study_summary = serialize_study_summary(study_summaries[0]) + assert not study_summary["is_preferential"] From 9f4e333bc35ac5fdf48f1bf89bbec7c173684ed4 Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Thu, 17 Aug 2023 15:31:23 +0900 Subject: [PATCH 33/45] add is_preferential flag to StudySummary in typescript --- optuna_dashboard/ts/apiClient.ts | 6 ++++++ optuna_dashboard/ts/types/index.d.ts | 1 + 2 files changed, 7 insertions(+) diff --git a/optuna_dashboard/ts/apiClient.ts b/optuna_dashboard/ts/apiClient.ts index 07c825d0..95ac15c7 100644 --- a/optuna_dashboard/ts/apiClient.ts +++ b/optuna_dashboard/ts/apiClient.ts @@ -111,6 +111,7 @@ interface StudySummariesResponse { study_name: string directions: StudyDirection[] user_attrs: Attribute[] + is_preferential: boolean datetime_start?: string }[] } @@ -125,6 +126,7 @@ export const getStudySummariesAPI = (): Promise => { study_name: study.study_name, directions: study.directions, user_attrs: study.user_attrs, + is_preferential: study.is_preferential, datetime_start: study.datetime_start ? new Date(study.datetime_start) : undefined, @@ -139,6 +141,7 @@ interface CreateNewStudyResponse { study_name: string directions: StudyDirection[] user_attrs: Attribute[] + is_preferential: boolean datetime_start?: string } } @@ -160,6 +163,7 @@ export const createNewStudyAPI = ( directions: study_summary.directions, // best_trial: undefined, user_attrs: study_summary.user_attrs, + is_preferential: study_summary.is_preferential, datetime_start: study_summary.datetime_start ? new Date(study_summary.datetime_start) : undefined, @@ -178,6 +182,7 @@ type RenameStudyResponse = { study_name: string directions: StudyDirection[] user_attrs: Attribute[] + is_prefential: boolean datetime_start?: string } @@ -195,6 +200,7 @@ export const renameStudyAPI = ( study_name: res.data.study_name, directions: res.data.directions, user_attrs: res.data.user_attrs, + is_preferential: res.data.is_prefential, datetime_start: res.data.datetime_start ? new Date(res.data.datetime_start) : undefined, diff --git a/optuna_dashboard/ts/types/index.d.ts b/optuna_dashboard/ts/types/index.d.ts index b27be14e..349f7de2 100644 --- a/optuna_dashboard/ts/types/index.d.ts +++ b/optuna_dashboard/ts/types/index.d.ts @@ -121,6 +121,7 @@ type StudySummary = { study_name: string directions: StudyDirection[] user_attrs: Attribute[] + is_preferential: boolean datetime_start?: Date } From f0c475fe03bb03dc4861062368506ca84b8320b7 Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Thu, 17 Aug 2023 15:45:34 +0900 Subject: [PATCH 34/45] fix by review --- optuna_dashboard/_serializer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/optuna_dashboard/_serializer.py b/optuna_dashboard/_serializer.py index 4a804af8..a037290e 100644 --- a/optuna_dashboard/_serializer.py +++ b/optuna_dashboard/_serializer.py @@ -108,7 +108,9 @@ def serialize_study_summary(summary: StudySummary) -> dict[str, Any]: "study_name": summary.study_name, "directions": [d.name.lower() for d in summary.directions], "user_attrs": serialize_attrs(summary.user_attrs), - "is_preferential": summary.system_attrs.get(_SYSTEM_ATTR_PREFERENTIAL_STUDY, False), + "is_preferential": getattr(summary, "_system_attrs", {}).get( + _SYSTEM_ATTR_PREFERENTIAL_STUDY, False + ), } if summary.datetime_start is not None: From 049c27945a7dbe000707f23bb96bdd568de426f2 Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Thu, 17 Aug 2023 14:00:57 +0900 Subject: [PATCH 35/45] create ui for preferential optimization --- optuna_dashboard/ts/action.ts | 23 ++++ optuna_dashboard/ts/apiClient.ts | 17 +++ optuna_dashboard/ts/components/App.tsx | 9 ++ optuna_dashboard/ts/components/AppDrawer.tsx | 84 ++++++++---- .../ts/components/PreferentialTrials.tsx | 129 ++++++++++++++++++ .../ts/components/StudyDetail.tsx | 3 + optuna_dashboard/ts/types/index.d.ts | 1 + 7 files changed, 239 insertions(+), 27 deletions(-) create mode 100644 optuna_dashboard/ts/components/PreferentialTrials.tsx diff --git a/optuna_dashboard/ts/action.ts b/optuna_dashboard/ts/action.ts index 96fde064..7f343d0b 100644 --- a/optuna_dashboard/ts/action.ts +++ b/optuna_dashboard/ts/action.ts @@ -14,6 +14,7 @@ import { uploadArtifactAPI, getMetaInfoAPI, deleteArtifactAPI, + reportPreferenceAPI, } from "./apiClient" import { graphVisibilityState, @@ -582,6 +583,27 @@ export const actionCreator = () => { console.log(err) }) } + + const updatePreference = ( + study_id: number, + best_trials: number[], + worst_trials: number[] + ) => { + reportPreferenceAPI(study_id, best_trials, worst_trials) + .then(() => { + setTimeout(() => { + updateStudyDetail(study_id) + }, 1000) + }) + .catch((err) => { + const reason = err.response?.data.reason + enqueueSnackbar(`Failed to report preference. Reason: ${reason}`, { + variant: "error", + }) + console.log(err) + }) + } + return { updateAPIMeta, updateStudyDetail, @@ -601,6 +623,7 @@ export const actionCreator = () => { makeTrialComplete, makeTrialFail, saveTrialUserAttrs, + updatePreference, } } diff --git a/optuna_dashboard/ts/apiClient.ts b/optuna_dashboard/ts/apiClient.ts index 95ac15c7..63eb9408 100644 --- a/optuna_dashboard/ts/apiClient.ts +++ b/optuna_dashboard/ts/apiClient.ts @@ -66,6 +66,7 @@ interface StudyDetailResponse { union_user_attrs: AttributeSpec[] has_intermediate_values: boolean note: Note + is_preferential: boolean objective_names?: string[] form_widgets?: FormWidgets } @@ -101,6 +102,7 @@ export const getStudyDetailAPI = ( note: res.data.note, objective_names: res.data.objective_names, form_widgets: res.data.form_widgets, + is_preferential: res.data.is_preferential, } }) } @@ -307,3 +309,18 @@ export const getParamImportances = ( return res.data.param_importances }) } + +export const reportPreferenceAPI = ( + studyId: number, + best_trials: number[], + worst_trials: number[] +): Promise => { + return axiosInstance + .post(`/api/studies/${studyId}/preference`, { + best_trials: best_trials, + worst_trials: worst_trials, + }) + .then(() => { + return + }) +} diff --git a/optuna_dashboard/ts/components/App.tsx b/optuna_dashboard/ts/components/App.tsx index 0a861641..5651370f 100644 --- a/optuna_dashboard/ts/components/App.tsx +++ b/optuna_dashboard/ts/components/App.tsx @@ -96,6 +96,15 @@ export const App: FC = () => { /> } /> + + } + /> ({ width: drawerWidth, @@ -120,6 +126,8 @@ export const AppDrawer: FC<{ const action = actionCreator() const [open, setOpen] = useRecoilState(drawerOpenState) const reloadInterval = useRecoilValue(reloadIntervalState) + const studyDetail = + studyId !== undefined ? useStudyDetailValue(studyId) : null const styleListItem = { display: "block", @@ -181,32 +189,54 @@ export const AppDrawer: FC<{ {studyId !== undefined && page && ( - - - - - - - - - - - - - - - - + {studyDetail !== null && studyDetail.is_preferential && ( + + + + + + + + + )} + {studyDetail !== null && !studyDetail.is_preferential && ( + + + + + + + + + )} + {studyDetail !== null && !studyDetail.is_preferential && ( + + + + + + + + + )} void +}> = ({ trial, studyDetail, hideTrial }) => { + const theme = useTheme() + const action = actionCreator() + + return ( + + + Trial {trial.number} (trial_id={trial.trial_id}) + + + + Note + + + + ) +} + +type DisplayTrials = { + numbers: number[] + last_number: number +} + +export const PreferentialTrials: FC<{ studyDetail: StudyDetail | null }> = ({ + studyDetail, +}) => { + if (studyDetail === null || !studyDetail.is_preferential) { + return null + } + const [displayTrials, setDisplayTrials] = useState({ + numbers: studyDetail.best_trials.map((t) => t.number), + last_number: Math.max(...studyDetail.best_trials.map((t) => t.number), -1), + }) + const new_trails = studyDetail.best_trials.filter( + (t) => + displayTrials.last_number < t.number && + displayTrials.numbers.find((n) => n === t.number) === undefined + ) + if (new_trails.length > 0) { + setDisplayTrials((display) => { + const numbers = [...display.numbers] + new_trails.map((t) => { + const index = numbers.findIndex((n) => n === -1) + if (index === -1) { + numbers.push(t.number) + } else { + numbers[index] = t.number + } + }) + return { + numbers: numbers, + last_number: Math.max(...numbers, -1), + } + }) + } + + const hideTrial = (num: number) => { + setDisplayTrials((display) => { + const index = display.numbers.findIndex((n) => n === num) + if (index === -1) { + return display + } + const numbers = [...displayTrials.numbers] + numbers[index] = -1 + return { + numbers: numbers, + last_number: display.last_number, + } + }) + } + + return ( + + {displayTrials.numbers.map((t, index) => + t === -1 ? ( + + ) : ( + trial.number === t)!} + studyDetail={studyDetail} + hideTrial={() => { + hideTrial(t) + }} + /> + ) + )} + + ) +} diff --git a/optuna_dashboard/ts/components/StudyDetail.tsx b/optuna_dashboard/ts/components/StudyDetail.tsx index 255be7d2..6052c295 100644 --- a/optuna_dashboard/ts/components/StudyDetail.tsx +++ b/optuna_dashboard/ts/components/StudyDetail.tsx @@ -28,6 +28,7 @@ import { GraphSlice } from "./GraphSlice" import { GraphEdf } from "./GraphEdf" import { TrialList } from "./TrialList" import { StudyHistory } from "./StudyHistory" +import { PreferentialTrials } from "./PreferentialTrials" interface ParamTypes { studyId: string @@ -158,6 +159,8 @@ export const StudyDetail: FC<{ /> ) + } else if (page === "preference") { + content = } const toolbar = ( diff --git a/optuna_dashboard/ts/types/index.d.ts b/optuna_dashboard/ts/types/index.d.ts index 349f7de2..7f661525 100644 --- a/optuna_dashboard/ts/types/index.d.ts +++ b/optuna_dashboard/ts/types/index.d.ts @@ -193,6 +193,7 @@ type StudyDetail = { union_user_attrs: AttributeSpec[] has_intermediate_values: boolean note: Note + is_preferential: boolean objective_names?: string[] form_widgets?: FormWidgets } From ff5b1e3ed8676a7eed0d7be004b758a52bd526a4 Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Thu, 17 Aug 2023 16:30:13 +0900 Subject: [PATCH 36/45] hide direction when using preferential optimization --- optuna_dashboard/ts/components/App.tsx | 1 - optuna_dashboard/ts/components/StudyDetail.tsx | 5 ++++- optuna_dashboard/ts/components/StudyList.tsx | 10 ++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/optuna_dashboard/ts/components/App.tsx b/optuna_dashboard/ts/components/App.tsx index 5651370f..3f52e96b 100644 --- a/optuna_dashboard/ts/components/App.tsx +++ b/optuna_dashboard/ts/components/App.tsx @@ -110,7 +110,6 @@ export const App: FC = () => { element={ } /> diff --git a/optuna_dashboard/ts/components/StudyDetail.tsx b/optuna_dashboard/ts/components/StudyDetail.tsx index 6052c295..212400fe 100644 --- a/optuna_dashboard/ts/components/StudyDetail.tsx +++ b/optuna_dashboard/ts/components/StudyDetail.tsx @@ -42,7 +42,7 @@ export const useURLVars = (): number => { export const StudyDetail: FC<{ toggleColorMode: () => void - page: PageId + page?: PageId }> = ({ toggleColorMode, page }) => { const theme = useTheme() const action = actionCreator() @@ -82,6 +82,9 @@ export const StudyDetail: FC<{ }, [reloadInterval, studyDetail, page]) let content = null + if (page === undefined){ + page = studyDetail?.is_preferential ? "preference" : "history" + } if (page === "history") { content = } else if (page === "analytics") { diff --git a/optuna_dashboard/ts/components/StudyList.tsx b/optuna_dashboard/ts/components/StudyList.tsx index fee9de4b..c41c51cc 100644 --- a/optuna_dashboard/ts/components/StudyList.tsx +++ b/optuna_dashboard/ts/components/StudyList.tsx @@ -215,10 +215,12 @@ export const StudyList: FC<{ color="text.secondary" component="div" > - {"Direction: " + - study.directions - .map((d) => d.toString().toUpperCase()) - .join(", ")} + {study.is_preferential + ? "Preferential Optimization" + : "Direction: " + + study.directions + .map((d) => d.toString().toUpperCase()) + .join(", ")} From 95bc385dc75a316a21fa3e805b7a2f277d67259c Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Thu, 17 Aug 2023 16:34:05 +0900 Subject: [PATCH 37/45] format --- optuna_dashboard/ts/components/App.tsx | 6 +----- optuna_dashboard/ts/components/StudyDetail.tsx | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/optuna_dashboard/ts/components/App.tsx b/optuna_dashboard/ts/components/App.tsx index 3f52e96b..e5cc408d 100644 --- a/optuna_dashboard/ts/components/App.tsx +++ b/optuna_dashboard/ts/components/App.tsx @@ -107,11 +107,7 @@ export const App: FC = () => { /> - } + element={} /> Date: Thu, 17 Aug 2023 17:24:58 +0900 Subject: [PATCH 38/45] fix by lint --- .../ts/components/PreferentialTrials.tsx | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/optuna_dashboard/ts/components/PreferentialTrials.tsx b/optuna_dashboard/ts/components/PreferentialTrials.tsx index c637dbf3..080ad4b8 100644 --- a/optuna_dashboard/ts/components/PreferentialTrials.tsx +++ b/optuna_dashboard/ts/components/PreferentialTrials.tsx @@ -4,15 +4,18 @@ import { Typography, Box, Button, useTheme } from "@mui/material" import { TrialNote } from "./Note" import { actionCreator } from "../action" -const trialWidth = 500 - const PreferentialTrial: FC<{ - trial: Trial + trial?: Trial studyDetail: StudyDetail hideTrial: () => void }> = ({ trial, studyDetail, hideTrial }) => { const theme = useTheme() const action = actionCreator() + const trialWidth = 500 + + if (trial == undefined) { + return + } return ( @@ -110,20 +113,16 @@ export const PreferentialTrials: FC<{ studyDetail: StudyDetail | null }> = ({ return ( - {displayTrials.numbers.map((t, index) => - t === -1 ? ( - - ) : ( - trial.number === t)!} - studyDetail={studyDetail} - hideTrial={() => { - hideTrial(t) - }} - /> - ) - )} + {displayTrials.numbers.map((t, index) => ( + trial.number === t)} + studyDetail={studyDetail} + hideTrial={() => { + hideTrial(t) + }} + /> + ))} ) } From b284aa74bfe73100b78d20d2ea903242270d710c Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Thu, 17 Aug 2023 17:27:52 +0900 Subject: [PATCH 39/45] add ignorefile for eslint --- .eslintignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..92ff5e55 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +venv/**/*.ts +venv/**/*.js \ No newline at end of file From 6094c408d9b5f696e9951ebe4441be4427bad898 Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Thu, 17 Aug 2023 18:23:03 +0900 Subject: [PATCH 40/45] fix by review and little more --- optuna_dashboard/ts/action.ts | 18 +++---- optuna_dashboard/ts/components/App.tsx | 17 +----- optuna_dashboard/ts/components/AppDrawer.tsx | 52 +++++++------------ .../ts/components/StudyDetail.tsx | 30 ++++++----- 4 files changed, 43 insertions(+), 74 deletions(-) diff --git a/optuna_dashboard/ts/action.ts b/optuna_dashboard/ts/action.ts index 7f343d0b..c0fed1aa 100644 --- a/optuna_dashboard/ts/action.ts +++ b/optuna_dashboard/ts/action.ts @@ -589,19 +589,13 @@ export const actionCreator = () => { best_trials: number[], worst_trials: number[] ) => { - reportPreferenceAPI(study_id, best_trials, worst_trials) - .then(() => { - setTimeout(() => { - updateStudyDetail(study_id) - }, 1000) - }) - .catch((err) => { - const reason = err.response?.data.reason - enqueueSnackbar(`Failed to report preference. Reason: ${reason}`, { - variant: "error", - }) - console.log(err) + reportPreferenceAPI(study_id, best_trials, worst_trials).catch((err) => { + const reason = err.response?.data.reason + enqueueSnackbar(`Failed to report preference. Reason: ${reason}`, { + variant: "error", }) + console.log(err) + }) } return { diff --git a/optuna_dashboard/ts/components/App.tsx b/optuna_dashboard/ts/components/App.tsx index e5cc408d..78015049 100644 --- a/optuna_dashboard/ts/components/App.tsx +++ b/optuna_dashboard/ts/components/App.tsx @@ -69,15 +69,6 @@ export const App: FC = () => { /> } /> - - } - /> { } /> } /> - } - /> } diff --git a/optuna_dashboard/ts/components/AppDrawer.tsx b/optuna_dashboard/ts/components/AppDrawer.tsx index d6d10388..c08309bc 100644 --- a/optuna_dashboard/ts/components/AppDrawer.tsx +++ b/optuna_dashboard/ts/components/AppDrawer.tsx @@ -39,7 +39,7 @@ import { actionCreator } from "../action" const drawerWidth = 240 export type PageId = - | "history" + | "top" | "analytics" | "trialTable" | "trialList" @@ -128,6 +128,7 @@ export const AppDrawer: FC<{ const reloadInterval = useRecoilValue(reloadIntervalState) const studyDetail = studyId !== undefined ? useStudyDetailValue(studyId) : null + const is_preferential = studyDetail?.is_preferential ?? false const styleListItem = { display: "block", @@ -189,39 +190,22 @@ export const AppDrawer: FC<{ {studyId !== undefined && page && ( - {studyDetail !== null && studyDetail.is_preferential && ( - - - - - - - - - )} - {studyDetail !== null && !studyDetail.is_preferential && ( - - - - - - - - - )} + + + + {is_preferential ? : } + + + + {studyDetail !== null && !studyDetail.is_preferential && ( { export const StudyDetail: FC<{ toggleColorMode: () => void - page?: PageId + page: PageId }> = ({ toggleColorMode, page }) => { const theme = useTheme() const action = actionCreator() @@ -68,11 +68,16 @@ export const StudyDetail: FC<{ let interval = reloadInterval * 1000 // For Human-in-the-loop Optimization, the interval is set to 2 seconds - // when the number of trials is small and the page is "trialList". - if (page === "trialList" && nTrials < 100) { - interval = 2000 - } else if (page === "trialList" && nTrials < 500) { - interval = 5000 + // when the number of trials is small, and the page is "trialList" or top page of preferential. + if ( + (!studyDetail?.is_preferential && page === "trialList") || + (studyDetail?.is_preferential && page === "top") + ) { + if (nTrials < 100) { + interval = 2000 + } else if (nTrials < 500) { + interval = 5000 + } } const intervalId = setInterval(function () { @@ -82,11 +87,12 @@ export const StudyDetail: FC<{ }, [reloadInterval, studyDetail, page]) let content = null - if (page === undefined) { - page = studyDetail?.is_preferential ? "preference" : "history" - } - if (page === "history") { - content = + if (page === "top") { + content = studyDetail?.is_preferential ? ( + + ) : ( + + ) } else if (page === "analytics") { content = ( @@ -162,8 +168,6 @@ export const StudyDetail: FC<{ /> ) - } else if (page === "preference") { - content = } const toolbar = ( From 9c32be458871ba67140ca9d0ae4264c9d9691c65 Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Thu, 17 Aug 2023 16:14:16 +0900 Subject: [PATCH 41/45] uniform internal error style --- optuna_dashboard/_app.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index 7aae8864..256ddbb6 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -310,11 +310,7 @@ def create_app( response.status = 400 # Bad request return {"reason": "values attribute must be an array of numbers"} - try: - storage.set_trial_state_values(trial_id, state, values) - except Exception as e: - response.status = 500 - return {"reason": f"Internal server error: {e}"} + storage.set_trial_state_values(trial_id, state, values) response.status = 204 return {} @@ -327,12 +323,8 @@ def create_app( response.status = 400 # Bad request return {"reason": "user_attrs must be specified."} - try: - for key, val in user_attrs.items(): - storage.set_trial_user_attr(trial_id, key, val) - except Exception as e: - response.status = 500 - return {"reason": f"Internal server error: {e}"} + for key, val in user_attrs.items(): + storage.set_trial_user_attr(trial_id, key, val) response.status = 204 return {} From 7b322c8bb886dbd5f6ee852ce077fca484018fed Mon Sep 17 00:00:00 2001 From: keisuke-umezawa Date: Sat, 19 Aug 2023 16:06:20 +0900 Subject: [PATCH 42/45] Only show legend with multiple studies in history plot --- optuna_dashboard/ts/components/GraphHistory.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optuna_dashboard/ts/components/GraphHistory.tsx b/optuna_dashboard/ts/components/GraphHistory.tsx index 0b77e563..280c02fc 100644 --- a/optuna_dashboard/ts/components/GraphHistory.tsx +++ b/optuna_dashboard/ts/components/GraphHistory.tsx @@ -206,7 +206,7 @@ const plotHistory = ( title: xAxis === "number" ? "Trial" : "Time", type: xAxis === "number" ? "linear" : "date", }, - showlegend: true, + showlegend: historyPlotInfos.length === 1 ? false : true, template: mode === "dark" ? plotlyDarkTemplate : {}, } From e38232a341f06c0dd810b02b630a6fc4f828ac6b Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Mon, 21 Aug 2023 14:52:08 +0900 Subject: [PATCH 43/45] fix by review --- optuna_dashboard/ts/components/AppDrawer.tsx | 20 +++++++++---------- .../ts/components/StudyDetail.tsx | 10 +++++++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/optuna_dashboard/ts/components/AppDrawer.tsx b/optuna_dashboard/ts/components/AppDrawer.tsx index c08309bc..79f7d615 100644 --- a/optuna_dashboard/ts/components/AppDrawer.tsx +++ b/optuna_dashboard/ts/components/AppDrawer.tsx @@ -18,6 +18,7 @@ import { drawerOpenState, reloadIntervalState, useStudyDetailValue, + useStudySummaryValue, } from "../state" import { Link } from "react-router-dom" import AutoGraphIcon from "@mui/icons-material/AutoGraph" @@ -38,13 +39,7 @@ import { actionCreator } from "../action" const drawerWidth = 240 -export type PageId = - | "top" - | "analytics" - | "trialTable" - | "trialList" - | "note" - | "preference" +export type PageId = "top" | "analytics" | "trialTable" | "trialList" | "note" const openedMixin = (theme: Theme): CSSObject => ({ width: drawerWidth, @@ -128,7 +123,10 @@ export const AppDrawer: FC<{ const reloadInterval = useRecoilValue(reloadIntervalState) const studyDetail = studyId !== undefined ? useStudyDetailValue(studyId) : null - const is_preferential = studyDetail?.is_preferential ?? false + const studySummary = + studyId !== undefined ? useStudySummaryValue(studyId) : null + const isPreferential = + studyDetail?.is_preferential ?? studySummary?.is_preferential ?? false const styleListItem = { display: "block", @@ -198,15 +196,15 @@ export const AppDrawer: FC<{ selected={page === "top"} > - {is_preferential ? : } + {isPreferential ? : } - {studyDetail !== null && !studyDetail.is_preferential && ( + {!isPreferential && ( (reloadIntervalState) const studyName = useStudyName(studyId) + const isPreferential = + studySummary?.is_preferential ?? studyDetail?.is_preferential ?? false const title = studyName !== null ? `${studyName} (id=${studyId})` : `Study #${studyId}` @@ -70,8 +74,8 @@ export const StudyDetail: FC<{ // For Human-in-the-loop Optimization, the interval is set to 2 seconds // when the number of trials is small, and the page is "trialList" or top page of preferential. if ( - (!studyDetail?.is_preferential && page === "trialList") || - (studyDetail?.is_preferential && page === "top") + (!isPreferential && page === "trialList") || + (isPreferential && page === "top") ) { if (nTrials < 100) { interval = 2000 @@ -88,7 +92,7 @@ export const StudyDetail: FC<{ let content = null if (page === "top") { - content = studyDetail?.is_preferential ? ( + content = isPreferential ? ( ) : ( From d34bc9a8af795d854bb46d14a7a40f0ef06ea17b Mon Sep 17 00:00:00 2001 From: moririn2528 Date: Mon, 21 Aug 2023 15:19:36 +0900 Subject: [PATCH 44/45] fix design a little --- .../ts/components/PreferentialTrials.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/optuna_dashboard/ts/components/PreferentialTrials.tsx b/optuna_dashboard/ts/components/PreferentialTrials.tsx index 080ad4b8..9b57586d 100644 --- a/optuna_dashboard/ts/components/PreferentialTrials.tsx +++ b/optuna_dashboard/ts/components/PreferentialTrials.tsx @@ -29,6 +29,7 @@ const PreferentialTrial: FC<{ Trial {trial.number} (trial_id={trial.trial_id}) - - Note - = ({ } return ( - + {displayTrials.numbers.map((t, index) => ( Date: Mon, 21 Aug 2023 15:35:14 +0900 Subject: [PATCH 45/45] Update optuna_dashboard/ts/apiClient.ts Co-authored-by: c-bata --- optuna_dashboard/ts/apiClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optuna_dashboard/ts/apiClient.ts b/optuna_dashboard/ts/apiClient.ts index 63eb9408..d9881715 100644 --- a/optuna_dashboard/ts/apiClient.ts +++ b/optuna_dashboard/ts/apiClient.ts @@ -316,7 +316,7 @@ export const reportPreferenceAPI = ( worst_trials: number[] ): Promise => { return axiosInstance - .post(`/api/studies/${studyId}/preference`, { + .post(`/api/studies/${studyId}/preference`, { best_trials: best_trials, worst_trials: worst_trials, })