From 22b54261f07bc84f2f368cd2bf9584f73a3436df Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Fri, 23 Feb 2024 09:16:20 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=8F=20Formats=20app=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/index.html | 7 +- app/package.json | 99 +-- app/pnpm-lock.yaml | 36 +- app/src/App.svelte | 46 +- app/src/components/Controls.svelte | 25 +- app/src/components/Topbar.svelte | 125 +-- app/src/components/Views/Model.svelte | 277 ++++--- .../components/settings/Calibration.svelte | 105 +-- .../components/settings/Configuration.svelte | 39 +- app/src/components/settings/Info.svelte | 50 +- app/src/components/settings/Log.svelte | 29 +- app/src/lib/kinematic.ts | 754 +++++++++--------- app/src/lib/sceneBuilder.ts | 521 ++++++------ app/src/lib/services/file-service.ts | 113 ++- app/src/lib/services/index.ts | 4 +- app/src/lib/services/socket-service.ts | 163 ++-- app/src/lib/store.ts | 11 +- app/src/lib/utilities/buffer-utilities.ts | 27 +- app/src/lib/utilities/index.ts | 14 +- app/src/lib/utilities/location-utilities.ts | 10 +- app/src/lib/utilities/math-utilities.ts | 2 +- app/src/lib/utilities/model-utilities.ts | 66 +- app/src/lib/utilities/result/err.ts | 68 +- app/src/lib/utilities/result/index.ts | 6 +- app/src/lib/utilities/result/ok.ts | 72 +- app/src/lib/utilities/result/result.ts | 30 +- app/src/lib/utilities/string-utilities.ts | 10 +- app/src/lib/utilities/svelte-utilities.ts | 24 +- app/src/routes/Controller.svelte | 12 +- app/src/routes/Settings.svelte | 57 +- app/tailwind.config.js | 32 +- app/test/result.test.ts | 58 +- app/tsconfig.json | 5 +- app/vite.config.ts | 28 +- 34 files changed, 1525 insertions(+), 1400 deletions(-) diff --git a/app/index.html b/app/index.html index 7507292..f1fe239 100644 --- a/app/index.html +++ b/app/index.html @@ -1,9 +1,12 @@ - + - +
diff --git a/app/package.json b/app/package.json index 26da4c2..bc121f9 100644 --- a/app/package.json +++ b/app/package.json @@ -1,51 +1,52 @@ { - "name": "app", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "dev:mock": "vite --mode MOCK", - "build": "cross-env FOR_EMBEDDED=true vite build", - "build:web": "cross-env FOR_EMBEDDED=false vite build --mode WEB", - "preview": "vite preview", - "test": "vitest", - "check": "svelte-check --tsconfig ./tsconfig.json", - "format": "prettier --plugin-search-dir . --write ." - }, - "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.2", - "@tsconfig/svelte": "^5.0.2", - "@types/three": "^0.160.0", - "@typescript-eslint/eslint-plugin": "^6.20.0", - "@typescript-eslint/parser": "^6.20.0", - "autoprefixer": "^10.4.17", - "cross-env": "^7.0.3", - "husky": "^9.0.7", - "lint-staged": "^15.2.0", - "postcss": "^8.4.33", - "prettier": "3.2.4", - "svelte": "^4.2.9", - "svelte-check": "^3.6.3", - "svelte-hero-icons": "^5.0.0", - "tailwindcss": "^3.4.1", - "tslib": "^2.6.2", - "typescript": "^5.3.3", - "vite": "^5.0.12", - "vite-plugin-compression": "^0.5.1", - "vite-plugin-singlefile": "^1.0.0", - "vitest": "^1.3.1" - }, - "dependencies": { - "nipplejs": "^0.10.1", - "svelte-routing": "^2.11.0", - "three": "^0.160.1", - "urdf-loader": "^0.12.1", - "uzip": "^0.20201231.0", - "xacro-parser": "^0.3.9" - }, - "lint-staged": { - "*.js": "eslint --cache --fix", - "*.{js,css,md,ts,svelte}": "prettier --write" - } + "name": "app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "dev:mock": "vite --mode MOCK", + "build": "cross-env FOR_EMBEDDED=true vite build", + "build:web": "cross-env FOR_EMBEDDED=false vite build --mode WEB", + "preview": "vite preview", + "test": "vitest", + "check": "svelte-check --tsconfig ./tsconfig.json", + "format": "prettier --plugin-search-dir . --write ." + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.2", + "@tsconfig/svelte": "^5.0.2", + "@types/three": "^0.160.0", + "@typescript-eslint/eslint-plugin": "^6.20.0", + "@typescript-eslint/parser": "^6.20.0", + "autoprefixer": "^10.4.17", + "cross-env": "^7.0.3", + "husky": "^9.0.7", + "lint-staged": "^15.2.0", + "postcss": "^8.4.33", + "prettier": "3.2.4", + "svelte": "^4.2.9", + "svelte-check": "^3.6.3", + "svelte-hero-icons": "^5.0.0", + "tailwindcss": "^3.4.1", + "tslib": "^2.6.2", + "typescript": "^5.3.3", + "vite": "^5.0.12", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-singlefile": "^1.0.0", + "vitest": "^1.3.1" + }, + "dependencies": { + "nipplejs": "^0.10.1", + "prettier-plugin-svelte": "^3.2.1", + "svelte-routing": "^2.11.0", + "three": "^0.160.1", + "urdf-loader": "^0.12.1", + "uzip": "^0.20201231.0", + "xacro-parser": "^0.3.9" + }, + "lint-staged": { + "*.js": "eslint --cache --fix", + "*.{js,css,md,ts,svelte}": "prettier --write" + } } diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index e55ac68..9b33dc4 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -8,6 +8,9 @@ dependencies: nipplejs: specifier: ^0.10.1 version: 0.10.1 + prettier-plugin-svelte: + specifier: ^3.2.1 + version: 3.2.1(prettier@3.2.4)(svelte@4.2.9) svelte-routing: specifier: ^2.11.0 version: 2.11.0 @@ -107,7 +110,6 @@ packages: dependencies: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.18 - dev: true /@esbuild/aix-ppc64@0.19.12: resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} @@ -387,32 +389,26 @@ packages: '@jridgewell/set-array': 1.1.2 '@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/trace-mapping': 0.3.18 - dev: true /@jridgewell/resolve-uri@3.1.0: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} engines: {node: '>=6.0.0'} - dev: true /@jridgewell/set-array@1.1.2: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} engines: {node: '>=6.0.0'} - dev: true /@jridgewell/sourcemap-codec@1.4.14: resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - dev: true /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: true /@jridgewell/trace-mapping@0.3.18: resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} dependencies: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 - dev: true /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -589,7 +585,6 @@ packages: /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true /@types/json-schema@7.0.12: resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} @@ -812,7 +807,6 @@ packages: resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} hasBin: true - dev: true /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -879,7 +873,6 @@ packages: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: dequal: 2.0.3 - dev: true /array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} @@ -910,7 +903,6 @@ packages: resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==} dependencies: dequal: 2.0.3 - dev: true /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1054,7 +1046,6 @@ packages: acorn: 8.11.3 estree-walker: 3.0.3 periscopic: 3.1.0 - dev: true /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -1106,7 +1097,6 @@ packages: dependencies: mdn-data: 2.0.30 source-map-js: 1.0.2 - dev: true /cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} @@ -1145,7 +1135,6 @@ packages: /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - dev: true /detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} @@ -1327,7 +1316,6 @@ packages: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: '@types/estree': 1.0.5 - dev: true /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} @@ -1644,7 +1632,6 @@ packages: resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} dependencies: '@types/estree': 1.0.5 - dev: true /is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} @@ -1769,7 +1756,6 @@ packages: /locate-character@3.0.0: resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} - dev: true /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} @@ -1811,11 +1797,9 @@ packages: engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - dev: true /mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - dev: true /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -2051,7 +2035,6 @@ packages: '@types/estree': 1.0.5 estree-walker: 3.0.3 is-reference: 3.0.2 - dev: true /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -2161,11 +2144,20 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /prettier-plugin-svelte@3.2.1(prettier@3.2.4)(svelte@4.2.9): + resolution: {integrity: sha512-ENAPbIxASf2R79IZwgkG5sBdeNA9kLRlXVvKKmTXh79zWTy0KKoT86XO2pHrTitUPINd+iXWy12MRmgzKGVckA==} + peerDependencies: + prettier: ^3.0.0 + svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 + dependencies: + prettier: 3.2.4 + svelte: 4.2.9 + dev: false + /prettier@3.2.4: resolution: {integrity: sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==} engines: {node: '>=14'} hasBin: true - dev: true /pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} @@ -2364,7 +2356,6 @@ packages: /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} - dev: true /stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -2572,7 +2563,6 @@ packages: locate-character: 3.0.0 magic-string: 0.30.5 periscopic: 3.1.0 - dev: true /tailwindcss@3.4.1: resolution: {integrity: sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==} diff --git a/app/src/App.svelte b/app/src/App.svelte index 032d8ee..47456fc 100644 --- a/app/src/App.svelte +++ b/app/src/App.svelte @@ -4,39 +4,37 @@ import TopBar from './components/TopBar.svelte'; import socketService from '$lib/services/socket-service'; import Controller from './routes/Controller.svelte'; - import fileService from '$lib/services/file-service'; - import Settings from './routes/Settings.svelte'; + import { fileService } from '$lib/services'; + import Settings from './routes/Settings.svelte'; import { jointNames, model } from '$lib/store'; import { loadModelAsync } from '$lib/utilities'; import { socketLocation } from '$lib/utilities'; import type { Result } from '$lib/utilities/result'; - export let url = window.location.pathname + export let url = window.location.pathname; onMount(async () => { socketService.connect(socketLocation); - registerFetchIntercept() - const modelRes = await loadModelAsync('/spot_micro.urdf.xacro') - - if (modelRes.isOk()) { - const [urdf, JOINT_NAME] = modelRes.inner - jointNames.set(JOINT_NAME) - model.set(urdf) - } else { - console.error(modelRes.inner, {"exception": modelRes.exception}) - } + registerFetchIntercept(); + const modelRes = await loadModelAsync('/spot_micro.urdf.xacro'); + + if (modelRes.isOk()) { + const [urdf, JOINT_NAME] = modelRes.inner; + jointNames.set(JOINT_NAME); + model.set(urdf); + } else { + console.error(modelRes.inner, { exception: modelRes.exception }); + } }); - const registerFetchIntercept = () => { - const { fetch: originalFetch } = window; - window.fetch = async (...args) => { - const [resource, config] = args; - let file: Result; - file = await fileService.getFile(resource.toString()); - return file.isOk() - ? new Response(file.inner) - : originalFetch(resource, config) - }; - } + const registerFetchIntercept = () => { + const { fetch: originalFetch } = window; + window.fetch = async (...args) => { + const [resource, config] = args; + let file: Result; + file = await fileService.getFile(resource.toString()); + return file.isOk() ? new Response(file.inner) : originalFetch(resource, config); + }; + }; diff --git a/app/src/components/Controls.svelte b/app/src/components/Controls.svelte index ca8b75d..b4f9aed 100644 --- a/app/src/components/Controls.svelte +++ b/app/src/components/Controls.svelte @@ -25,12 +25,18 @@ }); left.on('move', (evt, data) => { - input.update(o => {o.left = data.vector; return o;}) + input.update((o) => { + o.left = data.vector; + return o; + }); throttle.throttle(updateData, throttle_timing); }); left.on('end', (evt, data) => { - input.update(o => {o.left = { x: 0, y: 0 }; return o;}) + input.update((o) => { + o.left = { x: 0, y: 0 }; + return o; + }); throttle.throttle(updateData, throttle_timing); }); @@ -43,12 +49,18 @@ }); right.on('move', (evt, data) => { - input.update(o => {o.right = data.vector; return o;}) + input.update((o) => { + o.right = data.vector; + return o; + }); throttle.throttle(updateData, throttle_timing); }); right.on('end', (evt, data) => { - input.update(o => {o.right = { x: 0, y: 0 }; return o;}) + input.update((o) => { + o.right = { x: 0, y: 0 }; + return o; + }); throttle.throttle(updateData, throttle_timing); }); }); @@ -62,11 +74,10 @@ data[5] = $input.height; data[6] = $input.speed; - outControllerData.set(data) + outControllerData.set(data); - if(!$emulateModel) socketService.send(data); + if (!$emulateModel) socketService.send(data); }; -
diff --git a/app/src/components/Topbar.svelte b/app/src/components/Topbar.svelte index b8951c4..8d0bec9 100644 --- a/app/src/components/Topbar.svelte +++ b/app/src/components/Topbar.svelte @@ -2,76 +2,83 @@ import socketService from '$lib/services/socket-service'; import { Icon, Bars3, XMark, Power, Battery100, Signal, SignalSlash } from 'svelte-hero-icons'; import { emulateModel } from '$lib/store'; - import { Link, useLocation } from 'svelte-routing' + import { Link, useLocation } from 'svelte-routing'; - const views = ["Virtual environment", "Robot camera"] - const modes = ["Drive", "Choreography"] + const views = ['Virtual environment', 'Robot camera']; + const modes = ['Drive', 'Choreography']; - const location = useLocation() + const location = useLocation(); let selected_view = views[0]; let selected_modes = modes[0]; - let settingOpen = window.location.pathname.includes('/settings') - let isConnected = socketService.isConnected + let settingOpen = window.location.pathname.includes('/settings'); + let isConnected = socketService.isConnected; - $: emulateModel.set(selected_view === views[0]) - $: settingOpen = $location.pathname.includes('/settings') + $: emulateModel.set(selected_view === views[0]); + $: settingOpen = $location.pathname.includes('/settings'); - const stop = () => { - if ($isConnected) { - socketService.send(JSON.stringify({type:"system/stop"})) - } - } + const stop = () => { + if ($isConnected) { + socketService.send(JSON.stringify({ type: 'system/stop' })); + } + }; -
-
- {#if settingOpen} - - - - {:else} - - - - {/if} - - - -
+
+ {#if settingOpen} + + + + {:else} + + + + {/if} + -
- - - -
-
- -
+ +
+ +
+ + + +
+
+ +
\ No newline at end of file + .topbar { + height: 50px; + } + .action_button { + border-radius: 4px; + width: 34px; + height: 34px; + display: flex; + justify-content: center; + align-items: center; + outline: 1px solid #52525b; + } + diff --git a/app/src/components/Views/Model.svelte b/app/src/components/Views/Model.svelte index 75c6479..fae33ed 100644 --- a/app/src/components/Views/Model.svelte +++ b/app/src/components/Views/Model.svelte @@ -1,149 +1,184 @@ - - + + {#if showStream} - - {/if} + +{/if} - \ No newline at end of file + diff --git a/app/src/components/settings/Calibration.svelte b/app/src/components/settings/Calibration.svelte index d73ab6b..5976770 100644 --- a/app/src/components/settings/Calibration.svelte +++ b/app/src/components/settings/Calibration.svelte @@ -2,43 +2,43 @@ import { onMount } from 'svelte'; import { jointNames } from '../../lib/store'; - type Servo = { - id: number; - name: string; - minPWM: number; - maxPWM: number; - pwmFor180: number; - }; + type Servo = { + id: number; + name: string; + minPWM: number; + maxPWM: number; + pwmFor180: number; + }; let servos: any[] = []; - onMount(() => { - jointNames.subscribe(data => { - servos = data.map((name:string, i:number) => { - return { - id: i, - name, - minPWM: 0, - maxPWM: 0, - pwmFor180: 0 - }; - }); - }) - }) - + onMount(() => { + jointNames.subscribe((data) => { + servos = data.map((name: string, i: number) => { + return { + id: i, + name, + minPWM: 0, + maxPWM: 0, + pwmFor180: 0 + }; + }); + }); + }); let selectedServo: number | null = null; - function updateServoValue(index: number, field: keyof Servo, value: number): void { - servos[index] = { ...servos[index], [field]: value }; - } + function updateServoValue(index: number, field: keyof Servo, value: number): void { + servos[index] = { ...servos[index], [field]: value }; + } - const formatServo = (servo:Servo) => { - const string = servo.name - const name = string.charAt(0).toUpperCase() + string.split('_').join(' ').slice(1); - return `${servo.id} ${name}` - } + const formatServo = (servo: Servo) => { + const string = servo.name; + const name = string.charAt(0).toUpperCase() + string.split('_').join(' ').slice(1); + return `${servo.id} ${name}`; + }; +
@@ -49,23 +49,36 @@
-{#if selectedServo !== null} -
-

Servo {formatServo(servos[selectedServo])} Calibration

- - updateServoValue(selectedServo, 'minPWM', Number(event.target.value))} /> + {#if selectedServo !== null} +
+

Servo {formatServo(servos[selectedServo])} Calibration

+ + updateServoValue(selectedServo, 'minPWM', Number(event.target.value))} + /> - - updateServoValue(selectedServo, 'maxPWM', Number(event.target.value))} /> + + updateServoValue(selectedServo, 'maxPWM', Number(event.target.value))} + /> - - updateServoValue(selectedServo, 'pwmFor180', Number(event.target.value))} /> -
-{/if} + + + updateServoValue(selectedServo, 'pwmFor180', Number(event.target.value))} + /> +
+ {/if}
diff --git a/app/src/components/settings/Configuration.svelte b/app/src/components/settings/Configuration.svelte index 00bab52..be8290f 100644 --- a/app/src/components/settings/Configuration.svelte +++ b/app/src/components/settings/Configuration.svelte @@ -1,26 +1,25 @@
-
- {#each Object.entries($settings) as entry} -
-
{entry[0]}:
-
{entry[1]}
-
- {/each} -
-
\ No newline at end of file +
+ {#each Object.entries($settings) as entry} +
+
{entry[0]}:
+
{entry[1]}
+
+ {/each} +
+
diff --git a/app/src/components/settings/Info.svelte b/app/src/components/settings/Info.svelte index a333378..4cd7b14 100644 --- a/app/src/components/settings/Info.svelte +++ b/app/src/components/settings/Info.svelte @@ -1,31 +1,31 @@ +
-
- {#each Object.entries($systemInfo ?? {}) as entry} -
-
{entry[0]}:
- {#if entry[0].includes("Size") || entry[0].includes("Free") || entry[0].includes("Min")} -
{humanFileSize(entry[1])}
- {:else} -
{entry[1]}
- {/if} -
- {/each} -
-
\ No newline at end of file +
+ {#each Object.entries($systemInfo ?? {}) as entry} +
+
{entry[0]}:
+ {#if entry[0].includes('Size') || entry[0].includes('Free') || entry[0].includes('Min')} +
{humanFileSize(entry[1])}
+ {:else} +
{entry[1]}
+ {/if} +
+ {/each} +
+ diff --git a/app/src/components/settings/Log.svelte b/app/src/components/settings/Log.svelte index 41228e6..d3a59a3 100644 --- a/app/src/components/settings/Log.svelte +++ b/app/src/components/settings/Log.svelte @@ -1,21 +1,20 @@
- {#each $log as entry} -
{entry}
- {/each} -
\ No newline at end of file + {#each $log as entry} +
{entry}
+ {/each} + diff --git a/app/src/lib/kinematic.ts b/app/src/lib/kinematic.ts index f1f444b..f239f9a 100644 --- a/app/src/lib/kinematic.ts +++ b/app/src/lib/kinematic.ts @@ -1,381 +1,393 @@ export default class Kinematic { - private l1: number; - private l2: number; - private l3: number; - private l4: number; - - private L: number; - private W: number; - - constructor() { - this.l1 = 50; - this.l2 = 20; - this.l3 = 120; - this.l4 = 155; - - this.L = 140; - this.W = 75; - } - - bodyIK(omega: number, phi: number, psi: number, xm: number, ym: number, zm: number): number[][][] { - const { cos, sin } = Math; - - const Rx: number[][] = [ - [1, 0, 0, 0], - [0, cos(omega), -sin(omega), 0], - [0, sin(omega), cos(omega), 0], - [0, 0, 0, 1], - ]; - const Ry: number[][] = [ - [cos(phi), 0, sin(phi), 0], - [0, 1, 0, 0], - [-sin(phi), 0, cos(phi), 0], - [0, 0, 0, 1], - ]; - const Rz: number[][] = [ - [cos(psi), -sin(psi), 0, 0], - [sin(psi), cos(psi), 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - ]; - const Rxyz: number[][] = this.matrixMultiply(this.matrixMultiply(Rx, Ry), Rz); - - const T: number[][] = [ - [0, 0, 0, xm], - [0, 0, 0, ym], - [0, 0, 0, zm], - [0, 0, 0, 0], - ]; - const Tm: number[][] = this.matrixAdd(T, Rxyz); - - const sHp = sin(Math.PI / 2); - const cHp = cos(Math.PI / 2); - const L = this.L; - const W = this.W; - - return [ - this.matrixMultiply(Tm, [ - [cHp, 0, sHp, L / 2], - [0, 1, 0, 0], - [-sHp, 0, cHp, W / 2], - [0, 0, 0, 1], - ]), - this.matrixMultiply(Tm, [ - [cHp, 0, sHp, L / 2], - [0, 1, 0, 0], - [-sHp, 0, cHp, -W / 2], - [0, 0, 0, 1], - ]), - this.matrixMultiply(Tm, [ - [cHp, 0, sHp, -L / 2], - [0, 1, 0, 0], - [-sHp, 0, cHp, W / 2], - [0, 0, 0, 1], - ]), - this.matrixMultiply(Tm, [ - [cHp, 0, sHp, -L / 2], - [0, 1, 0, 0], - [-sHp, 0, cHp, -W / 2], - [0, 0, 0, 1], - ]), - ]; - } - - private legIK(point: number[]): number[] { - const [x, y, z] = point; - const { atan2, cos, sin, sqrt, acos } = Math; - const { l1, l2, l3, l4 } = this; - - let F; + private l1: number; + private l2: number; + private l3: number; + private l4: number; - try { - F = sqrt(x ** 2 + y ** 2 - l1 ** 2); - if(isNaN(F)) throw new Error("F is NaN") - } catch (error) { - //console.log(error) - F = l1 - } - const G = F - l2; - const H = sqrt(G ** 2 + z ** 2); + private L: number; + private W: number; - const theta1 = -atan2(y, x) - atan2(F, -l1); - const D = (H ** 2 - l3 ** 2 - l4 ** 2) / (2 * l3 * l4); - let theta3: number - try { - theta3 = acos(D); - if(isNaN(theta3)) throw new Error("theta3 is NaN") - } catch (error) { - theta3 = 0 - } - const theta2 = atan2(z, G) - atan2(l4 * sin(theta3), l3 + l4 * cos(theta3)); + constructor() { + this.l1 = 50; + this.l2 = 20; + this.l3 = 120; + this.l4 = 155; - return [theta1, theta2, theta3]; - } - - matrixMultiply(a: number[][], b: number[][]): number[][] { - const result: number[][] = []; - - for (let i = 0; i < a.length; i++) { - const row: number[] = []; - - for (let j = 0; j < b[0].length; j++) { - let sum = 0; - - for (let k = 0; k < a[i].length; k++) { - sum += a[i][k] * b[k][j]; - } - - row.push(sum); - } - - result.push(row); - } - - return result; - } + this.L = 140; + this.W = 75; + } - multiplyVector(matrix: number[][], vector: number[]): number[] { - const rows = matrix.length; - const cols = matrix[0].length; - const vectorLength = vector.length; - - if (cols !== vectorLength) { - throw new Error("Matrix and vector dimensions do not match for multiplication."); - } - - const result = []; - - for (let i = 0; i < rows; i++) { - let sum = 0; - - for (let j = 0; j < cols; j++) { - sum += matrix[i][j] * vector[j]; - } - - result.push(sum); - } - - return result; - } - - private matrixAdd(a: number[][], b: number[][]): number[][] { - const result: number[][] = []; - - for (let i = 0; i < a.length; i++) { - const row: number[] = []; - - for (let j = 0; j < a[i].length; j++) { - row.push(a[i][j] + b[i][j]); - } - - result.push(row); - } - - return result; - } - - public calcLegPoints(angles: number[]): number[][] { - const [theta1, theta2, theta3] = angles; - const theta23 = theta2 + theta3; - - const T0: number[] = [0, 0, 0, 1]; - const T1: number[] = this.vectorAdd( - T0, - [-this.l1 * Math.cos(theta1), this.l1 * Math.sin(theta1), 0, 0] - ); - const T2: number[] = this.vectorAdd( - T1, - [-this.l2 * Math.sin(theta1), -this.l2 * Math.cos(theta1), 0, 0] - ); - const T3: number[] = this.vectorAdd( - T2, - [ - -this.l3 * Math.sin(theta1) * Math.cos(theta2), - -this.l3 * Math.cos(theta1) * Math.cos(theta2), - this.l3 * Math.sin(theta2), - 0, - ] - ); - const T4: number[] = this.vectorAdd( - T3, - [ - -this.l4 * Math.sin(theta1) * Math.cos(theta23), - -this.l4 * Math.cos(theta1) * Math.cos(theta23), - this.l4 * Math.sin(theta23), - 0, - ] - ); - - return [T0, T1, T2, T3, T4]; - } - - public calcIK(Lp: number[][], angles: number[], center: number[]): number[][] { - const [omega, phi, psi] = angles; - const [xm, ym, zm] = center; - - const [Tlf, Trf, Tlb, Trb] = this.bodyIK(omega, phi, psi, xm, ym, zm); - - const Ix: number[][] = [ - [-1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - ]; + bodyIK( + omega: number, + phi: number, + psi: number, + xm: number, + ym: number, + zm: number + ): number[][][] { + const { cos, sin } = Math; - return [ - this.legIK(this.multiplyVector(this.matrixInverse(Tlf), Lp[0])), - this.legIK(this.multiplyVector(Ix, this.multiplyVector(this.matrixInverse(Trf), Lp[1]))), - this.legIK(this.multiplyVector(this.matrixInverse(Tlb), Lp[2])), - this.legIK(this.multiplyVector(Ix, this.multiplyVector(this.matrixInverse(Trb), Lp[3]))), - ]; - } - - private vectorAdd(a: number[], b: number[]): number[] { - return a.map((val, index) => val + b[index]); - } - - private matrixInverse(matrix: number[][]): number[][] { - const det = this.determinant(matrix); - const adjugate = this.adjugate(matrix); - const scalar = 1 / det; - const inverse: number[][] = []; - - for (let i = 0; i < matrix.length; i++) { - const row: number[] = []; - - for (let j = 0; j < matrix[i].length; j++) { - row.push(adjugate[i][j] * scalar); - } - - inverse.push(row); - } - - return inverse; - } - - private determinant(matrix: number[][]): number { - if (matrix.length !== matrix[0].length) { - throw new Error("The matrix is not square."); - } - - if (matrix.length === 2) { - return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]; - } - - let det = 0; - - for (let i = 0; i < matrix.length; i++) { - const sign = i % 2 === 0 ? 1 : -1; - const subMatrix: number[][] = []; - - for (let j = 1; j < matrix.length; j++) { - const row: number[] = []; - - for (let k = 0; k < matrix.length; k++) { - if (k !== i) { - row.push(matrix[j][k]); - } - } - - subMatrix.push(row); - } - - det += sign * matrix[0][i] * this.determinant(subMatrix); - } - - return det; - } - - private adjugate(matrix: number[][]): number[][] { - if (matrix.length !== matrix[0].length) { - throw new Error("The matrix is not square."); - } - - const adjugate: number[][] = []; - - for (let i = 0; i < matrix.length; i++) { - const row: number[] = []; - - for (let j = 0; j < matrix[i].length; j++) { - const sign = (i + j) % 2 === 0 ? 1 : -1; - const subMatrix: number[][] = []; - - for (let k = 0; k < matrix.length; k++) { - if (k !== i) { - const subRow: number[] = []; - - for (let l = 0; l < matrix.length; l++) { - if (l !== j) { - subRow.push(matrix[k][l]); - } - } - - subMatrix.push(subRow); - } - } - - const cofactor = sign * this.determinant(subMatrix); - row.push(cofactor); - } - - adjugate.push(row); - } - - return this.transpose(adjugate); - } - - private transpose(matrix: number[][]): number[][] { - const transposed: number[][] = []; - - for (let i = 0; i < matrix.length; i++) { - const row: number[] = []; - - for (let j = 0; j < matrix[i].length; j++) { - row.push(matrix[j][i]); - } - - transposed.push(row); - } - - return transposed; - } - } + const Rx: number[][] = [ + [1, 0, 0, 0], + [0, cos(omega), -sin(omega), 0], + [0, sin(omega), cos(omega), 0], + [0, 0, 0, 1] + ]; + const Ry: number[][] = [ + [cos(phi), 0, sin(phi), 0], + [0, 1, 0, 0], + [-sin(phi), 0, cos(phi), 0], + [0, 0, 0, 1] + ]; + const Rz: number[][] = [ + [cos(psi), -sin(psi), 0, 0], + [sin(psi), cos(psi), 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]; + const Rxyz: number[][] = this.matrixMultiply(this.matrixMultiply(Rx, Ry), Rz); + const T: number[][] = [ + [0, 0, 0, xm], + [0, 0, 0, ym], + [0, 0, 0, zm], + [0, 0, 0, 0] + ]; + const Tm: number[][] = this.matrixAdd(T, Rxyz); + + const sHp = sin(Math.PI / 2); + const cHp = cos(Math.PI / 2); + const L = this.L; + const W = this.W; + + return [ + this.matrixMultiply(Tm, [ + [cHp, 0, sHp, L / 2], + [0, 1, 0, 0], + [-sHp, 0, cHp, W / 2], + [0, 0, 0, 1] + ]), + this.matrixMultiply(Tm, [ + [cHp, 0, sHp, L / 2], + [0, 1, 0, 0], + [-sHp, 0, cHp, -W / 2], + [0, 0, 0, 1] + ]), + this.matrixMultiply(Tm, [ + [cHp, 0, sHp, -L / 2], + [0, 1, 0, 0], + [-sHp, 0, cHp, W / 2], + [0, 0, 0, 1] + ]), + this.matrixMultiply(Tm, [ + [cHp, 0, sHp, -L / 2], + [0, 1, 0, 0], + [-sHp, 0, cHp, -W / 2], + [0, 0, 0, 1] + ]) + ]; + } + + private legIK(point: number[]): number[] { + const [x, y, z] = point; + const { atan2, cos, sin, sqrt, acos } = Math; + const { l1, l2, l3, l4 } = this; + + let F; + + try { + F = sqrt(x ** 2 + y ** 2 - l1 ** 2); + if (isNaN(F)) throw new Error('F is NaN'); + } catch (error) { + //console.log(error) + F = l1; + } + const G = F - l2; + const H = sqrt(G ** 2 + z ** 2); + + const theta1 = -atan2(y, x) - atan2(F, -l1); + const D = (H ** 2 - l3 ** 2 - l4 ** 2) / (2 * l3 * l4); + let theta3: number; + try { + theta3 = acos(D); + if (isNaN(theta3)) throw new Error('theta3 is NaN'); + } catch (error) { + theta3 = 0; + } + const theta2 = atan2(z, G) - atan2(l4 * sin(theta3), l3 + l4 * cos(theta3)); + + return [theta1, theta2, theta3]; + } + + matrixMultiply(a: number[][], b: number[][]): number[][] { + const result: number[][] = []; + + for (let i = 0; i < a.length; i++) { + const row: number[] = []; + + for (let j = 0; j < b[0].length; j++) { + let sum = 0; + + for (let k = 0; k < a[i].length; k++) { + sum += a[i][k] * b[k][j]; + } + + row.push(sum); + } + + result.push(row); + } + + return result; + } + + multiplyVector(matrix: number[][], vector: number[]): number[] { + const rows = matrix.length; + const cols = matrix[0].length; + const vectorLength = vector.length; + + if (cols !== vectorLength) { + throw new Error('Matrix and vector dimensions do not match for multiplication.'); + } + + const result = []; + + for (let i = 0; i < rows; i++) { + let sum = 0; + + for (let j = 0; j < cols; j++) { + sum += matrix[i][j] * vector[j]; + } + + result.push(sum); + } + + return result; + } + + private matrixAdd(a: number[][], b: number[][]): number[][] { + const result: number[][] = []; + + for (let i = 0; i < a.length; i++) { + const row: number[] = []; + + for (let j = 0; j < a[i].length; j++) { + row.push(a[i][j] + b[i][j]); + } + + result.push(row); + } + + return result; + } + + public calcLegPoints(angles: number[]): number[][] { + const [theta1, theta2, theta3] = angles; + const theta23 = theta2 + theta3; + + const T0: number[] = [0, 0, 0, 1]; + const T1: number[] = this.vectorAdd(T0, [ + -this.l1 * Math.cos(theta1), + this.l1 * Math.sin(theta1), + 0, + 0 + ]); + const T2: number[] = this.vectorAdd(T1, [ + -this.l2 * Math.sin(theta1), + -this.l2 * Math.cos(theta1), + 0, + 0 + ]); + const T3: number[] = this.vectorAdd(T2, [ + -this.l3 * Math.sin(theta1) * Math.cos(theta2), + -this.l3 * Math.cos(theta1) * Math.cos(theta2), + this.l3 * Math.sin(theta2), + 0 + ]); + const T4: number[] = this.vectorAdd(T3, [ + -this.l4 * Math.sin(theta1) * Math.cos(theta23), + -this.l4 * Math.cos(theta1) * Math.cos(theta23), + this.l4 * Math.sin(theta23), + 0 + ]); + + return [T0, T1, T2, T3, T4]; + } + + public calcIK(Lp: number[][], angles: number[], center: number[]): number[][] { + const [omega, phi, psi] = angles; + const [xm, ym, zm] = center; + + const [Tlf, Trf, Tlb, Trb] = this.bodyIK(omega, phi, psi, xm, ym, zm); + + const Ix: number[][] = [ + [-1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]; + + return [ + this.legIK(this.multiplyVector(this.matrixInverse(Tlf), Lp[0])), + this.legIK(this.multiplyVector(Ix, this.multiplyVector(this.matrixInverse(Trf), Lp[1]))), + this.legIK(this.multiplyVector(this.matrixInverse(Tlb), Lp[2])), + this.legIK(this.multiplyVector(Ix, this.multiplyVector(this.matrixInverse(Trb), Lp[3]))) + ]; + } + + private vectorAdd(a: number[], b: number[]): number[] { + return a.map((val, index) => val + b[index]); + } + + private matrixInverse(matrix: number[][]): number[][] { + const det = this.determinant(matrix); + const adjugate = this.adjugate(matrix); + const scalar = 1 / det; + const inverse: number[][] = []; + + for (let i = 0; i < matrix.length; i++) { + const row: number[] = []; + + for (let j = 0; j < matrix[i].length; j++) { + row.push(adjugate[i][j] * scalar); + } + + inverse.push(row); + } + + return inverse; + } + + private determinant(matrix: number[][]): number { + if (matrix.length !== matrix[0].length) { + throw new Error('The matrix is not square.'); + } + + if (matrix.length === 2) { + return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]; + } + + let det = 0; + + for (let i = 0; i < matrix.length; i++) { + const sign = i % 2 === 0 ? 1 : -1; + const subMatrix: number[][] = []; + + for (let j = 1; j < matrix.length; j++) { + const row: number[] = []; + + for (let k = 0; k < matrix.length; k++) { + if (k !== i) { + row.push(matrix[j][k]); + } + } + + subMatrix.push(row); + } + + det += sign * matrix[0][i] * this.determinant(subMatrix); + } + + return det; + } + + private adjugate(matrix: number[][]): number[][] { + if (matrix.length !== matrix[0].length) { + throw new Error('The matrix is not square.'); + } + + const adjugate: number[][] = []; + + for (let i = 0; i < matrix.length; i++) { + const row: number[] = []; + + for (let j = 0; j < matrix[i].length; j++) { + const sign = (i + j) % 2 === 0 ? 1 : -1; + const subMatrix: number[][] = []; + + for (let k = 0; k < matrix.length; k++) { + if (k !== i) { + const subRow: number[] = []; + + for (let l = 0; l < matrix.length; l++) { + if (l !== j) { + subRow.push(matrix[k][l]); + } + } + + subMatrix.push(subRow); + } + } + + const cofactor = sign * this.determinant(subMatrix); + row.push(cofactor); + } + + adjugate.push(row); + } + + return this.transpose(adjugate); + } + + private transpose(matrix: number[][]): number[][] { + const transposed: number[][] = []; + + for (let i = 0; i < matrix.length; i++) { + const row: number[] = []; + + for (let j = 0; j < matrix[i].length; j++) { + row.push(matrix[j][i]); + } + + transposed.push(row); + } + + return transposed; + } +} export class ForwardKinematics { - private l1: number; - private l2: number; - private l3: number; - private l4: number; - - constructor() { - this.l1 = 50; - this.l2 = 20; - this.l3 = 120; - this.l4 = 155; - } - - public calculateFootpoint(theta1: number, theta2: number, theta3: number): number[] { - const { cos, sin } = Math; - - const x = this.l1 * cos(theta1) + this.l2 * cos(theta1) + this.l3 * cos(theta1 + theta2) + this.l4 * cos(theta1 + theta2 + theta3); - const y = this.l1 * sin(theta1) + this.l2 * sin(theta1) + this.l3 * sin(theta1 + theta2) + this.l4 * sin(theta1 + theta2 + theta3); - const z = 0; - - return [x, y, z]; - } + private l1: number; + private l2: number; + private l3: number; + private l4: number; - public calculateFootpoints(angles: number[]): number[][] { - const footpoints: number[][] = []; - - for (let i = 0; i < angles.length; i += 3) { - const theta1 = angles[i]; - const theta2 = angles[i + 1]; - const theta3 = angles[i + 2]; - const footpoint = this.calculateFootpoint(theta1, theta2, theta3); - footpoints.push(footpoint); - } - - return footpoints; - } - } \ No newline at end of file + constructor() { + this.l1 = 50; + this.l2 = 20; + this.l3 = 120; + this.l4 = 155; + } + + public calculateFootpoint(theta1: number, theta2: number, theta3: number): number[] { + const { cos, sin } = Math; + + const x = + this.l1 * cos(theta1) + + this.l2 * cos(theta1) + + this.l3 * cos(theta1 + theta2) + + this.l4 * cos(theta1 + theta2 + theta3); + const y = + this.l1 * sin(theta1) + + this.l2 * sin(theta1) + + this.l3 * sin(theta1 + theta2) + + this.l4 * sin(theta1 + theta2 + theta3); + const z = 0; + + return [x, y, z]; + } + + public calculateFootpoints(angles: number[]): number[][] { + const footpoints: number[][] = []; + + for (let i = 0; i < angles.length; i += 3) { + const theta1 = angles[i]; + const theta2 = angles[i + 1]; + const theta3 = angles[i + 2]; + const footpoint = this.calculateFootpoint(theta1, theta2, theta3); + footpoints.push(footpoint); + } + + return footpoints; + } +} diff --git a/app/src/lib/sceneBuilder.ts b/app/src/lib/sceneBuilder.ts index 51c64fe..a1ffce8 100644 --- a/app/src/lib/sceneBuilder.ts +++ b/app/src/lib/sceneBuilder.ts @@ -1,293 +1,318 @@ -import { Mesh, - PerspectiveCamera, - PlaneGeometry, - Scene, - ShadowMaterial, - WebGLRenderer, - AmbientLight, - DirectionalLight, - PCFSoftShadowMap, - GridHelper, - ArrowHelper, - Vector3, - LoaderUtils, - Object3D, - FogExp2, - CanvasTexture, - type ColorRepresentation, - type WebGLRendererParameters, - MeshPhongMaterial, - EquirectangularReflectionMapping, - ACESFilmicToneMapping, - MathUtils, -} from "three"; +import { + Mesh, + PerspectiveCamera, + PlaneGeometry, + Scene, + ShadowMaterial, + WebGLRenderer, + AmbientLight, + DirectionalLight, + PCFSoftShadowMap, + GridHelper, + ArrowHelper, + Vector3, + LoaderUtils, + Object3D, + FogExp2, + CanvasTexture, + type ColorRepresentation, + type WebGLRendererParameters, + MeshPhongMaterial, + EquirectangularReflectionMapping, + ACESFilmicToneMapping, + MathUtils +} from 'three'; import { Sky } from 'three/addons/objects/Sky.js'; -import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; -import { type URDFMimicJoint } from "urdf-loader"; -import { PointerURDFDragControls } from 'urdf-loader/src/URDFDragControls' +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; +import { type URDFMimicJoint } from 'urdf-loader'; +import { PointerURDFDragControls } from 'urdf-loader/src/URDFDragControls'; -export const addScene = () => new Scene() +export const addScene = () => new Scene(); interface position { - x?: number, - y?: number, - z?: number + x?: number; + y?: number; + z?: number; } interface light { - color?: ColorRepresentation, - intensity?: number + color?: ColorRepresentation; + intensity?: number; } interface gridOptions { - divisions?: number, - size?: number, + divisions?: number; + size?: number; } interface arrowOptions { - origin:position, - direction:position, - length?:number, - color?:ColorRepresentation + origin: position; + direction: position; + length?: number; + color?: ColorRepresentation; } -type directionalLight = position & light +type directionalLight = position & light; -type gridHelperOptions = gridOptions & position +type gridHelperOptions = gridOptions & position; function calculateCurrentSunElevation() { - let now = new Date(); - let decimalTime = now.getHours() + now.getMinutes() / 60; - let normalizedTime = ((decimalTime - 6) % 12) / 6 - 1; - return 10 * Math.sin(normalizedTime * Math.PI); + let now = new Date(); + let decimalTime = now.getHours() + now.getMinutes() / 60; + let normalizedTime = (decimalTime % 12) / 6 - 1; + return 10 * Math.sin(normalizedTime * Math.PI); } export default class SceneBuilder { - public scene: Scene - public camera: PerspectiveCamera - public ground: Mesh - public renderer:WebGLRenderer - public controls:OrbitControls - public callback:Function - public gridHelper: GridHelper; - public model: Object3D - public liveStreamTexture: CanvasTexture - private fog:FogExp2 - private isLoaded:boolean = false - highlightMaterial: any; + public scene: Scene; + public camera: PerspectiveCamera; + public ground: Mesh; + public renderer: WebGLRenderer; + public controls: OrbitControls; + public callback: Function; + public gridHelper: GridHelper; + public model: Object3D; + public liveStreamTexture: CanvasTexture; + private fog: FogExp2; + private isLoaded: boolean = false; + highlightMaterial: any; - constructor() { - this.scene = new Scene() - if (this.scene.environment?.mapping) { - this.scene.environment.mapping = EquirectangularReflectionMapping; - } - return this - } + constructor() { + this.scene = new Scene(); + if (this.scene.environment?.mapping) { + this.scene.environment.mapping = EquirectangularReflectionMapping; + } + return this; + } - public addRenderer = (parameters?: WebGLRendererParameters) => { - this.renderer = new WebGLRenderer(parameters); - this.renderer.outputColorSpace = "srgb"; - this.renderer.shadowMap.enabled = true; - this.renderer.shadowMap.type = PCFSoftShadowMap; - this.renderer.toneMapping = ACESFilmicToneMapping; - this.renderer.toneMappingExposure = 0.85; - document.body.appendChild(this.renderer.domElement); - return this - } + public addRenderer = (parameters?: WebGLRendererParameters) => { + this.renderer = new WebGLRenderer(parameters); + this.renderer.outputColorSpace = 'srgb'; + this.renderer.shadowMap.enabled = true; + this.renderer.shadowMap.type = PCFSoftShadowMap; + this.renderer.toneMapping = ACESFilmicToneMapping; + this.renderer.toneMappingExposure = 0.85; + document.body.appendChild(this.renderer.domElement); + return this; + }; - public addSky = () => { - const sky = new Sky(); - sky.scale.setScalar(450000); - this.scene.add(sky); - const effectController = { - turbidity: 10, - rayleigh: 3, - mieCoefficient: 0.005, - mieDirectionalG: 0.7, - elevation: calculateCurrentSunElevation(), - azimuth: 180, - exposure: this.renderer.toneMappingExposure - }; - const uniforms = sky.material.uniforms; - uniforms['turbidity'].value = effectController.turbidity; - uniforms['rayleigh'].value = effectController.rayleigh; - uniforms['mieCoefficient'].value = effectController.mieCoefficient; - uniforms['mieDirectionalG'].value = effectController.mieDirectionalG; - this.renderer.toneMappingExposure = 0.5; - const phi = MathUtils.degToRad( 90 - effectController.elevation ); - const theta = MathUtils.degToRad( effectController.azimuth ); - const sun = new Vector3(); + public addSky = () => { + const sky = new Sky(); + sky.scale.setScalar(450000); + this.scene.add(sky); + const effectController = { + turbidity: 10, + rayleigh: 3, + mieCoefficient: 0.005, + mieDirectionalG: 0.7, + elevation: calculateCurrentSunElevation(), + azimuth: 180, + exposure: this.renderer.toneMappingExposure + }; + const uniforms = sky.material.uniforms; + uniforms['turbidity'].value = effectController.turbidity; + uniforms['rayleigh'].value = effectController.rayleigh; + uniforms['mieCoefficient'].value = effectController.mieCoefficient; + uniforms['mieDirectionalG'].value = effectController.mieDirectionalG; + this.renderer.toneMappingExposure = 0.5; + const phi = MathUtils.degToRad(90 - effectController.elevation); + const theta = MathUtils.degToRad(effectController.azimuth); + const sun = new Vector3(); - sun.setFromSphericalCoords( 1, phi, theta ); - uniforms[ 'sunPosition' ].value.copy( sun ); - return this - } + sun.setFromSphericalCoords(1, phi, theta); + uniforms['sunPosition'].value.copy(sun); + return this; + }; - public addPerspectiveCamera = (options:position) => { - this.camera = new PerspectiveCamera(); - this.camera.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0); - this.scene.add(this.camera); - return this - } + public addPerspectiveCamera = (options: position) => { + this.camera = new PerspectiveCamera(); + this.camera.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0); + this.scene.add(this.camera); + return this; + }; - public addGroundPlane = (options:position) => { - this.ground = new Mesh( new PlaneGeometry(), new ShadowMaterial({side: 2})); - this.ground.rotation.x = -Math.PI / 2; - this.ground.scale.setScalar(30); - this.ground.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0); - this.ground.receiveShadow = true; - this.scene.add(this.ground); - return this - } + public addGroundPlane = (options: position) => { + this.ground = new Mesh(new PlaneGeometry(), new ShadowMaterial({ side: 2 })); + this.ground.rotation.x = -Math.PI / 2; + this.ground.scale.setScalar(30); + this.ground.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0); + this.ground.receiveShadow = true; + this.scene.add(this.ground); + return this; + }; - public addOrbitControls = (minDistance:number, maxDistance:number) => { - this.controls = new OrbitControls(this.camera, this.renderer.domElement); - this.controls.minDistance = minDistance; - this.controls.maxDistance = maxDistance; - this.controls.update(); - return this - } + public addOrbitControls = (minDistance: number, maxDistance: number) => { + this.controls = new OrbitControls(this.camera, this.renderer.domElement); + this.controls.minDistance = minDistance; + this.controls.maxDistance = maxDistance; + this.controls.update(); + return this; + }; - public addAmbientLight = (options:light) => { - const ambientLight = new AmbientLight(options.color, options.intensity); - this.scene.add(ambientLight); - return this - } + public addAmbientLight = (options: light) => { + const ambientLight = new AmbientLight(options.color, options.intensity); + this.scene.add(ambientLight); + return this; + }; - public addDirectionalLight = (options:directionalLight) => { - const directionalLight = new DirectionalLight(options.color, options.intensity); - directionalLight.castShadow = true; - directionalLight.shadow.mapSize.setScalar(2048); - directionalLight.shadow.mapSize.width = 1024; - directionalLight.shadow.mapSize.height = 1024; - directionalLight.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0); - directionalLight.shadow.radius = 5 - this.scene.add(directionalLight); - return this - } + public addDirectionalLight = (options: directionalLight) => { + const directionalLight = new DirectionalLight(options.color, options.intensity); + directionalLight.castShadow = true; + directionalLight.shadow.mapSize.setScalar(2048); + directionalLight.shadow.mapSize.width = 1024; + directionalLight.shadow.mapSize.height = 1024; + directionalLight.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0); + directionalLight.shadow.radius = 5; + this.scene.add(directionalLight); + return this; + }; - public addGridHelper = (options:gridHelperOptions) => { - this.gridHelper = new GridHelper(options.size, options.divisions); - this.gridHelper.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0); - this.gridHelper.material.opacity = 0.2; - this.gridHelper.material.depthWrite = false; - this.gridHelper.material.transparent = true; - this.scene.add(this.gridHelper); - return this - } + public addGridHelper = (options: gridHelperOptions) => { + this.gridHelper = new GridHelper(options.size, options.divisions); + this.gridHelper.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0); + this.gridHelper.material.opacity = 0.2; + this.gridHelper.material.depthWrite = false; + this.gridHelper.material.transparent = true; + this.scene.add(this.gridHelper); + return this; + }; - public addFogExp2 = (color:ColorRepresentation, density?:number) => { - this.scene.fog = new FogExp2(color, density); - return this - } + public addFogExp2 = (color: ColorRepresentation, density?: number) => { + this.scene.fog = new FogExp2(color, density); + return this; + }; - public handleResize = () => { - this.renderer.setSize(window.innerWidth, window.innerHeight); - this.renderer.setPixelRatio(window.devicePixelRatio); - this.camera.aspect = window.innerWidth / window.innerHeight; - this.camera.updateProjectionMatrix(); - return this - } + public handleResize = () => { + this.renderer.setSize(window.innerWidth, window.innerHeight); + this.renderer.setPixelRatio(window.devicePixelRatio); + this.camera.aspect = window.innerWidth / window.innerHeight; + this.camera.updateProjectionMatrix(); + return this; + }; - public addRenderCb = (callback:Function) => { - this.callback = callback - return this - } + public addRenderCb = (callback: Function) => { + this.callback = callback; + return this; + }; - public startRenderLoop = () => { - this.renderer.setAnimationLoop(() => { - this.renderer.render(this.scene, this.camera); - this.handleRobotShadow() - if(this.callback) this.callback() - if(!this.liveStreamTexture) return - }); - return this - } + public startRenderLoop = () => { + this.renderer.setAnimationLoop(() => { + this.renderer.render(this.scene, this.camera); + this.handleRobotShadow(); + if (this.callback) this.callback(); + if (!this.liveStreamTexture) return; + }); + return this; + }; - public addArrowHelper = (options?:arrowOptions) => { - const dir = new Vector3(options?.direction.x ?? 0, options?.direction.y ?? 0, options?.direction.z ?? 0); - const origin = new Vector3(options?.origin.x ?? 0, options?.origin.y ?? 0, options?.origin.z ?? 0); - const arrowHelper = new ArrowHelper( dir, origin, options?.length ?? 1.5, options?.color ?? 0xff0000 ); - this.scene.add( arrowHelper ); - return this - } + public addArrowHelper = (options?: arrowOptions) => { + const dir = new Vector3( + options?.direction.x ?? 0, + options?.direction.y ?? 0, + options?.direction.z ?? 0 + ); + const origin = new Vector3( + options?.origin.x ?? 0, + options?.origin.y ?? 0, + options?.origin.z ?? 0 + ); + const arrowHelper = new ArrowHelper( + dir, + origin, + options?.length ?? 1.5, + options?.color ?? 0xff0000 + ); + this.scene.add(arrowHelper); + return this; + }; - private setJointValue(jointName:string, angle:number) { - if (!this.model) return; - if (!this.model.joints[jointName]) return; - this.model.joints[jointName].setJointValue(angle) - } + private setJointValue(jointName: string, angle: number) { + if (!this.model) return; + if (!this.model.joints[jointName]) return; + this.model.joints[jointName].setJointValue(angle); + } - isJoint = j => j.isURDFJoint && j.jointType !== 'fixed'; + isJoint = (j) => j.isURDFJoint && j.jointType !== 'fixed'; - highlightLinkGeometry = (m: URDFMimicJoint, revert:boolean, material: MeshPhongMaterial) => { - const traverse = c => { - if (c.type === 'Mesh') { - if (revert) { - c.material = c.__origMaterial; - delete c.__origMaterial; - } else { - c.__origMaterial = c.material; - c.material = material; - } - } + highlightLinkGeometry = (m: URDFMimicJoint, revert: boolean, material: MeshPhongMaterial) => { + const traverse = (c) => { + if (c.type === 'Mesh') { + if (revert) { + c.material = c.__origMaterial; + delete c.__origMaterial; + } else { + c.__origMaterial = c.material; + c.material = material; + } + } - if (c === m || !this.isJoint(c)) { - for (let i = 0; i < c.children.length; i++) { - const child = c.children[i]; - if (!child.isURDFCollider) { - traverse(c.children[i]); - } - } - } - }; - traverse(m); - }; + if (c === m || !this.isJoint(c)) { + for (let i = 0; i < c.children.length; i++) { + const child = c.children[i]; + if (!child.isURDFCollider) { + traverse(c.children[i]); + } + } + } + }; + traverse(m); + }; - public addModel = (model: any) => { - this.model = model - this.scene.add(model) - return this - } + public addModel = (model: any) => { + this.model = model; + this.scene.add(model); + return this; + }; - public addDragControl = (updateAngle:any) => { - const highlightColor = '#FFFFFF' - const highlightMaterial = - new MeshPhongMaterial({ - shininess: 10, - color: highlightColor, - emissive: highlightColor, - emissiveIntensity: 0.25, - }); - - const dragControls = new PointerURDFDragControls(this.scene, this.camera, this.renderer.domElement); - dragControls.updateJoint = (joint:URDFMimicJoint, angle:number) => { - this.setJointValue(joint.name, angle); - updateAngle(joint.name, angle) - }; - dragControls.onDragStart = () => this.controls.enabled = false; - dragControls.onDragEnd = () => this.controls.enabled = true; - dragControls.onHover = (joint:URDFMimicJoint) => this.highlightLinkGeometry(joint, false, highlightMaterial); - dragControls.onUnhover = (joint:URDFMimicJoint) => this.highlightLinkGeometry(joint, true, highlightMaterial); + public addDragControl = (updateAngle: any) => { + const highlightColor = '#FFFFFF'; + const highlightMaterial = new MeshPhongMaterial({ + shininess: 10, + color: highlightColor, + emissive: highlightColor, + emissiveIntensity: 0.25 + }); - this.renderer.domElement.addEventListener('touchstart', (data) => dragControls._mouseDown(data.touches[0])); - this.renderer.domElement.addEventListener('touchmove', (data) => dragControls._mouseMove(data.touches[0])) - this.renderer.domElement.addEventListener('touchup', (data) => dragControls._mouseUp(data.touches[0])); - return this - } + const dragControls = new PointerURDFDragControls( + this.scene, + this.camera, + this.renderer.domElement + ); + dragControls.updateJoint = (joint: URDFMimicJoint, angle: number) => { + this.setJointValue(joint.name, angle); + updateAngle(joint.name, angle); + }; + dragControls.onDragStart = () => (this.controls.enabled = false); + dragControls.onDragEnd = () => (this.controls.enabled = true); + dragControls.onHover = (joint: URDFMimicJoint) => + this.highlightLinkGeometry(joint, false, highlightMaterial); + dragControls.onUnhover = (joint: URDFMimicJoint) => + this.highlightLinkGeometry(joint, true, highlightMaterial); - public toggleFog = () => { - this.scene.fog = this.scene.fog ? null : this.fog; - } + this.renderer.domElement.addEventListener('touchstart', (data) => + dragControls._mouseDown(data.touches[0]) + ); + this.renderer.domElement.addEventListener('touchmove', (data) => + dragControls._mouseMove(data.touches[0]) + ); + this.renderer.domElement.addEventListener('touchup', (data) => + dragControls._mouseUp(data.touches[0]) + ); + return this; + }; - private handleRobotShadow = () => { - if(this.isLoaded) return - const intervalId = setInterval(() => { - this.model?.traverse(c => c.castShadow = true); - }, 10); - setTimeout(() => { - clearInterval(intervalId) - }, 1000); - this.isLoaded = true; - } -} \ No newline at end of file + public toggleFog = () => { + this.scene.fog = this.scene.fog ? null : this.fog; + }; + + private handleRobotShadow = () => { + if (this.isLoaded) return; + const intervalId = setInterval(() => { + this.model?.traverse((c) => (c.castShadow = true)); + }, 10); + setTimeout(() => { + clearInterval(intervalId); + }, 1000); + this.isLoaded = true; + }; +} diff --git a/app/src/lib/services/file-service.ts b/app/src/lib/services/file-service.ts index 4cf1cfa..2a529db 100644 --- a/app/src/lib/services/file-service.ts +++ b/app/src/lib/services/file-service.ts @@ -1,72 +1,71 @@ import { Result } from '$lib/utilities/result'; class FileService { - private dbName = 'fileStorageDB'; - private dbVersion = 1; - private storeName = 'files'; - private dbPromise: Promise>; + private dbName = 'fileStorageDB'; + private dbVersion = 1; + private storeName = 'files'; + private dbPromise: Promise>; - constructor() { - this.dbPromise = this.openDatabase(); - } + constructor() { + this.dbPromise = this.openDatabase(); + } - private async openDatabase(): Promise> { - return new Promise((resolve) => { - const request = indexedDB.open(this.dbName, this.dbVersion); + private async openDatabase(): Promise> { + return new Promise((resolve) => { + const request = indexedDB.open(this.dbName, this.dbVersion); - request.onerror = () => resolve(Result.err("Error opening database")); + request.onerror = () => resolve(Result.err('Error opening database')); - request.onsuccess = () => resolve(Result.ok(request.result)); + request.onsuccess = () => resolve(Result.ok(request.result)); - request.onupgradeneeded = (event) => { - const db = request.result; - if (!db.objectStoreNames.contains(this.storeName)) { - db.createObjectStore(this.storeName); - } - }; - }); - } + request.onupgradeneeded = (event) => { + const db = request.result; + if (!db.objectStoreNames.contains(this.storeName)) { + db.createObjectStore(this.storeName); + } + }; + }); + } - private async getStore(mode: IDBTransactionMode): Promise> { - const dbResult = await this.dbPromise; - if (dbResult.isErr()) { - return Result.err("Database not initialized properly"); - } - const db = dbResult.inner; - const transaction = db.transaction(this.storeName, mode); - return Result.ok(transaction.objectStore(this.storeName)); - } + private async getStore(mode: IDBTransactionMode): Promise> { + const dbResult = await this.dbPromise; + if (dbResult.isErr()) { + return Result.err('Database not initialized properly'); + } + const db = dbResult.inner; + const transaction = db.transaction(this.storeName, mode); + return Result.ok(transaction.objectStore(this.storeName)); + } - public async saveFile(key: string, file: Uint8Array): Promise> { - const storeResult = await this.getStore("readwrite"); - if (storeResult.isErr()) { - return Result.err("Failed to access object store for writing"); - } - const store = storeResult.inner; + public async saveFile(key: string, file: Uint8Array): Promise> { + const storeResult = await this.getStore('readwrite'); + if (storeResult.isErr()) { + return Result.err('Failed to access object store for writing'); + } + const store = storeResult.inner; - return new Promise((resolve) => { - const request = store.put(file, key); - request.onsuccess = () => resolve(Result.ok(request.result)); - request.onerror = () => resolve(Result.err("Failed to save file")); - }); - } + return new Promise((resolve) => { + const request = store.put(file, key); + request.onsuccess = () => resolve(Result.ok(request.result)); + request.onerror = () => resolve(Result.err('Failed to save file')); + }); + } - public async getFile(key: string): Promise> { - const storeResult = await this.getStore("readonly"); - if (storeResult.isErr()) { - return Result.err("Failed to access object store for reading"); - } - const store = storeResult.inner; + public async getFile(key: string): Promise> { + const storeResult = await this.getStore('readonly'); + if (storeResult.isErr()) { + return Result.err('Failed to access object store for reading'); + } + const store = storeResult.inner; - return new Promise((resolve) => { - const request = store.get(key); - - request.onsuccess = () => - resolve(request.result ? Result.ok(request.result) : Result.err("File content not found")) - request.onerror = () => - resolve(Result.err("Failed to retrieve file")); - }); - } + return new Promise((resolve) => { + const request = store.get(key); + + request.onsuccess = () => + resolve(request.result ? Result.ok(request.result) : Result.err('File content not found')); + request.onerror = () => resolve(Result.err('Failed to retrieve file')); + }); + } } -export default new FileService(); \ No newline at end of file +export default new FileService(); diff --git a/app/src/lib/services/index.ts b/app/src/lib/services/index.ts index 2b6f66c..d3ed92d 100644 --- a/app/src/lib/services/index.ts +++ b/app/src/lib/services/index.ts @@ -1,2 +1,2 @@ -export * from './file-service' -export * from './socket-service' \ No newline at end of file +export { default as fileService } from './file-service'; +export { default as socketService } from './socket-service'; diff --git a/app/src/lib/services/socket-service.ts b/app/src/lib/services/socket-service.ts index 8318327..3497098 100644 --- a/app/src/lib/services/socket-service.ts +++ b/app/src/lib/services/socket-service.ts @@ -1,89 +1,100 @@ import { Result, Ok } from '$lib/utilities'; import { writable, type Writable } from 'svelte/store'; -type WebsocketData = string | ArrayBufferLike | Blob | ArrayBufferView +type WebsocketData = string | ArrayBufferLike | Blob | ArrayBufferView; + +// TODO +/** + * MOVE THE store to a store.ts file + * + * Make an object on the class that encapsulate all the stores + * + * Make the handle message function look up the type and set the value, to simplify the code + */ class SocketService { + public isConnected = writable(false); + public angles = writable(new Int16Array(12).fill(0)); + public log = writable([] as string[]); + public battery = writable({}); + public mpu = writable({ heading: 0 }); + public distances = writable({}); + public settings = writable({}); + public systemInfo = writable({} as number); + public dataBuffer = writable(new Float32Array(13)); + public servoBuffer: Writable = writable(new Int16Array(12)); + public data = writable(); + private socket!: WebSocket; - public isConnected = writable(false); - public angles = writable(new Int16Array(12).fill(0)); - public log = writable([] as string[]); - public battery = writable({}); - public mpu = writable({ heading: 0 }); - public distances = writable({}); - public settings = writable({}); - public systemInfo = writable({} as number); - public dataBuffer = writable(new Float32Array(13)); - public servoBuffer: Writable = writable(new Int16Array(12)); - public data = writable(); - private socket!: WebSocket; + constructor() {} - constructor() {} + public connect(url: string): void { + this.socket = new WebSocket(url); + this.socket.binaryType = 'arraybuffer'; + this.socket.onopen = () => this.handleConnected(); + this.socket.onclose = () => this.handleDisconnected(); + this.socket.onmessage = (event: unknown) => this.handleMessage(event); + } - public connect(url: string): void { - this.socket = new WebSocket(url); - this.socket.binaryType = "arraybuffer"; - this.socket.onopen = () => this.handleConnected(); - this.socket.onclose = () => this.handleDisconnected(); - this.socket.onmessage = (event: unknown) => this.handleMessage(event); - } + public send(data: WebsocketData): Result { + if (this.socket.readyState === WebSocket.OPEN) { + this.socket.send(data); + return Ok.void(); + } + return Result.err('The connection is not open'); + } - public send(data: WebsocketData): Result { - if (this.socket.readyState === WebSocket.OPEN){ - this.socket.send(data) - return Ok.void() - } - return Result.err("The connection is not open") - } + private handleConnected(): void { + this.isConnected.set(true); + } - private handleConnected(): void { - this.isConnected.set(true); - } + private handleDisconnected(): void { + this.isConnected.set(false); + } - private handleDisconnected(): void { - this.isConnected.set(false); - } - - private handleMessage(event: any): void { - if (event.data instanceof ArrayBuffer) { - let buffer = new Int8Array(event.data); - if (buffer.length === 44) { - this.dataBuffer.set(new Float32Array(buffer.buffer)); - } - } else { - let data = event.data; - try { - data = JSON.parse(event.data); - } catch (error) { - console.warn(error); - } - switch (data.type) { - case "angles": - this.angles.set(data.angles); - break; - case "logs": - this.log.set(data.logs); - break; - case "log": - this.log.update(entries => { entries.push(data.log); return entries; }); - break; - case "settings": - this.settings.set(data.settings); - case "info": - this.systemInfo.set(data.info); - break; - case "mpu": - this.mpu.set(data.mpu); - break; - case "distances": - this.distances.set(data.distances); - break; - case "battery": - this.battery.set(data.battery); - break; - } - } - } + private handleMessage(event: any): void { + if (event.data instanceof ArrayBuffer) { + let buffer = new Int8Array(event.data); + if (buffer.length === 44) { + this.dataBuffer.set(new Float32Array(buffer.buffer)); + } + } else { + let data = event.data; + try { + data = JSON.parse(event.data); + } catch (error) { + console.warn(error); + } + switch (data.type) { + case 'angles': + this.angles.set(data.angles); + break; + case 'logs': + this.log.set(data.logs); + break; + case 'log': + this.log.update((entries) => { + entries.push(data.log); + return entries; + }); + break; + case 'settings': + this.settings.set(data.settings); + case 'info': + this.systemInfo.set(data.info); + break; + case 'mpu': + this.mpu.set(data.mpu); + break; + case 'distances': + this.distances.set(data.distances); + break; + case 'battery': + this.battery.set(data.battery); + break; + } + } + } } -export default new SocketService(); \ No newline at end of file +export default new SocketService(); diff --git a/app/src/lib/store.ts b/app/src/lib/store.ts index 371a43e..fececc1 100644 --- a/app/src/lib/store.ts +++ b/app/src/lib/store.ts @@ -3,10 +3,15 @@ import { persistentStore } from '$lib/utilities'; export const emulateModel = writable(true); -export const input = writable({left:{x:0, y:0}, right:{x:0, y:0}, height:70, speed:0}); +export const input = writable({ + left: { x: 0, y: 0 }, + right: { x: 0, y: 0 }, + height: 70, + speed: 0 +}); export const outControllerData = writable(new Uint8Array([0, 128, 128, 128, 128, 70, 0])); -export const jointNames = persistentStore("joint_names", []) +export const jointNames = persistentStore('joint_names', []); -export const model = writable() \ No newline at end of file +export const model = writable(); diff --git a/app/src/lib/utilities/buffer-utilities.ts b/app/src/lib/utilities/buffer-utilities.ts index 111680c..a5f5182 100644 --- a/app/src/lib/utilities/buffer-utilities.ts +++ b/app/src/lib/utilities/buffer-utilities.ts @@ -1,16 +1,15 @@ export class throttler { - private _throttlePause: boolean; - constructor() { - this._throttlePause = false; - } - throttle = (callback:Function, time:number) => { - if (this._throttlePause) return; - - this._throttlePause = true; - setTimeout(() => { - callback(); - this._throttlePause = false; - }, time); - }; -} + private _throttlePause: boolean; + constructor() { + this._throttlePause = false; + } + throttle = (callback: Function, time: number) => { + if (this._throttlePause) return; + this._throttlePause = true; + setTimeout(() => { + callback(); + this._throttlePause = false; + }, time); + }; +} diff --git a/app/src/lib/utilities/index.ts b/app/src/lib/utilities/index.ts index 1fad676..90b8760 100644 --- a/app/src/lib/utilities/index.ts +++ b/app/src/lib/utilities/index.ts @@ -1,7 +1,7 @@ -export * from './result' -export * from './string-utilities' -export * from './svelte-utilities' -export * from './math-utilities' -export * from './buffer-utilities' -export * from './model-utilities' -export * from './location-utilities' \ No newline at end of file +export * from './result'; +export * from './string-utilities'; +export * from './svelte-utilities'; +export * from './math-utilities'; +export * from './buffer-utilities'; +export * from './model-utilities'; +export * from './location-utilities'; diff --git a/app/src/lib/utilities/location-utilities.ts b/app/src/lib/utilities/location-utilities.ts index 8de566b..da1a008 100644 --- a/app/src/lib/utilities/location-utilities.ts +++ b/app/src/lib/utilities/location-utilities.ts @@ -1,6 +1,8 @@ -const forWeb = import.meta.env.MODE === "WEB" -const mock = import.meta.env.MODE === "MOCK" +const forWeb = import.meta.env.MODE === 'WEB'; +const mock = import.meta.env.MODE === 'MOCK'; -export const location = mock ? `${window.location.hostname}:2096` : "leika.local" +export const location = mock ? `${window.location.hostname}:2096` : 'leika.local'; -export const socketLocation = forWeb ? `wss://${window.location.hostname}:2096` : `ws://${location}` +export const socketLocation = forWeb + ? `wss://${window.location.hostname}:2096` + : `ws://${location}`; diff --git a/app/src/lib/utilities/math-utilities.ts b/app/src/lib/utilities/math-utilities.ts index ee5a675..9be3559 100644 --- a/app/src/lib/utilities/math-utilities.ts +++ b/app/src/lib/utilities/math-utilities.ts @@ -1,3 +1,3 @@ export const lerp = (start: number, end: number, amt: number) => { - return (1 - amt) * start + amt * end; + return (1 - amt) * start + amt * end; }; diff --git a/app/src/lib/utilities/model-utilities.ts b/app/src/lib/utilities/model-utilities.ts index 625569f..7feda9c 100644 --- a/app/src/lib/utilities/model-utilities.ts +++ b/app/src/lib/utilities/model-utilities.ts @@ -1,32 +1,38 @@ -import { LoaderUtils } from "three"; -import URDFLoader, { type URDFRobot } from "urdf-loader" -import { XacroLoader } from "xacro-parser" -import { Result } from "$lib/utilities"; +import { LoaderUtils } from 'three'; +import URDFLoader, { type URDFRobot } from 'urdf-loader'; +import { XacroLoader } from 'xacro-parser'; +import { Result } from '$lib/utilities'; -export const loadModelAsync = async (url:string):Promise> => { - return new Promise((resolve, reject) => { - const xacroLoader = new XacroLoader(); - - xacroLoader.load(url, async (xml) => { - const urdfLoader = new URDFLoader(); - - urdfLoader.workingPath = LoaderUtils.extractUrlBase(url); - - try { - const model = urdfLoader.parse(xml); - model.rotation.x = -Math.PI / 2; - model.rotation.z = Math.PI / 2; - model.traverse(c => c.castShadow = true); - model.updateMatrixWorld(true); - model.scale.setScalar(10); - const joints = Object.entries(model.joints) - .filter(joint => joint[1].jointType !== 'fixed') - .map(joint => joint[0]) +export const loadModelAsync = async ( + url: string +): Promise> => { + return new Promise((resolve, reject) => { + const xacroLoader = new XacroLoader(); - resolve(Result.ok([model, joints])); - } catch (error) { - resolve(Result.err("Failed to load model", error)); - } - }, (error) => reject(error)); - }); -} \ No newline at end of file + xacroLoader.load( + url, + async (xml) => { + const urdfLoader = new URDFLoader(); + + urdfLoader.workingPath = LoaderUtils.extractUrlBase(url); + + try { + const model = urdfLoader.parse(xml); + model.rotation.x = -Math.PI / 2; + model.rotation.z = Math.PI / 2; + model.traverse((c) => (c.castShadow = true)); + model.updateMatrixWorld(true); + model.scale.setScalar(10); + const joints = Object.entries(model.joints) + .filter((joint) => joint[1].jointType !== 'fixed') + .map((joint) => joint[0]); + + resolve(Result.ok([model, joints])); + } catch (error) { + resolve(Result.err('Failed to load model', error)); + } + }, + (error) => reject(error) + ); + }); +}; diff --git a/app/src/lib/utilities/result/err.ts b/app/src/lib/utilities/result/err.ts index 1ce0dbb..8c879be 100644 --- a/app/src/lib/utilities/result/err.ts +++ b/app/src/lib/utilities/result/err.ts @@ -1,42 +1,42 @@ export class Err { - #inner: T - #exception?: U + #inner: T; + #exception?: U; - constructor(inner: T, exception?: U) { - this.#inner = inner - this.#exception = exception - } + constructor(inner: T, exception?: U) { + this.#inner = inner; + this.#exception = exception; + } - get inner(): T { - return this.#inner - } + get inner(): T { + return this.#inner; + } - get exception(): U | undefined { - return this.#exception; - } + get exception(): U | undefined { + return this.#exception; + } - /** - * Type guard for `Ok` - * @returns `true` if `Ok`; `false` if `Err` - */ - isOk(): false { - return false - } + /** + * Type guard for `Ok` + * @returns `true` if `Ok`; `false` if `Err` + */ + isOk(): false { + return false; + } - /** - * Type guard for `Err` - * @returns `true` if `Err`; `false` if `Ok` - */ - isErr(): this is Err { - return true - } + /** + * Type guard for `Err` + * @returns `true` if `Err`; `false` if `Ok` + */ + isErr(): this is Err { + return true; + } - /** - * Create an `Err` - * @param inner - * @returns `Err(inner)` - */ - static new(inner: E, exception: F): Err { - return new Err(inner, exception) - } + /** + * Create an `Err` + * @param inner + * @returns `Err(inner)` + */ + static new(inner: E, exception: F): Err { + return new Err(inner, exception); + } } diff --git a/app/src/lib/utilities/result/index.ts b/app/src/lib/utilities/result/index.ts index 5130b66..55e069b 100644 --- a/app/src/lib/utilities/result/index.ts +++ b/app/src/lib/utilities/result/index.ts @@ -1,3 +1,3 @@ -export * from './err' -export * from './ok' -export * from './result' +export * from './err'; +export * from './ok'; +export * from './result'; diff --git a/app/src/lib/utilities/result/ok.ts b/app/src/lib/utilities/result/ok.ts index ccfb0cb..868b3c8 100644 --- a/app/src/lib/utilities/result/ok.ts +++ b/app/src/lib/utilities/result/ok.ts @@ -1,44 +1,44 @@ export class Ok { - #inner: T + #inner: T; - constructor(inner: T) { - this.#inner = inner - } + constructor(inner: T) { + this.#inner = inner; + } - get inner(): T { - return this.#inner - } + get inner(): T { + return this.#inner; + } - /** - * Type guard for `Ok` - * @returns `true` if `Ok`; `false` if `Err` - */ - isOk(): this is Ok { - return true - } + /** + * Type guard for `Ok` + * @returns `true` if `Ok`; `false` if `Err` + */ + isOk(): this is Ok { + return true; + } - /** - * Type guard for `Err` - * @returns `true` if `Err`; `false` if `Ok` - */ - isErr(): false { - return false - } + /** + * Type guard for `Err` + * @returns `true` if `Err`; `false` if `Ok` + */ + isErr(): false { + return false; + } - /** - * Create an `Ok` - * @param inner - * @returns `Ok(inner)` - */ - static new(inner: T): Ok { - return new Ok(inner) - } + /** + * Create an `Ok` + * @param inner + * @returns `Ok(inner)` + */ + static new(inner: T): Ok { + return new Ok(inner); + } - /** - * Create an empty `Ok` - * @returns `Ok(void)` - */ - static void(): Ok { - return new Ok(undefined) - } + /** + * Create an empty `Ok` + * @returns `Ok(void)` + */ + static void(): Ok { + return new Ok(undefined); + } } diff --git a/app/src/lib/utilities/result/result.ts b/app/src/lib/utilities/result/result.ts index 1722b0b..4e86e00 100644 --- a/app/src/lib/utilities/result/result.ts +++ b/app/src/lib/utilities/result/result.ts @@ -1,20 +1,20 @@ -import { Err } from './err' -import { Ok } from './ok' +import { Err } from './err'; +import { Ok } from './ok'; -export type Result = Ok | Err +export type Result = Ok | Err; export namespace Result { - /** - * @returns `Ok` - */ - export function ok(value: T) { - return Ok.new(value) - } + /** + * @returns `Ok` + */ + export function ok(value: T) { + return Ok.new(value); + } - /** - * @returns `Err` - */ - export function err(error: E, exception?: F) { - return Err.new(error, exception) - } + /** + * @returns `Err` + */ + export function err(error: E, exception?: F) { + return Err.new(error, exception); + } } diff --git a/app/src/lib/utilities/string-utilities.ts b/app/src/lib/utilities/string-utilities.ts index 594fd30..4394aaa 100644 --- a/app/src/lib/utilities/string-utilities.ts +++ b/app/src/lib/utilities/string-utilities.ts @@ -1,5 +1,5 @@ -export const humanFileSize = (size:number):string => { - const units = ['B', 'kB', 'MB', 'GB', 'TB'] - var i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024)); - return Number((size / Math.pow(1024, i)).toFixed(2)) * 1 + units[i]; -} \ No newline at end of file +export const humanFileSize = (size: number): string => { + const units = ['B', 'kB', 'MB', 'GB', 'TB']; + var i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024)); + return Number((size / Math.pow(1024, i)).toFixed(2)) * 1 + units[i]; +}; diff --git a/app/src/lib/utilities/svelte-utilities.ts b/app/src/lib/utilities/svelte-utilities.ts index 8bd576b..5a72ba8 100644 --- a/app/src/lib/utilities/svelte-utilities.ts +++ b/app/src/lib/utilities/svelte-utilities.ts @@ -1,13 +1,13 @@ -import { writable } from "svelte/store"; +import { writable } from 'svelte/store'; -export const persistentStore = (key:string, initialValue:any) => { - const savedValue = JSON.parse(localStorage.getItem(key) as string); - const data = savedValue !== null ? savedValue : initialValue; - const store = writable(data); - - store.subscribe(value => { - localStorage.setItem(key, JSON.stringify(value)); - }); - - return store; -} \ No newline at end of file +export const persistentStore = (key: string, initialValue: any) => { + const savedValue = JSON.parse(localStorage.getItem(key) as string); + const data = savedValue !== null ? savedValue : initialValue; + const store = writable(data); + + store.subscribe((value) => { + localStorage.setItem(key, JSON.stringify(value)); + }); + + return store; +}; diff --git a/app/src/routes/Controller.svelte b/app/src/routes/Controller.svelte index e6108e1..6475a03 100644 --- a/app/src/routes/Controller.svelte +++ b/app/src/routes/Controller.svelte @@ -1,15 +1,15 @@
- {#if $emulateModel} - - {:else} - - {/if} + {#if $emulateModel} + + {:else} + + {/if}
diff --git a/app/src/routes/Settings.svelte b/app/src/routes/Settings.svelte index d827f93..c9b5b89 100644 --- a/app/src/routes/Settings.svelte +++ b/app/src/routes/Settings.svelte @@ -3,54 +3,63 @@ import Info from '../components/settings/Info.svelte'; import Log from '../components/settings/Log.svelte'; import Configuration from '../components/settings/Configuration.svelte'; - import { Icon, Wifi, CommandLine, InformationCircle, BookOpen, AdjustmentsVertical, Cog6Tooth, Newspaper } from 'svelte-hero-icons'; + import { + Icon, + Wifi, + CommandLine, + InformationCircle, + BookOpen, + AdjustmentsVertical, + Cog6Tooth, + Newspaper + } from 'svelte-hero-icons'; import Calibration from '../components/settings/Calibration.svelte'; - export const page = "" + export const page = ''; const menu = [ { title: 'Calibration', path: '/calibration', - icon: AdjustmentsVertical, + icon: AdjustmentsVertical, component: Calibration }, { - title: 'System info', + title: 'System info', path: '/info', - icon: InformationCircle, + icon: InformationCircle, component: Info }, - { - title: 'Log', + { + title: 'Log', path: '/log', - icon: BookOpen, + icon: BookOpen, component: Log }, - { - title: 'Settings', - path: '/settings', - icon: Cog6Tooth, - component: Configuration - }, + { + title: 'Settings', + path: '/settings', + icon: Cog6Tooth, + component: Configuration + } ];
- - {#each menu as link} - - {/each} - + + {#each menu as link} + + {/each} +
diff --git a/app/tailwind.config.js b/app/tailwind.config.js index 34038df..bbcf24f 100644 --- a/app/tailwind.config.js +++ b/app/tailwind.config.js @@ -2,22 +2,22 @@ export default { content: ['./src/**/*.{html,js,ts,svelte}'], theme: { - extend: { - colors: { - 'primary': '#6200EE', - 'primary-variant': '#3700B3', - 'secondary': '#3700B3', - 'secondary-variant': '#3700B3', - 'background': '#1e1e1e', - 'surface': '#2c2c2c', - 'error': '#B00020', - 'on-primary': '#FFFFFF', - 'on-secondary': '#FFFFFF', - 'on-background': '#FFFFFF', - 'on-surface': '#FFFFFF', - 'on-error': '#FFFFFF', - } - }, + extend: { + colors: { + primary: '#6200EE', + 'primary-variant': '#3700B3', + secondary: '#3700B3', + 'secondary-variant': '#3700B3', + background: '#1e1e1e', + surface: '#2c2c2c', + error: '#B00020', + 'on-primary': '#FFFFFF', + 'on-secondary': '#FFFFFF', + 'on-background': '#FFFFFF', + 'on-surface': '#FFFFFF', + 'on-error': '#FFFFFF' + } + } }, plugins: [] }; diff --git a/app/test/result.test.ts b/app/test/result.test.ts index 22a0f1e..ee78ee6 100644 --- a/app/test/result.test.ts +++ b/app/test/result.test.ts @@ -2,38 +2,38 @@ import { describe, it, expect } from 'vitest'; import { Result } from '../src/lib/utilities'; describe('Result', () => { - it('should create a success result correctly', () => { - const successValue = "Success value"; - const result = Result.ok(successValue); + it('should create a success result correctly', () => { + const successValue = 'Success value'; + const result = Result.ok(successValue); - expect(result.isOk()).toBe(true); - expect(result.isErr()).toBe(false); - expect(result.inner).toBe(successValue); - }); + expect(result.isOk()).toBe(true); + expect(result.isErr()).toBe(false); + expect(result.inner).toBe(successValue); + }); - it('should create an error result correctly', () => { - const errorMessage = "Error message"; - const result = Result.err(errorMessage); + it('should create an error result correctly', () => { + const errorMessage = 'Error message'; + const result = Result.err(errorMessage); - expect(result.isOk()).toBe(false); - expect(result.isErr()).toBe(true); - expect(result.inner).toBe(errorMessage); - }); + expect(result.isOk()).toBe(false); + expect(result.isErr()).toBe(true); + expect(result.inner).toBe(errorMessage); + }); - it('should type guard success and error results correctly', () => { - const successResult = Result.ok(123); - const errorResult = Result.err("Error"); + it('should type guard success and error results correctly', () => { + const successResult = Result.ok(123); + const errorResult = Result.err('Error'); - if (successResult.isOk()) { - expect(typeof successResult.inner).toBe('number'); - } else { - throw new Error('Expected successResult to be ok'); - } + if (successResult.isOk()) { + expect(typeof successResult.inner).toBe('number'); + } else { + throw new Error('Expected successResult to be ok'); + } - if (errorResult.isErr()) { - expect(typeof errorResult.inner).toBe('string'); - } else { - throw new Error('Expected errorResult to be fail'); - } - }); -}); \ No newline at end of file + if (errorResult.isErr()) { + expect(typeof errorResult.inner).toBe('string'); + } else { + throw new Error('Expected errorResult to be fail'); + } + }); +}); diff --git a/app/tsconfig.json b/app/tsconfig.json index 2e1fbc5..f36052d 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -5,7 +5,7 @@ "useDefineForClassFields": true, "module": "ESNext", "resolveJsonModule": true, - "moduleResolution": "Node", + "moduleResolution": "Node", /** * Typecheck JS in `.svelte` and `.js` files by default. * Disable checkJs if you'd like to use dynamic types in JS. @@ -15,13 +15,12 @@ "allowJs": true, "checkJs": true, "isolatedModules": true, - "paths": { + "paths": { "$lib/*": ["./src/lib/*"], "$utils/*": ["./src/utils/*"], "$components/*": ["./src/components/*"], "$stores/*": ["./src/stores/*"] } - }, "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], "references": [{ "path": "./tsconfig.node.json" }] diff --git a/app/vite.config.ts b/app/vite.config.ts index 9c50df8..4a7058c 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -2,24 +2,26 @@ import { defineConfig } from 'vite'; import { svelte } from '@sveltejs/vite-plugin-svelte'; import { viteSingleFile } from 'vite-plugin-singlefile'; import viteCompression from 'vite-plugin-compression'; -import path from 'path' +import path from 'path'; -const forEmbedded = process.env.FOR_EMBEDDED == 'true' +const forEmbedded = process.env.FOR_EMBEDDED == 'true'; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [svelte(), - ...(forEmbedded ? [ viteSingleFile(), viteCompression({deleteOriginFile: true})]: [])], + plugins: [ + svelte(), + ...(forEmbedded ? [viteSingleFile(), viteCompression({ deleteOriginFile: true })] : []) + ], build: { - outDir: forEmbedded ? '../data': './build', + outDir: forEmbedded ? '../data' : './build', emptyOutDir: true }, - resolve: { - alias: { - '$lib': path.resolve('./src/lib/'), - '$components': path.resolve('./src/components'), - '$utils': path.resolve('./src/utils'), - '$stores': path.resolve('./src/stores'), - }, - }, + resolve: { + alias: { + $lib: path.resolve('./src/lib/'), + $components: path.resolve('./src/components'), + $utils: path.resolve('./src/utils'), + $stores: path.resolve('./src/stores') + } + } });