📏 Formats app code
This commit is contained in:
+5
-2
@@ -1,9 +1,12 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"
|
||||||
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
+50
-49
@@ -1,51 +1,52 @@
|
|||||||
{
|
{
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"dev:mock": "vite --mode MOCK",
|
"dev:mock": "vite --mode MOCK",
|
||||||
"build": "cross-env FOR_EMBEDDED=true vite build",
|
"build": "cross-env FOR_EMBEDDED=true vite build",
|
||||||
"build:web": "cross-env FOR_EMBEDDED=false vite build --mode WEB",
|
"build:web": "cross-env FOR_EMBEDDED=false vite build --mode WEB",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
"format": "prettier --plugin-search-dir . --write ."
|
"format": "prettier --plugin-search-dir . --write ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||||
"@tsconfig/svelte": "^5.0.2",
|
"@tsconfig/svelte": "^5.0.2",
|
||||||
"@types/three": "^0.160.0",
|
"@types/three": "^0.160.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||||
"@typescript-eslint/parser": "^6.20.0",
|
"@typescript-eslint/parser": "^6.20.0",
|
||||||
"autoprefixer": "^10.4.17",
|
"autoprefixer": "^10.4.17",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"husky": "^9.0.7",
|
"husky": "^9.0.7",
|
||||||
"lint-staged": "^15.2.0",
|
"lint-staged": "^15.2.0",
|
||||||
"postcss": "^8.4.33",
|
"postcss": "^8.4.33",
|
||||||
"prettier": "3.2.4",
|
"prettier": "3.2.4",
|
||||||
"svelte": "^4.2.9",
|
"svelte": "^4.2.9",
|
||||||
"svelte-check": "^3.6.3",
|
"svelte-check": "^3.6.3",
|
||||||
"svelte-hero-icons": "^5.0.0",
|
"svelte-hero-icons": "^5.0.0",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.0.12",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-singlefile": "^1.0.0",
|
"vite-plugin-singlefile": "^1.0.0",
|
||||||
"vitest": "^1.3.1"
|
"vitest": "^1.3.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nipplejs": "^0.10.1",
|
"nipplejs": "^0.10.1",
|
||||||
"svelte-routing": "^2.11.0",
|
"prettier-plugin-svelte": "^3.2.1",
|
||||||
"three": "^0.160.1",
|
"svelte-routing": "^2.11.0",
|
||||||
"urdf-loader": "^0.12.1",
|
"three": "^0.160.1",
|
||||||
"uzip": "^0.20201231.0",
|
"urdf-loader": "^0.12.1",
|
||||||
"xacro-parser": "^0.3.9"
|
"uzip": "^0.20201231.0",
|
||||||
},
|
"xacro-parser": "^0.3.9"
|
||||||
"lint-staged": {
|
},
|
||||||
"*.js": "eslint --cache --fix",
|
"lint-staged": {
|
||||||
"*.{js,css,md,ts,svelte}": "prettier --write"
|
"*.js": "eslint --cache --fix",
|
||||||
}
|
"*.{js,css,md,ts,svelte}": "prettier --write"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+13
-23
@@ -8,6 +8,9 @@ dependencies:
|
|||||||
nipplejs:
|
nipplejs:
|
||||||
specifier: ^0.10.1
|
specifier: ^0.10.1
|
||||||
version: 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:
|
svelte-routing:
|
||||||
specifier: ^2.11.0
|
specifier: ^2.11.0
|
||||||
version: 2.11.0
|
version: 2.11.0
|
||||||
@@ -107,7 +110,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/gen-mapping': 0.3.3
|
'@jridgewell/gen-mapping': 0.3.3
|
||||||
'@jridgewell/trace-mapping': 0.3.18
|
'@jridgewell/trace-mapping': 0.3.18
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@esbuild/aix-ppc64@0.19.12:
|
/@esbuild/aix-ppc64@0.19.12:
|
||||||
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
|
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
|
||||||
@@ -387,32 +389,26 @@ packages:
|
|||||||
'@jridgewell/set-array': 1.1.2
|
'@jridgewell/set-array': 1.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.4.15
|
'@jridgewell/sourcemap-codec': 1.4.15
|
||||||
'@jridgewell/trace-mapping': 0.3.18
|
'@jridgewell/trace-mapping': 0.3.18
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@jridgewell/resolve-uri@3.1.0:
|
/@jridgewell/resolve-uri@3.1.0:
|
||||||
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
|
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@jridgewell/set-array@1.1.2:
|
/@jridgewell/set-array@1.1.2:
|
||||||
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
|
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@jridgewell/sourcemap-codec@1.4.14:
|
/@jridgewell/sourcemap-codec@1.4.14:
|
||||||
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
|
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@jridgewell/sourcemap-codec@1.4.15:
|
/@jridgewell/sourcemap-codec@1.4.15:
|
||||||
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@jridgewell/trace-mapping@0.3.18:
|
/@jridgewell/trace-mapping@0.3.18:
|
||||||
resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==}
|
resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/resolve-uri': 3.1.0
|
'@jridgewell/resolve-uri': 3.1.0
|
||||||
'@jridgewell/sourcemap-codec': 1.4.14
|
'@jridgewell/sourcemap-codec': 1.4.14
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@nodelib/fs.scandir@2.1.5:
|
/@nodelib/fs.scandir@2.1.5:
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
@@ -589,7 +585,6 @@ packages:
|
|||||||
|
|
||||||
/@types/estree@1.0.5:
|
/@types/estree@1.0.5:
|
||||||
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/json-schema@7.0.12:
|
/@types/json-schema@7.0.12:
|
||||||
resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
|
resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
|
||||||
@@ -812,7 +807,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
|
resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
|
||||||
|
|
||||||
/ajv@6.12.6:
|
/ajv@6.12.6:
|
||||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||||
@@ -879,7 +873,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
|
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
dequal: 2.0.3
|
dequal: 2.0.3
|
||||||
dev: true
|
|
||||||
|
|
||||||
/array-union@2.1.0:
|
/array-union@2.1.0:
|
||||||
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
||||||
@@ -910,7 +903,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==}
|
resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
dequal: 2.0.3
|
dequal: 2.0.3
|
||||||
dev: true
|
|
||||||
|
|
||||||
/balanced-match@1.0.2:
|
/balanced-match@1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
@@ -1054,7 +1046,6 @@ packages:
|
|||||||
acorn: 8.11.3
|
acorn: 8.11.3
|
||||||
estree-walker: 3.0.3
|
estree-walker: 3.0.3
|
||||||
periscopic: 3.1.0
|
periscopic: 3.1.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/color-convert@2.0.1:
|
/color-convert@2.0.1:
|
||||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||||
@@ -1106,7 +1097,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mdn-data: 2.0.30
|
mdn-data: 2.0.30
|
||||||
source-map-js: 1.0.2
|
source-map-js: 1.0.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/cssesc@3.0.0:
|
/cssesc@3.0.0:
|
||||||
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
||||||
@@ -1145,7 +1135,6 @@ packages:
|
|||||||
/dequal@2.0.3:
|
/dequal@2.0.3:
|
||||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/detect-indent@6.1.0:
|
/detect-indent@6.1.0:
|
||||||
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
|
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
|
||||||
@@ -1327,7 +1316,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.5
|
'@types/estree': 1.0.5
|
||||||
dev: true
|
|
||||||
|
|
||||||
/esutils@2.0.3:
|
/esutils@2.0.3:
|
||||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||||
@@ -1644,7 +1632,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==}
|
resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.5
|
'@types/estree': 1.0.5
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-stream@3.0.0:
|
/is-stream@3.0.0:
|
||||||
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
||||||
@@ -1769,7 +1756,6 @@ packages:
|
|||||||
|
|
||||||
/locate-character@3.0.0:
|
/locate-character@3.0.0:
|
||||||
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
|
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/locate-path@6.0.0:
|
/locate-path@6.0.0:
|
||||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||||
@@ -1811,11 +1797,9 @@ packages:
|
|||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.4.15
|
'@jridgewell/sourcemap-codec': 1.4.15
|
||||||
dev: true
|
|
||||||
|
|
||||||
/mdn-data@2.0.30:
|
/mdn-data@2.0.30:
|
||||||
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
|
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/merge-stream@2.0.0:
|
/merge-stream@2.0.0:
|
||||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||||
@@ -2051,7 +2035,6 @@ packages:
|
|||||||
'@types/estree': 1.0.5
|
'@types/estree': 1.0.5
|
||||||
estree-walker: 3.0.3
|
estree-walker: 3.0.3
|
||||||
is-reference: 3.0.2
|
is-reference: 3.0.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/picocolors@1.0.0:
|
/picocolors@1.0.0:
|
||||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||||
@@ -2161,11 +2144,20 @@ packages:
|
|||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
dev: true
|
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:
|
/prettier@3.2.4:
|
||||||
resolution: {integrity: sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==}
|
resolution: {integrity: sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
|
||||||
|
|
||||||
/pretty-format@29.7.0:
|
/pretty-format@29.7.0:
|
||||||
resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
|
resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
|
||||||
@@ -2364,7 +2356,6 @@ packages:
|
|||||||
/source-map-js@1.0.2:
|
/source-map-js@1.0.2:
|
||||||
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
|
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/stackback@0.0.2:
|
/stackback@0.0.2:
|
||||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||||
@@ -2572,7 +2563,6 @@ packages:
|
|||||||
locate-character: 3.0.0
|
locate-character: 3.0.0
|
||||||
magic-string: 0.30.5
|
magic-string: 0.30.5
|
||||||
periscopic: 3.1.0
|
periscopic: 3.1.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/tailwindcss@3.4.1:
|
/tailwindcss@3.4.1:
|
||||||
resolution: {integrity: sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==}
|
resolution: {integrity: sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==}
|
||||||
|
|||||||
+22
-24
@@ -4,39 +4,37 @@
|
|||||||
import TopBar from './components/TopBar.svelte';
|
import TopBar from './components/TopBar.svelte';
|
||||||
import socketService from '$lib/services/socket-service';
|
import socketService from '$lib/services/socket-service';
|
||||||
import Controller from './routes/Controller.svelte';
|
import Controller from './routes/Controller.svelte';
|
||||||
import fileService from '$lib/services/file-service';
|
import { fileService } from '$lib/services';
|
||||||
import Settings from './routes/Settings.svelte';
|
import Settings from './routes/Settings.svelte';
|
||||||
import { jointNames, model } from '$lib/store';
|
import { jointNames, model } from '$lib/store';
|
||||||
import { loadModelAsync } from '$lib/utilities';
|
import { loadModelAsync } from '$lib/utilities';
|
||||||
import { socketLocation } from '$lib/utilities';
|
import { socketLocation } from '$lib/utilities';
|
||||||
import type { Result } from '$lib/utilities/result';
|
import type { Result } from '$lib/utilities/result';
|
||||||
|
|
||||||
export let url = window.location.pathname
|
export let url = window.location.pathname;
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
socketService.connect(socketLocation);
|
socketService.connect(socketLocation);
|
||||||
registerFetchIntercept()
|
registerFetchIntercept();
|
||||||
const modelRes = await loadModelAsync('/spot_micro.urdf.xacro')
|
const modelRes = await loadModelAsync('/spot_micro.urdf.xacro');
|
||||||
|
|
||||||
if (modelRes.isOk()) {
|
if (modelRes.isOk()) {
|
||||||
const [urdf, JOINT_NAME] = modelRes.inner
|
const [urdf, JOINT_NAME] = modelRes.inner;
|
||||||
jointNames.set(JOINT_NAME)
|
jointNames.set(JOINT_NAME);
|
||||||
model.set(urdf)
|
model.set(urdf);
|
||||||
} else {
|
} else {
|
||||||
console.error(modelRes.inner, {"exception": modelRes.exception})
|
console.error(modelRes.inner, { exception: modelRes.exception });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const registerFetchIntercept = () => {
|
const registerFetchIntercept = () => {
|
||||||
const { fetch: originalFetch } = window;
|
const { fetch: originalFetch } = window;
|
||||||
window.fetch = async (...args) => {
|
window.fetch = async (...args) => {
|
||||||
const [resource, config] = args;
|
const [resource, config] = args;
|
||||||
let file: Result<Uint8Array | undefined, string>;
|
let file: Result<Uint8Array | undefined, string>;
|
||||||
file = await fileService.getFile(resource.toString());
|
file = await fileService.getFile(resource.toString());
|
||||||
return file.isOk()
|
return file.isOk() ? new Response(file.inner) : originalFetch(resource, config);
|
||||||
? new Response(file.inner)
|
};
|
||||||
: originalFetch(resource, config)
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Router {url}>
|
<Router {url}>
|
||||||
|
|||||||
@@ -25,12 +25,18 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
left.on('move', (evt, data) => {
|
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);
|
throttle.throttle(updateData, throttle_timing);
|
||||||
});
|
});
|
||||||
|
|
||||||
left.on('end', (evt, data) => {
|
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);
|
throttle.throttle(updateData, throttle_timing);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -43,12 +49,18 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
right.on('move', (evt, data) => {
|
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);
|
throttle.throttle(updateData, throttle_timing);
|
||||||
});
|
});
|
||||||
|
|
||||||
right.on('end', (evt, data) => {
|
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);
|
throttle.throttle(updateData, throttle_timing);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -62,11 +74,10 @@
|
|||||||
data[5] = $input.height;
|
data[5] = $input.height;
|
||||||
data[6] = $input.speed;
|
data[6] = $input.speed;
|
||||||
|
|
||||||
outControllerData.set(data)
|
outControllerData.set(data);
|
||||||
|
|
||||||
if(!$emulateModel) socketService.send(data);
|
if (!$emulateModel) socketService.send(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="absolute top-0 left-0 w-screen h-screen">
|
<div class="absolute top-0 left-0 w-screen h-screen">
|
||||||
|
|||||||
@@ -2,76 +2,83 @@
|
|||||||
import socketService from '$lib/services/socket-service';
|
import socketService from '$lib/services/socket-service';
|
||||||
import { Icon, Bars3, XMark, Power, Battery100, Signal, SignalSlash } from 'svelte-hero-icons';
|
import { Icon, Bars3, XMark, Power, Battery100, Signal, SignalSlash } from 'svelte-hero-icons';
|
||||||
import { emulateModel } from '$lib/store';
|
import { emulateModel } from '$lib/store';
|
||||||
import { Link, useLocation } from 'svelte-routing'
|
import { Link, useLocation } from 'svelte-routing';
|
||||||
|
|
||||||
const views = ["Virtual environment", "Robot camera"]
|
const views = ['Virtual environment', 'Robot camera'];
|
||||||
const modes = ["Drive", "Choreography"]
|
const modes = ['Drive', 'Choreography'];
|
||||||
|
|
||||||
const location = useLocation()
|
const location = useLocation();
|
||||||
|
|
||||||
let selected_view = views[0];
|
let selected_view = views[0];
|
||||||
let selected_modes = modes[0];
|
let selected_modes = modes[0];
|
||||||
let settingOpen = window.location.pathname.includes('/settings')
|
let settingOpen = window.location.pathname.includes('/settings');
|
||||||
let isConnected = socketService.isConnected
|
let isConnected = socketService.isConnected;
|
||||||
|
|
||||||
$: emulateModel.set(selected_view === views[0])
|
$: emulateModel.set(selected_view === views[0]);
|
||||||
$: settingOpen = $location.pathname.includes('/settings')
|
$: settingOpen = $location.pathname.includes('/settings');
|
||||||
|
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
if ($isConnected) {
|
if ($isConnected) {
|
||||||
socketService.send(JSON.stringify({type:"system/stop"}))
|
socketService.send(JSON.stringify({ type: 'system/stop' }));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<div class="topbar absolute left-0 top-0 w-full z-10 flex justify-between bg-zinc-800">
|
<div class="topbar absolute left-0 top-0 w-full z-10 flex justify-between bg-zinc-800">
|
||||||
<div class="flex gap-2 p-2">
|
<div class="flex gap-2 p-2">
|
||||||
{#if settingOpen}
|
{#if settingOpen}
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
<Icon src={XMark} size="32" />
|
<Icon src={XMark} size="32" />
|
||||||
</Link>
|
</Link>
|
||||||
{:else}
|
{:else}
|
||||||
<Link to="/settings">
|
<Link to="/settings">
|
||||||
<Icon src={Bars3} size="32" />
|
<Icon src={Bars3} size="32" />
|
||||||
</Link>
|
</Link>
|
||||||
{/if}
|
{/if}
|
||||||
<select bind:value={selected_modes} class="rounded-md outline outline-2 text-zinc-200 outline-zinc-600 bg-zinc-800">
|
<select
|
||||||
{#each modes as mode}
|
bind:value={selected_modes}
|
||||||
<option>{mode}</option>
|
class="rounded-md outline outline-2 text-zinc-200 outline-zinc-600 bg-zinc-800"
|
||||||
{/each}
|
>
|
||||||
</select>
|
{#each modes as mode}
|
||||||
|
<option>{mode}</option>
|
||||||
<select bind:value={selected_view} class="rounded-md outline outline-2 text-zinc-200 outline-zinc-600 bg-zinc-800">
|
{/each}
|
||||||
{#each views as view}
|
</select>
|
||||||
<option>{view}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex gap-2 p-2">
|
<select
|
||||||
<button class="action_button bg-zinc-600">
|
bind:value={selected_view}
|
||||||
<Icon src={Power} size="24" />
|
class="rounded-md outline outline-2 text-zinc-200 outline-zinc-600 bg-zinc-800"
|
||||||
</button>
|
>
|
||||||
<button class="action_button"><Icon src={Battery100} size="24" /></button>
|
{#each views as view}
|
||||||
<button class="action_button"><Icon src={$isConnected ? Signal : SignalSlash} size="24" /></button>
|
<option>{view}</option>
|
||||||
</div>
|
{/each}
|
||||||
<div>
|
</select>
|
||||||
<button class="h-full w-20 bg-red-600 text-white" on:click={stop}>STOP</button>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="flex gap-2 p-2">
|
||||||
|
<button class="action_button bg-zinc-600">
|
||||||
|
<Icon src={Power} size="24" />
|
||||||
|
</button>
|
||||||
|
<button class="action_button"><Icon src={Battery100} size="24" /></button>
|
||||||
|
<button class="action_button"
|
||||||
|
><Icon src={$isConnected ? Signal : SignalSlash} size="24" /></button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="h-full w-20 bg-red-600 text-white" on:click={stop}>STOP</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.topbar {
|
.topbar {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
.action_button {
|
.action_button {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
width: 34px;
|
width: 34px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
outline: 1px solid #52525b;
|
outline: 1px solid #52525b;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,149 +1,184 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { CanvasTexture, CircleGeometry, Mesh, MeshBasicMaterial} from 'three';
|
import { CanvasTexture, CircleGeometry, Mesh, MeshBasicMaterial } from 'three';
|
||||||
import socketService from '$lib/services/socket-service';
|
import socketService from '$lib/services/socket-service';
|
||||||
import { lerp } from '$lib/utilities';
|
import { lerp } from '$lib/utilities';
|
||||||
import uzip from 'uzip';
|
import uzip from 'uzip';
|
||||||
import { model, outControllerData } from '$lib/store';
|
import { model, outControllerData } from '$lib/store';
|
||||||
import { ForwardKinematics } from '$lib/kinematic';
|
import { ForwardKinematics } from '$lib/kinematic';
|
||||||
import { location } from '$lib/utilities';
|
import { location } from '$lib/utilities';
|
||||||
import FileService from '$lib/services/file-service';
|
import { fileService } from '$lib/services';
|
||||||
import SceneBuilder from '$lib/sceneBuilder';
|
import SceneBuilder from '$lib/sceneBuilder';
|
||||||
|
|
||||||
const angles = socketService.angles
|
const angles = socketService.angles;
|
||||||
const mpu = socketService.mpu
|
const mpu = socketService.mpu;
|
||||||
|
|
||||||
let sceneManager:SceneBuilder
|
let sceneManager: SceneBuilder;
|
||||||
let canvas: HTMLCanvasElement, streamCanvas: HTMLCanvasElement, stream: HTMLImageElement
|
let canvas: HTMLCanvasElement, streamCanvas: HTMLCanvasElement, stream: HTMLImageElement;
|
||||||
let context: CanvasRenderingContext2D, texture: CanvasTexture
|
let context: CanvasRenderingContext2D, texture: CanvasTexture;
|
||||||
|
|
||||||
let modelAngles:number[] | Int16Array = new Array(12).fill(0)
|
let modelAngles: number[] | Int16Array = new Array(12).fill(0);
|
||||||
let modelTargetAngles:number[] | Int16Array = new Array(12).fill(0)
|
let modelTargetAngles: number[] | Int16Array = new Array(12).fill(0);
|
||||||
|
|
||||||
let modelBodyAngles:EulerAngle = { omega: 0, phi: 0, psi: 0 }
|
let modelBodyAngles: EulerAngle = { omega: 0, phi: 0, psi: 0 };
|
||||||
let modelTargeBodyAngles:EulerAngle = { omega: 0, phi: 0, psi: 0 }
|
let modelTargeBodyAngles: EulerAngle = { omega: 0, phi: 0, psi: 0 };
|
||||||
|
|
||||||
const videoStream = `//${location}/api/stream`;
|
const videoStream = `//${location}/api/stream`;
|
||||||
|
|
||||||
let showModel = true, showStream = false
|
let showModel = true,
|
||||||
|
showStream = false;
|
||||||
|
|
||||||
const servoNames = [
|
const servoNames = [
|
||||||
"front_left_shoulder", "front_left_leg", "front_left_foot",
|
'front_left_shoulder',
|
||||||
"front_right_shoulder", "front_right_leg", "front_right_foot",
|
'front_left_leg',
|
||||||
"rear_left_shoulder", "rear_left_leg", "rear_left_foot",
|
'front_left_foot',
|
||||||
"rear_right_shoulder", "rear_right_leg", "rear_right_foot"
|
'front_right_shoulder',
|
||||||
]
|
'front_right_leg',
|
||||||
|
'front_right_foot',
|
||||||
|
'rear_left_shoulder',
|
||||||
|
'rear_left_leg',
|
||||||
|
'rear_left_foot',
|
||||||
|
'rear_right_shoulder',
|
||||||
|
'rear_right_leg',
|
||||||
|
'rear_right_foot'
|
||||||
|
];
|
||||||
|
|
||||||
interface EulerAngle {
|
interface EulerAngle {
|
||||||
omega: number;
|
omega: number;
|
||||||
phi: number;
|
phi: number;
|
||||||
psi: number;
|
psi: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const degToRad = (val:number) => val * (Math.PI / 180)
|
const degToRad = (val: number) => val * (Math.PI / 180);
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await cacheModelFiles()
|
await cacheModelFiles();
|
||||||
await createScene()
|
await createScene();
|
||||||
|
|
||||||
outControllerData.subscribe(data => {
|
outControllerData.subscribe((data) => {
|
||||||
socketService.send(JSON.stringify({
|
socketService.send(
|
||||||
type: "kinematic/bodystate",
|
JSON.stringify({
|
||||||
angles:[0, (data[1]-128)/3, (data[2]-128) / 4],
|
type: 'kinematic/bodystate',
|
||||||
position:[(data[4]-128)/2, data[5], (data[3]-128)/2]}))
|
angles: [0, (data[1] - 128) / 3, (data[2] - 128) / 4],
|
||||||
})
|
position: [(data[4] - 128) / 2, data[5], (data[3] - 128) / 2]
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
canvas.remove()
|
canvas.remove();
|
||||||
})
|
});
|
||||||
|
|
||||||
const cacheModelFiles = async () => {
|
const cacheModelFiles = async () => {
|
||||||
let data = await fetch("/stl.zip").then(data => data.arrayBuffer())
|
let data = await fetch('/stl.zip').then((data) => data.arrayBuffer());
|
||||||
|
|
||||||
var files = uzip.parse(data);
|
|
||||||
|
|
||||||
for(const [path, data] of Object.entries(files) as [path:string, data:Uint8Array][]){
|
|
||||||
const url = new URL(path, window.location.href)
|
|
||||||
FileService.saveFile(url.toString(), data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateAngles = (name:string, angle:number) => {
|
var files = uzip.parse(data);
|
||||||
modelTargetAngles[servoNames.indexOf(name)] = angle * (180/Math.PI)
|
|
||||||
socketService.send(JSON.stringify({type:"kinematic/angle", angle:angle * (180/Math.PI), id:servoNames.indexOf(name)}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const createScene = async () => {
|
for (const [path, data] of Object.entries(files) as [path: string, data: Uint8Array][]) {
|
||||||
sceneManager = new SceneBuilder()
|
const url = new URL(path, window.location.href);
|
||||||
.addRenderer({ antialias: true, canvas: canvas, alpha: true})
|
fileService.saveFile(url.toString(), data);
|
||||||
.addPerspectiveCamera({x:-0.5, y:0.5, z:1})
|
}
|
||||||
.addOrbitControls(10, 30)
|
};
|
||||||
.addSky()
|
|
||||||
.addGroundPlane({x:0, y:-2, z:0})
|
|
||||||
.addGridHelper({size:250, divisions:125, y:-2})
|
|
||||||
.addAmbientLight({color:0xffffff, intensity:0.7})
|
|
||||||
.addDirectionalLight({x:10, y:100, z:10, color:0xffffff, intensity:1})
|
|
||||||
.addArrowHelper({origin:{x:0, y:0, z:0}, direction:{x:0, y:-2, z:0}})
|
|
||||||
.addFogExp2(0xcccccc, 0.015)
|
|
||||||
.addModel($model)
|
|
||||||
.addDragControl(updateAngles)
|
|
||||||
.handleResize()
|
|
||||||
.addRenderCb(render)
|
|
||||||
.startRenderLoop()
|
|
||||||
|
|
||||||
addVideoStream()
|
const updateAngles = (name: string, angle: number) => {
|
||||||
}
|
modelTargetAngles[servoNames.indexOf(name)] = angle * (180 / Math.PI);
|
||||||
|
socketService.send(
|
||||||
|
JSON.stringify({
|
||||||
|
type: 'kinematic/angle',
|
||||||
|
angle: angle * (180 / Math.PI),
|
||||||
|
id: servoNames.indexOf(name)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const addVideoStream = () => {
|
const createScene = async () => {
|
||||||
context = streamCanvas.getContext("2d");
|
sceneManager = new SceneBuilder()
|
||||||
texture = new CanvasTexture( stream );
|
.addRenderer({ antialias: true, canvas: canvas, alpha: true })
|
||||||
const liveStream = new Mesh( new CircleGeometry(35, 32), new MeshBasicMaterial({ map: texture }))
|
.addPerspectiveCamera({ x: -0.5, y: 0.5, z: 1 })
|
||||||
liveStream.position.z = -50
|
.addOrbitControls(10, 30)
|
||||||
liveStream.visible = showStream
|
.addSky()
|
||||||
sceneManager.scene.add(liveStream)
|
.addGroundPlane({ x: 0, y: -2, z: 0 })
|
||||||
}
|
.addGridHelper({ size: 250, divisions: 125, y: -2 })
|
||||||
|
.addAmbientLight({ color: 0xffffff, intensity: 0.7 })
|
||||||
|
.addDirectionalLight({ x: 10, y: 100, z: 10, color: 0xffffff, intensity: 1 })
|
||||||
|
.addArrowHelper({ origin: { x: 0, y: 0, z: 0 }, direction: { x: 0, y: -2, z: 0 } })
|
||||||
|
.addFogExp2(0xcccccc, 0.015)
|
||||||
|
.addModel($model)
|
||||||
|
.addDragControl(updateAngles)
|
||||||
|
.handleResize()
|
||||||
|
.addRenderCb(render)
|
||||||
|
.startRenderLoop();
|
||||||
|
|
||||||
const handleVideoStream = () => {
|
addVideoStream();
|
||||||
if(!showStream) return
|
};
|
||||||
context.drawImage(stream, 0, 0)
|
|
||||||
texture.needsUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const render = () => {
|
const addVideoStream = () => {
|
||||||
const robot = sceneManager.model
|
context = streamCanvas.getContext('2d');
|
||||||
if(!robot) return
|
texture = new CanvasTexture(stream);
|
||||||
|
const liveStream = new Mesh(
|
||||||
|
new CircleGeometry(35, 32),
|
||||||
|
new MeshBasicMaterial({ map: texture })
|
||||||
|
);
|
||||||
|
liveStream.position.z = -50;
|
||||||
|
liveStream.visible = showStream;
|
||||||
|
sceneManager.scene.add(liveStream);
|
||||||
|
};
|
||||||
|
|
||||||
const forwardKinematics = new ForwardKinematics()
|
const handleVideoStream = () => {
|
||||||
|
if (!showStream) return;
|
||||||
|
context.drawImage(stream, 0, 0);
|
||||||
|
texture.needsUpdate = true;
|
||||||
|
};
|
||||||
|
|
||||||
const points = forwardKinematics.calculateFootpoints(modelAngles.map(ang => degToRad(ang)) as number[])
|
const render = () => {
|
||||||
robot.position.y = Math.max(...points.map(coord => coord[0] / 100)) - 2.7
|
const robot = sceneManager.model;
|
||||||
robot.rotation.z = lerp(robot.rotation.z, degToRad($mpu.heading + 90), 0.1)
|
if (!robot) return;
|
||||||
modelTargetAngles = $angles
|
|
||||||
|
|
||||||
handleVideoStream()
|
|
||||||
|
|
||||||
for (let i = 0; i < servoNames.length; i++) {
|
const forwardKinematics = new ForwardKinematics();
|
||||||
modelAngles[i] = lerp(robot.joints[servoNames[i]].angle * (180/Math.PI), modelTargetAngles[i], 0.1)
|
|
||||||
robot.joints[servoNames[i]].setJointValue(degToRad(modelAngles[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
modelBodyAngles.omega = lerp(robot.rotation.x * (180/Math.PI), modelTargeBodyAngles.omega - 90, 0.1)
|
const points = forwardKinematics.calculateFootpoints(
|
||||||
modelBodyAngles.phi = lerp(robot.rotation.y * (180/Math.PI), modelTargeBodyAngles.phi, 0.1)
|
modelAngles.map((ang) => degToRad(ang)) as number[]
|
||||||
modelBodyAngles.psi = lerp(robot.rotation.z * (180/Math.PI), modelTargeBodyAngles.psi + 90, 0.1)
|
);
|
||||||
}
|
robot.position.y = Math.max(...points.map((coord) => coord[0] / 100)) - 2.7;
|
||||||
|
robot.rotation.z = lerp(robot.rotation.z, degToRad($mpu.heading + 90), 0.1);
|
||||||
|
modelTargetAngles = $angles;
|
||||||
|
|
||||||
|
handleVideoStream();
|
||||||
|
|
||||||
|
for (let i = 0; i < servoNames.length; i++) {
|
||||||
|
modelAngles[i] = lerp(
|
||||||
|
robot.joints[servoNames[i]].angle * (180 / Math.PI),
|
||||||
|
modelTargetAngles[i],
|
||||||
|
0.1
|
||||||
|
);
|
||||||
|
robot.joints[servoNames[i]].setJointValue(degToRad(modelAngles[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
modelBodyAngles.omega = lerp(
|
||||||
|
robot.rotation.x * (180 / Math.PI),
|
||||||
|
modelTargeBodyAngles.omega - 90,
|
||||||
|
0.1
|
||||||
|
);
|
||||||
|
modelBodyAngles.phi = lerp(robot.rotation.y * (180 / Math.PI), modelTargeBodyAngles.phi, 0.1);
|
||||||
|
modelBodyAngles.psi = lerp(
|
||||||
|
robot.rotation.z * (180 / Math.PI),
|
||||||
|
modelTargeBodyAngles.psi + 90,
|
||||||
|
0.1
|
||||||
|
);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:resize={sceneManager.handleResize}></svelte:window>
|
<svelte:window on:resize={sceneManager.handleResize} />
|
||||||
|
|
||||||
{#if showStream}
|
{#if showStream}
|
||||||
<img
|
<img
|
||||||
bind:this={stream}
|
bind:this={stream}
|
||||||
src={videoStream}
|
src={videoStream}
|
||||||
class="hidden"
|
class="hidden"
|
||||||
alt="Live stream is down"
|
alt="Live stream is down"
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<canvas bind:this={streamCanvas} class="hidden"></canvas>
|
<canvas bind:this={streamCanvas} class="hidden"></canvas>
|
||||||
<canvas bind:this={canvas} class="absolute"></canvas>
|
<canvas bind:this={canvas} class="absolute"></canvas>
|
||||||
|
|||||||
@@ -2,43 +2,43 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { jointNames } from '../../lib/store';
|
import { jointNames } from '../../lib/store';
|
||||||
|
|
||||||
type Servo = {
|
type Servo = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
minPWM: number;
|
minPWM: number;
|
||||||
maxPWM: number;
|
maxPWM: number;
|
||||||
pwmFor180: number;
|
pwmFor180: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
let servos: any[] = [];
|
let servos: any[] = [];
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
jointNames.subscribe(data => {
|
jointNames.subscribe((data) => {
|
||||||
servos = data.map((name:string, i:number) => {
|
servos = data.map((name: string, i: number) => {
|
||||||
return {
|
return {
|
||||||
id: i,
|
id: i,
|
||||||
name,
|
name,
|
||||||
minPWM: 0,
|
minPWM: 0,
|
||||||
maxPWM: 0,
|
maxPWM: 0,
|
||||||
pwmFor180: 0
|
pwmFor180: 0
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
let selectedServo: number | null = null;
|
let selectedServo: number | null = null;
|
||||||
|
|
||||||
function updateServoValue(index: number, field: keyof Servo, value: number): void {
|
function updateServoValue(index: number, field: keyof Servo, value: number): void {
|
||||||
servos[index] = { ...servos[index], [field]: value };
|
servos[index] = { ...servos[index], [field]: value };
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatServo = (servo:Servo) => {
|
const formatServo = (servo: Servo) => {
|
||||||
const string = servo.name
|
const string = servo.name;
|
||||||
const name = string.charAt(0).toUpperCase() + string.split('_').join(' ').slice(1);
|
const name = string.charAt(0).toUpperCase() + string.split('_').join(' ').slice(1);
|
||||||
return `${servo.id} ${name}`
|
return `${servo.id} ${name}`;
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="servo-selector">
|
<div class="servo-selector">
|
||||||
<label for="servo-select">Select Servo:</label>
|
<label for="servo-select">Select Servo:</label>
|
||||||
@@ -49,23 +49,36 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if selectedServo !== null}
|
{#if selectedServo !== null}
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<h2>Servo {formatServo(servos[selectedServo])} Calibration</h2>
|
<h2>Servo {formatServo(servos[selectedServo])} Calibration</h2>
|
||||||
<label for="minPWM">Min PWM:</label>
|
<label for="minPWM">Min PWM:</label>
|
||||||
<input type="number" id="minPWM" class="bg-zinc-800"
|
<input
|
||||||
value={servos[selectedServo].minPWM}
|
type="number"
|
||||||
on:blur={(event) => updateServoValue(selectedServo, 'minPWM', Number(event.target.value))} />
|
id="minPWM"
|
||||||
|
class="bg-zinc-800"
|
||||||
|
value={servos[selectedServo].minPWM}
|
||||||
|
on:blur={(event) => updateServoValue(selectedServo, 'minPWM', Number(event.target.value))}
|
||||||
|
/>
|
||||||
|
|
||||||
<label for="maxPWM">Max PWM:</label>
|
<label for="maxPWM">Max PWM:</label>
|
||||||
<input type="number" id="maxPWM" class="bg-zinc-800"
|
<input
|
||||||
value={servos[selectedServo].maxPWM}
|
type="number"
|
||||||
on:blur={(event) => updateServoValue(selectedServo, 'maxPWM', Number(event.target.value))} />
|
id="maxPWM"
|
||||||
|
class="bg-zinc-800"
|
||||||
|
value={servos[selectedServo].maxPWM}
|
||||||
|
on:blur={(event) => updateServoValue(selectedServo, 'maxPWM', Number(event.target.value))}
|
||||||
|
/>
|
||||||
|
|
||||||
<label for="pwmFor180">PWM for 180°:</label>
|
<label for="pwmFor180">PWM for 180°:</label>
|
||||||
<input type="number" id="pwmFor180" class="bg-zinc-800"
|
<input
|
||||||
value={servos[selectedServo].pwmFor180}
|
type="number"
|
||||||
on:blur={(event) => updateServoValue(selectedServo, 'pwmFor180', Number(event.target.value))} />
|
id="pwmFor180"
|
||||||
</div>
|
class="bg-zinc-800"
|
||||||
{/if}
|
value={servos[selectedServo].pwmFor180}
|
||||||
|
on:blur={(event) =>
|
||||||
|
updateServoValue(selectedServo, 'pwmFor180', Number(event.target.value))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import socketService from "$lib/services/socket-service";
|
import socketService from '$lib/services/socket-service';
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
let isConnected = socketService.isConnected
|
|
||||||
let settings = socketService.settings
|
|
||||||
|
|
||||||
onMount(() => {
|
let isConnected = socketService.isConnected;
|
||||||
if ($isConnected) {
|
let settings = socketService.settings;
|
||||||
const message = JSON.stringify({type: 'system/settings'})
|
|
||||||
socketService.send(message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if ($isConnected) {
|
||||||
|
const message = JSON.stringify({ type: 'system/settings' });
|
||||||
|
socketService.send(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full h-full">
|
<div class="w-full h-full">
|
||||||
<div>
|
<div>
|
||||||
{#each Object.entries($settings) as entry}
|
{#each Object.entries($settings) as entry}
|
||||||
<div class="flex gap-8">
|
<div class="flex gap-8">
|
||||||
<div class="w-32">{entry[0]}:</div>
|
<div class="w-32">{entry[0]}:</div>
|
||||||
<div>{entry[1]}</div>
|
<div>{entry[1]}</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte';
|
||||||
import { humanFileSize } from "$lib/utilities";
|
import { humanFileSize } from '$lib/utilities';
|
||||||
import socketService from '$lib/services/socket-service';
|
import socketService from '$lib/services/socket-service';
|
||||||
|
|
||||||
let isConnected = socketService.isConnected
|
let isConnected = socketService.isConnected;
|
||||||
let settings = socketService.settings
|
let settings = socketService.settings;
|
||||||
let systemInfo = socketService.systemInfo
|
let systemInfo = socketService.systemInfo;
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if ($isConnected) {
|
|
||||||
const message = JSON.stringify({type: 'system/info'})
|
|
||||||
socketService.send(message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if ($isConnected) {
|
||||||
|
const message = JSON.stringify({ type: 'system/info' });
|
||||||
|
socketService.send(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full h-full">
|
<div class="w-full h-full">
|
||||||
<div class="w-1/3">
|
<div class="w-1/3">
|
||||||
{#each Object.entries($systemInfo ?? {}) as entry}
|
{#each Object.entries($systemInfo ?? {}) as entry}
|
||||||
<div class="flex gap-8">
|
<div class="flex gap-8">
|
||||||
<div class="w-32">{entry[0]}:</div>
|
<div class="w-32">{entry[0]}:</div>
|
||||||
{#if entry[0].includes("Size") || entry[0].includes("Free") || entry[0].includes("Min")}
|
{#if entry[0].includes('Size') || entry[0].includes('Free') || entry[0].includes('Min')}
|
||||||
<div>{humanFileSize(entry[1])}</div>
|
<div>{humanFileSize(entry[1])}</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div>{entry[1]}</div>
|
<div>{entry[1]}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import socketService from "$lib/services/socket-service";
|
import socketService from '$lib/services/socket-service';
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
let isConnected = socketService.isConnected
|
let isConnected = socketService.isConnected;
|
||||||
let log = socketService.log
|
let log = socketService.log;
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if ($isConnected) {
|
|
||||||
const message = JSON.stringify({type: 'system/logs'})
|
|
||||||
socketService.send(message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if ($isConnected) {
|
||||||
|
const message = JSON.stringify({ type: 'system/logs' });
|
||||||
|
socketService.send(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full h-full">
|
<div class="w-full h-full">
|
||||||
{#each $log as entry}
|
{#each $log as entry}
|
||||||
<div>{entry}</div>
|
<div>{entry}</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+383
-371
@@ -1,381 +1,393 @@
|
|||||||
export default class Kinematic {
|
export default class Kinematic {
|
||||||
private l1: number;
|
private l1: number;
|
||||||
private l2: number;
|
private l2: number;
|
||||||
private l3: number;
|
private l3: number;
|
||||||
private l4: 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;
|
|
||||||
|
|
||||||
try {
|
private L: number;
|
||||||
F = sqrt(x ** 2 + y ** 2 - l1 ** 2);
|
private W: number;
|
||||||
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);
|
constructor() {
|
||||||
const D = (H ** 2 - l3 ** 2 - l4 ** 2) / (2 * l3 * l4);
|
this.l1 = 50;
|
||||||
let theta3: number
|
this.l2 = 20;
|
||||||
try {
|
this.l3 = 120;
|
||||||
theta3 = acos(D);
|
this.l4 = 155;
|
||||||
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];
|
this.L = 140;
|
||||||
}
|
this.W = 75;
|
||||||
|
}
|
||||||
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[] {
|
bodyIK(
|
||||||
const rows = matrix.length;
|
omega: number,
|
||||||
const cols = matrix[0].length;
|
phi: number,
|
||||||
const vectorLength = vector.length;
|
psi: number,
|
||||||
|
xm: number,
|
||||||
if (cols !== vectorLength) {
|
ym: number,
|
||||||
throw new Error("Matrix and vector dimensions do not match for multiplication.");
|
zm: number
|
||||||
}
|
): number[][][] {
|
||||||
|
const { cos, sin } = Math;
|
||||||
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 [
|
const Rx: number[][] = [
|
||||||
this.legIK(this.multiplyVector(this.matrixInverse(Tlf), Lp[0])),
|
[1, 0, 0, 0],
|
||||||
this.legIK(this.multiplyVector(Ix, this.multiplyVector(this.matrixInverse(Trf), Lp[1]))),
|
[0, cos(omega), -sin(omega), 0],
|
||||||
this.legIK(this.multiplyVector(this.matrixInverse(Tlb), Lp[2])),
|
[0, sin(omega), cos(omega), 0],
|
||||||
this.legIK(this.multiplyVector(Ix, this.multiplyVector(this.matrixInverse(Trb), Lp[3]))),
|
[0, 0, 0, 1]
|
||||||
];
|
];
|
||||||
}
|
const Ry: number[][] = [
|
||||||
|
[cos(phi), 0, sin(phi), 0],
|
||||||
private vectorAdd(a: number[], b: number[]): number[] {
|
[0, 1, 0, 0],
|
||||||
return a.map((val, index) => val + b[index]);
|
[-sin(phi), 0, cos(phi), 0],
|
||||||
}
|
[0, 0, 0, 1]
|
||||||
|
];
|
||||||
private matrixInverse(matrix: number[][]): number[][] {
|
const Rz: number[][] = [
|
||||||
const det = this.determinant(matrix);
|
[cos(psi), -sin(psi), 0, 0],
|
||||||
const adjugate = this.adjugate(matrix);
|
[sin(psi), cos(psi), 0, 0],
|
||||||
const scalar = 1 / det;
|
[0, 0, 1, 0],
|
||||||
const inverse: number[][] = [];
|
[0, 0, 0, 1]
|
||||||
|
];
|
||||||
for (let i = 0; i < matrix.length; i++) {
|
const Rxyz: number[][] = this.matrixMultiply(this.matrixMultiply(Rx, Ry), Rz);
|
||||||
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 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 {
|
export class ForwardKinematics {
|
||||||
private l1: number;
|
private l1: number;
|
||||||
private l2: number;
|
private l2: number;
|
||||||
private l3: number;
|
private l3: number;
|
||||||
private l4: 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];
|
|
||||||
}
|
|
||||||
|
|
||||||
public calculateFootpoints(angles: number[]): number[][] {
|
constructor() {
|
||||||
const footpoints: number[][] = [];
|
this.l1 = 50;
|
||||||
|
this.l2 = 20;
|
||||||
for (let i = 0; i < angles.length; i += 3) {
|
this.l3 = 120;
|
||||||
const theta1 = angles[i];
|
this.l4 = 155;
|
||||||
const theta2 = angles[i + 1];
|
}
|
||||||
const theta3 = angles[i + 2];
|
|
||||||
const footpoint = this.calculateFootpoint(theta1, theta2, theta3);
|
public calculateFootpoint(theta1: number, theta2: number, theta3: number): number[] {
|
||||||
footpoints.push(footpoint);
|
const { cos, sin } = Math;
|
||||||
}
|
|
||||||
|
const x =
|
||||||
return footpoints;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+273
-248
@@ -1,293 +1,318 @@
|
|||||||
import { Mesh,
|
import {
|
||||||
PerspectiveCamera,
|
Mesh,
|
||||||
PlaneGeometry,
|
PerspectiveCamera,
|
||||||
Scene,
|
PlaneGeometry,
|
||||||
ShadowMaterial,
|
Scene,
|
||||||
WebGLRenderer,
|
ShadowMaterial,
|
||||||
AmbientLight,
|
WebGLRenderer,
|
||||||
DirectionalLight,
|
AmbientLight,
|
||||||
PCFSoftShadowMap,
|
DirectionalLight,
|
||||||
GridHelper,
|
PCFSoftShadowMap,
|
||||||
ArrowHelper,
|
GridHelper,
|
||||||
Vector3,
|
ArrowHelper,
|
||||||
LoaderUtils,
|
Vector3,
|
||||||
Object3D,
|
LoaderUtils,
|
||||||
FogExp2,
|
Object3D,
|
||||||
CanvasTexture,
|
FogExp2,
|
||||||
type ColorRepresentation,
|
CanvasTexture,
|
||||||
type WebGLRendererParameters,
|
type ColorRepresentation,
|
||||||
MeshPhongMaterial,
|
type WebGLRendererParameters,
|
||||||
EquirectangularReflectionMapping,
|
MeshPhongMaterial,
|
||||||
ACESFilmicToneMapping,
|
EquirectangularReflectionMapping,
|
||||||
MathUtils,
|
ACESFilmicToneMapping,
|
||||||
} from "three";
|
MathUtils
|
||||||
|
} from 'three';
|
||||||
import { Sky } from 'three/addons/objects/Sky.js';
|
import { Sky } from 'three/addons/objects/Sky.js';
|
||||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
||||||
import { type URDFMimicJoint } from "urdf-loader";
|
import { type URDFMimicJoint } from 'urdf-loader';
|
||||||
import { PointerURDFDragControls } from 'urdf-loader/src/URDFDragControls'
|
import { PointerURDFDragControls } from 'urdf-loader/src/URDFDragControls';
|
||||||
|
|
||||||
export const addScene = () => new Scene()
|
export const addScene = () => new Scene();
|
||||||
|
|
||||||
interface position {
|
interface position {
|
||||||
x?: number,
|
x?: number;
|
||||||
y?: number,
|
y?: number;
|
||||||
z?: number
|
z?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface light {
|
interface light {
|
||||||
color?: ColorRepresentation,
|
color?: ColorRepresentation;
|
||||||
intensity?: number
|
intensity?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface gridOptions {
|
interface gridOptions {
|
||||||
divisions?: number,
|
divisions?: number;
|
||||||
size?: number,
|
size?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface arrowOptions {
|
interface arrowOptions {
|
||||||
origin:position,
|
origin: position;
|
||||||
direction:position,
|
direction: position;
|
||||||
length?:number,
|
length?: number;
|
||||||
color?:ColorRepresentation
|
color?: ColorRepresentation;
|
||||||
}
|
}
|
||||||
|
|
||||||
type directionalLight = position & light
|
type directionalLight = position & light;
|
||||||
|
|
||||||
type gridHelperOptions = gridOptions & position
|
type gridHelperOptions = gridOptions & position;
|
||||||
|
|
||||||
function calculateCurrentSunElevation() {
|
function calculateCurrentSunElevation() {
|
||||||
let now = new Date();
|
let now = new Date();
|
||||||
let decimalTime = now.getHours() + now.getMinutes() / 60;
|
let decimalTime = now.getHours() + now.getMinutes() / 60;
|
||||||
let normalizedTime = ((decimalTime - 6) % 12) / 6 - 1;
|
let normalizedTime = (decimalTime % 12) / 6 - 1;
|
||||||
return 10 * Math.sin(normalizedTime * Math.PI);
|
return 10 * Math.sin(normalizedTime * Math.PI);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class SceneBuilder {
|
export default class SceneBuilder {
|
||||||
public scene: Scene
|
public scene: Scene;
|
||||||
public camera: PerspectiveCamera
|
public camera: PerspectiveCamera;
|
||||||
public ground: Mesh
|
public ground: Mesh;
|
||||||
public renderer:WebGLRenderer
|
public renderer: WebGLRenderer;
|
||||||
public controls:OrbitControls
|
public controls: OrbitControls;
|
||||||
public callback:Function
|
public callback: Function;
|
||||||
public gridHelper: GridHelper;
|
public gridHelper: GridHelper;
|
||||||
public model: Object3D<Event>
|
public model: Object3D<Event>;
|
||||||
public liveStreamTexture: CanvasTexture
|
public liveStreamTexture: CanvasTexture;
|
||||||
private fog:FogExp2
|
private fog: FogExp2;
|
||||||
private isLoaded:boolean = false
|
private isLoaded: boolean = false;
|
||||||
highlightMaterial: any;
|
highlightMaterial: any;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.scene = new Scene()
|
this.scene = new Scene();
|
||||||
if (this.scene.environment?.mapping) {
|
if (this.scene.environment?.mapping) {
|
||||||
this.scene.environment.mapping = EquirectangularReflectionMapping;
|
this.scene.environment.mapping = EquirectangularReflectionMapping;
|
||||||
}
|
}
|
||||||
return this
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public addRenderer = (parameters?: WebGLRendererParameters) => {
|
public addRenderer = (parameters?: WebGLRendererParameters) => {
|
||||||
this.renderer = new WebGLRenderer(parameters);
|
this.renderer = new WebGLRenderer(parameters);
|
||||||
this.renderer.outputColorSpace = "srgb";
|
this.renderer.outputColorSpace = 'srgb';
|
||||||
this.renderer.shadowMap.enabled = true;
|
this.renderer.shadowMap.enabled = true;
|
||||||
this.renderer.shadowMap.type = PCFSoftShadowMap;
|
this.renderer.shadowMap.type = PCFSoftShadowMap;
|
||||||
this.renderer.toneMapping = ACESFilmicToneMapping;
|
this.renderer.toneMapping = ACESFilmicToneMapping;
|
||||||
this.renderer.toneMappingExposure = 0.85;
|
this.renderer.toneMappingExposure = 0.85;
|
||||||
document.body.appendChild(this.renderer.domElement);
|
document.body.appendChild(this.renderer.domElement);
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
public addSky = () => {
|
public addSky = () => {
|
||||||
const sky = new Sky();
|
const sky = new Sky();
|
||||||
sky.scale.setScalar(450000);
|
sky.scale.setScalar(450000);
|
||||||
this.scene.add(sky);
|
this.scene.add(sky);
|
||||||
const effectController = {
|
const effectController = {
|
||||||
turbidity: 10,
|
turbidity: 10,
|
||||||
rayleigh: 3,
|
rayleigh: 3,
|
||||||
mieCoefficient: 0.005,
|
mieCoefficient: 0.005,
|
||||||
mieDirectionalG: 0.7,
|
mieDirectionalG: 0.7,
|
||||||
elevation: calculateCurrentSunElevation(),
|
elevation: calculateCurrentSunElevation(),
|
||||||
azimuth: 180,
|
azimuth: 180,
|
||||||
exposure: this.renderer.toneMappingExposure
|
exposure: this.renderer.toneMappingExposure
|
||||||
};
|
};
|
||||||
const uniforms = sky.material.uniforms;
|
const uniforms = sky.material.uniforms;
|
||||||
uniforms['turbidity'].value = effectController.turbidity;
|
uniforms['turbidity'].value = effectController.turbidity;
|
||||||
uniforms['rayleigh'].value = effectController.rayleigh;
|
uniforms['rayleigh'].value = effectController.rayleigh;
|
||||||
uniforms['mieCoefficient'].value = effectController.mieCoefficient;
|
uniforms['mieCoefficient'].value = effectController.mieCoefficient;
|
||||||
uniforms['mieDirectionalG'].value = effectController.mieDirectionalG;
|
uniforms['mieDirectionalG'].value = effectController.mieDirectionalG;
|
||||||
this.renderer.toneMappingExposure = 0.5;
|
this.renderer.toneMappingExposure = 0.5;
|
||||||
const phi = MathUtils.degToRad( 90 - effectController.elevation );
|
const phi = MathUtils.degToRad(90 - effectController.elevation);
|
||||||
const theta = MathUtils.degToRad( effectController.azimuth );
|
const theta = MathUtils.degToRad(effectController.azimuth);
|
||||||
const sun = new Vector3();
|
const sun = new Vector3();
|
||||||
|
|
||||||
sun.setFromSphericalCoords( 1, phi, theta );
|
sun.setFromSphericalCoords(1, phi, theta);
|
||||||
uniforms[ 'sunPosition' ].value.copy( sun );
|
uniforms['sunPosition'].value.copy(sun);
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
public addPerspectiveCamera = (options:position) => {
|
public addPerspectiveCamera = (options: position) => {
|
||||||
this.camera = new PerspectiveCamera();
|
this.camera = new PerspectiveCamera();
|
||||||
this.camera.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0);
|
this.camera.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0);
|
||||||
this.scene.add(this.camera);
|
this.scene.add(this.camera);
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
public addGroundPlane = (options:position) => {
|
public addGroundPlane = (options: position) => {
|
||||||
this.ground = new Mesh( new PlaneGeometry(), new ShadowMaterial({side: 2}));
|
this.ground = new Mesh(new PlaneGeometry(), new ShadowMaterial({ side: 2 }));
|
||||||
this.ground.rotation.x = -Math.PI / 2;
|
this.ground.rotation.x = -Math.PI / 2;
|
||||||
this.ground.scale.setScalar(30);
|
this.ground.scale.setScalar(30);
|
||||||
this.ground.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0);
|
this.ground.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0);
|
||||||
this.ground.receiveShadow = true;
|
this.ground.receiveShadow = true;
|
||||||
this.scene.add(this.ground);
|
this.scene.add(this.ground);
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
public addOrbitControls = (minDistance:number, maxDistance:number) => {
|
public addOrbitControls = (minDistance: number, maxDistance: number) => {
|
||||||
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
||||||
this.controls.minDistance = minDistance;
|
this.controls.minDistance = minDistance;
|
||||||
this.controls.maxDistance = maxDistance;
|
this.controls.maxDistance = maxDistance;
|
||||||
this.controls.update();
|
this.controls.update();
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
public addAmbientLight = (options:light) => {
|
public addAmbientLight = (options: light) => {
|
||||||
const ambientLight = new AmbientLight(options.color, options.intensity);
|
const ambientLight = new AmbientLight(options.color, options.intensity);
|
||||||
this.scene.add(ambientLight);
|
this.scene.add(ambientLight);
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
public addDirectionalLight = (options:directionalLight) => {
|
public addDirectionalLight = (options: directionalLight) => {
|
||||||
const directionalLight = new DirectionalLight(options.color, options.intensity);
|
const directionalLight = new DirectionalLight(options.color, options.intensity);
|
||||||
directionalLight.castShadow = true;
|
directionalLight.castShadow = true;
|
||||||
directionalLight.shadow.mapSize.setScalar(2048);
|
directionalLight.shadow.mapSize.setScalar(2048);
|
||||||
directionalLight.shadow.mapSize.width = 1024;
|
directionalLight.shadow.mapSize.width = 1024;
|
||||||
directionalLight.shadow.mapSize.height = 1024;
|
directionalLight.shadow.mapSize.height = 1024;
|
||||||
directionalLight.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0);
|
directionalLight.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0);
|
||||||
directionalLight.shadow.radius = 5
|
directionalLight.shadow.radius = 5;
|
||||||
this.scene.add(directionalLight);
|
this.scene.add(directionalLight);
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
public addGridHelper = (options:gridHelperOptions) => {
|
public addGridHelper = (options: gridHelperOptions) => {
|
||||||
this.gridHelper = new GridHelper(options.size, options.divisions);
|
this.gridHelper = new GridHelper(options.size, options.divisions);
|
||||||
this.gridHelper.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0);
|
this.gridHelper.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0);
|
||||||
this.gridHelper.material.opacity = 0.2;
|
this.gridHelper.material.opacity = 0.2;
|
||||||
this.gridHelper.material.depthWrite = false;
|
this.gridHelper.material.depthWrite = false;
|
||||||
this.gridHelper.material.transparent = true;
|
this.gridHelper.material.transparent = true;
|
||||||
this.scene.add(this.gridHelper);
|
this.scene.add(this.gridHelper);
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
public addFogExp2 = (color:ColorRepresentation, density?:number) => {
|
public addFogExp2 = (color: ColorRepresentation, density?: number) => {
|
||||||
this.scene.fog = new FogExp2(color, density);
|
this.scene.fog = new FogExp2(color, density);
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
public handleResize = () => {
|
public handleResize = () => {
|
||||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
this.renderer.setPixelRatio(window.devicePixelRatio);
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
||||||
this.camera.aspect = window.innerWidth / window.innerHeight;
|
this.camera.aspect = window.innerWidth / window.innerHeight;
|
||||||
this.camera.updateProjectionMatrix();
|
this.camera.updateProjectionMatrix();
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
public addRenderCb = (callback:Function) => {
|
public addRenderCb = (callback: Function) => {
|
||||||
this.callback = callback
|
this.callback = callback;
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
public startRenderLoop = () => {
|
public startRenderLoop = () => {
|
||||||
this.renderer.setAnimationLoop(() => {
|
this.renderer.setAnimationLoop(() => {
|
||||||
this.renderer.render(this.scene, this.camera);
|
this.renderer.render(this.scene, this.camera);
|
||||||
this.handleRobotShadow()
|
this.handleRobotShadow();
|
||||||
if(this.callback) this.callback()
|
if (this.callback) this.callback();
|
||||||
if(!this.liveStreamTexture) return
|
if (!this.liveStreamTexture) return;
|
||||||
});
|
});
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
public addArrowHelper = (options?:arrowOptions) => {
|
public addArrowHelper = (options?: arrowOptions) => {
|
||||||
const dir = new Vector3(options?.direction.x ?? 0, options?.direction.y ?? 0, options?.direction.z ?? 0);
|
const dir = new Vector3(
|
||||||
const origin = new Vector3(options?.origin.x ?? 0, options?.origin.y ?? 0, options?.origin.z ?? 0);
|
options?.direction.x ?? 0,
|
||||||
const arrowHelper = new ArrowHelper( dir, origin, options?.length ?? 1.5, options?.color ?? 0xff0000 );
|
options?.direction.y ?? 0,
|
||||||
this.scene.add( arrowHelper );
|
options?.direction.z ?? 0
|
||||||
return this
|
);
|
||||||
}
|
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) {
|
private setJointValue(jointName: string, angle: number) {
|
||||||
if (!this.model) return;
|
if (!this.model) return;
|
||||||
if (!this.model.joints[jointName]) return;
|
if (!this.model.joints[jointName]) return;
|
||||||
this.model.joints[jointName].setJointValue(angle)
|
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) => {
|
highlightLinkGeometry = (m: URDFMimicJoint, revert: boolean, material: MeshPhongMaterial) => {
|
||||||
const traverse = c => {
|
const traverse = (c) => {
|
||||||
if (c.type === 'Mesh') {
|
if (c.type === 'Mesh') {
|
||||||
if (revert) {
|
if (revert) {
|
||||||
c.material = c.__origMaterial;
|
c.material = c.__origMaterial;
|
||||||
delete c.__origMaterial;
|
delete c.__origMaterial;
|
||||||
} else {
|
} else {
|
||||||
c.__origMaterial = c.material;
|
c.__origMaterial = c.material;
|
||||||
c.material = material;
|
c.material = material;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c === m || !this.isJoint(c)) {
|
if (c === m || !this.isJoint(c)) {
|
||||||
for (let i = 0; i < c.children.length; i++) {
|
for (let i = 0; i < c.children.length; i++) {
|
||||||
const child = c.children[i];
|
const child = c.children[i];
|
||||||
if (!child.isURDFCollider) {
|
if (!child.isURDFCollider) {
|
||||||
traverse(c.children[i]);
|
traverse(c.children[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
traverse(m);
|
traverse(m);
|
||||||
};
|
};
|
||||||
|
|
||||||
public addModel = (model: any) => {
|
public addModel = (model: any) => {
|
||||||
this.model = model
|
this.model = model;
|
||||||
this.scene.add(model)
|
this.scene.add(model);
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
public addDragControl = (updateAngle:any) => {
|
public addDragControl = (updateAngle: any) => {
|
||||||
const highlightColor = '#FFFFFF'
|
const highlightColor = '#FFFFFF';
|
||||||
const highlightMaterial =
|
const highlightMaterial = new MeshPhongMaterial({
|
||||||
new MeshPhongMaterial({
|
shininess: 10,
|
||||||
shininess: 10,
|
color: highlightColor,
|
||||||
color: highlightColor,
|
emissive: highlightColor,
|
||||||
emissive: highlightColor,
|
emissiveIntensity: 0.25
|
||||||
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);
|
|
||||||
|
|
||||||
this.renderer.domElement.addEventListener('touchstart', (data) => dragControls._mouseDown(data.touches[0]));
|
const dragControls = new PointerURDFDragControls(
|
||||||
this.renderer.domElement.addEventListener('touchmove', (data) => dragControls._mouseMove(data.touches[0]))
|
this.scene,
|
||||||
this.renderer.domElement.addEventListener('touchup', (data) => dragControls._mouseUp(data.touches[0]));
|
this.camera,
|
||||||
return this
|
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.renderer.domElement.addEventListener('touchstart', (data) =>
|
||||||
this.scene.fog = this.scene.fog ? null : this.fog;
|
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 = () => {
|
public toggleFog = () => {
|
||||||
if(this.isLoaded) return
|
this.scene.fog = this.scene.fog ? null : this.fog;
|
||||||
const intervalId = setInterval(() => {
|
};
|
||||||
this.model?.traverse(c => c.castShadow = true);
|
|
||||||
}, 10);
|
private handleRobotShadow = () => {
|
||||||
setTimeout(() => {
|
if (this.isLoaded) return;
|
||||||
clearInterval(intervalId)
|
const intervalId = setInterval(() => {
|
||||||
}, 1000);
|
this.model?.traverse((c) => (c.castShadow = true));
|
||||||
this.isLoaded = true;
|
}, 10);
|
||||||
}
|
setTimeout(() => {
|
||||||
}
|
clearInterval(intervalId);
|
||||||
|
}, 1000);
|
||||||
|
this.isLoaded = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,72 +1,71 @@
|
|||||||
import { Result } from '$lib/utilities/result';
|
import { Result } from '$lib/utilities/result';
|
||||||
|
|
||||||
class FileService {
|
class FileService {
|
||||||
private dbName = 'fileStorageDB';
|
private dbName = 'fileStorageDB';
|
||||||
private dbVersion = 1;
|
private dbVersion = 1;
|
||||||
private storeName = 'files';
|
private storeName = 'files';
|
||||||
private dbPromise: Promise<Result<IDBDatabase, string>>;
|
private dbPromise: Promise<Result<IDBDatabase, string>>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.dbPromise = this.openDatabase();
|
this.dbPromise = this.openDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async openDatabase(): Promise<Result<IDBDatabase, string>> {
|
private async openDatabase(): Promise<Result<IDBDatabase, string>> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const request = indexedDB.open(this.dbName, this.dbVersion);
|
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) => {
|
request.onupgradeneeded = (event) => {
|
||||||
const db = request.result;
|
const db = request.result;
|
||||||
if (!db.objectStoreNames.contains(this.storeName)) {
|
if (!db.objectStoreNames.contains(this.storeName)) {
|
||||||
db.createObjectStore(this.storeName);
|
db.createObjectStore(this.storeName);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getStore(mode: IDBTransactionMode): Promise<Result<IDBObjectStore, string>> {
|
private async getStore(mode: IDBTransactionMode): Promise<Result<IDBObjectStore, string>> {
|
||||||
const dbResult = await this.dbPromise;
|
const dbResult = await this.dbPromise;
|
||||||
if (dbResult.isErr()) {
|
if (dbResult.isErr()) {
|
||||||
return Result.err("Database not initialized properly");
|
return Result.err('Database not initialized properly');
|
||||||
}
|
}
|
||||||
const db = dbResult.inner;
|
const db = dbResult.inner;
|
||||||
const transaction = db.transaction(this.storeName, mode);
|
const transaction = db.transaction(this.storeName, mode);
|
||||||
return Result.ok(transaction.objectStore(this.storeName));
|
return Result.ok(transaction.objectStore(this.storeName));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveFile(key: string, file: Uint8Array): Promise<Result<IDBValidKey, string>> {
|
public async saveFile(key: string, file: Uint8Array): Promise<Result<IDBValidKey, string>> {
|
||||||
const storeResult = await this.getStore("readwrite");
|
const storeResult = await this.getStore('readwrite');
|
||||||
if (storeResult.isErr()) {
|
if (storeResult.isErr()) {
|
||||||
return Result.err("Failed to access object store for writing");
|
return Result.err('Failed to access object store for writing');
|
||||||
}
|
}
|
||||||
const store = storeResult.inner;
|
const store = storeResult.inner;
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const request = store.put(file, key);
|
const request = store.put(file, key);
|
||||||
request.onsuccess = () => resolve(Result.ok(request.result));
|
request.onsuccess = () => resolve(Result.ok(request.result));
|
||||||
request.onerror = () => resolve(Result.err("Failed to save file"));
|
request.onerror = () => resolve(Result.err('Failed to save file'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getFile(key: string): Promise<Result<Uint8Array | undefined, string>> {
|
public async getFile(key: string): Promise<Result<Uint8Array | undefined, string>> {
|
||||||
const storeResult = await this.getStore("readonly");
|
const storeResult = await this.getStore('readonly');
|
||||||
if (storeResult.isErr()) {
|
if (storeResult.isErr()) {
|
||||||
return Result.err("Failed to access object store for reading");
|
return Result.err('Failed to access object store for reading');
|
||||||
}
|
}
|
||||||
const store = storeResult.inner;
|
const store = storeResult.inner;
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const request = store.get(key);
|
const request = store.get(key);
|
||||||
|
|
||||||
request.onsuccess = () =>
|
request.onsuccess = () =>
|
||||||
resolve(request.result ? Result.ok(request.result) : Result.err("File content not found"))
|
resolve(request.result ? Result.ok(request.result) : Result.err('File content not found'));
|
||||||
request.onerror = () =>
|
request.onerror = () => resolve(Result.err('Failed to retrieve file'));
|
||||||
resolve(Result.err("Failed to retrieve file"));
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new FileService();
|
export default new FileService();
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export * from './file-service'
|
export { default as fileService } from './file-service';
|
||||||
export * from './socket-service'
|
export { default as socketService } from './socket-service';
|
||||||
|
|||||||
@@ -1,89 +1,100 @@
|
|||||||
import { Result, Ok } from '$lib/utilities';
|
import { Result, Ok } from '$lib/utilities';
|
||||||
import { writable, type Writable } from 'svelte/store';
|
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 {
|
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<Int16Array | number[]> = writable(new Int16Array(12));
|
||||||
|
public data = writable();
|
||||||
|
private socket!: WebSocket;
|
||||||
|
|
||||||
public isConnected = writable(false);
|
constructor() {}
|
||||||
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<Int16Array | number[]> = writable(new Int16Array(12));
|
|
||||||
public data = writable();
|
|
||||||
private socket!: WebSocket;
|
|
||||||
|
|
||||||
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 {
|
public send(data: WebsocketData): Result<void, string> {
|
||||||
this.socket = new WebSocket(url);
|
if (this.socket.readyState === WebSocket.OPEN) {
|
||||||
this.socket.binaryType = "arraybuffer";
|
this.socket.send(data);
|
||||||
this.socket.onopen = () => this.handleConnected();
|
return Ok.void();
|
||||||
this.socket.onclose = () => this.handleDisconnected();
|
}
|
||||||
this.socket.onmessage = (event: unknown) => this.handleMessage(event);
|
return Result.err('The connection is not open');
|
||||||
}
|
}
|
||||||
|
|
||||||
public send(data: WebsocketData): Result<void, string> {
|
private handleConnected(): void {
|
||||||
if (this.socket.readyState === WebSocket.OPEN){
|
this.isConnected.set(true);
|
||||||
this.socket.send(data)
|
}
|
||||||
return Ok.void()
|
|
||||||
}
|
|
||||||
return Result.err("The connection is not open")
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleConnected(): void {
|
private handleDisconnected(): void {
|
||||||
this.isConnected.set(true);
|
this.isConnected.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleDisconnected(): void {
|
private handleMessage(event: any): void {
|
||||||
this.isConnected.set(false);
|
if (event.data instanceof ArrayBuffer) {
|
||||||
}
|
let buffer = new Int8Array(event.data);
|
||||||
|
if (buffer.length === 44) {
|
||||||
private handleMessage(event: any): void {
|
this.dataBuffer.set(new Float32Array(buffer.buffer));
|
||||||
if (event.data instanceof ArrayBuffer) {
|
}
|
||||||
let buffer = new Int8Array(event.data);
|
} else {
|
||||||
if (buffer.length === 44) {
|
let data = event.data;
|
||||||
this.dataBuffer.set(new Float32Array(buffer.buffer));
|
try {
|
||||||
}
|
data = JSON.parse(event.data);
|
||||||
} else {
|
} catch (error) {
|
||||||
let data = event.data;
|
console.warn(error);
|
||||||
try {
|
}
|
||||||
data = JSON.parse(event.data);
|
switch (data.type) {
|
||||||
} catch (error) {
|
case 'angles':
|
||||||
console.warn(error);
|
this.angles.set(data.angles);
|
||||||
}
|
break;
|
||||||
switch (data.type) {
|
case 'logs':
|
||||||
case "angles":
|
this.log.set(data.logs);
|
||||||
this.angles.set(data.angles);
|
break;
|
||||||
break;
|
case 'log':
|
||||||
case "logs":
|
this.log.update((entries) => {
|
||||||
this.log.set(data.logs);
|
entries.push(data.log);
|
||||||
break;
|
return entries;
|
||||||
case "log":
|
});
|
||||||
this.log.update(entries => { entries.push(data.log); return entries; });
|
break;
|
||||||
break;
|
case 'settings':
|
||||||
case "settings":
|
this.settings.set(data.settings);
|
||||||
this.settings.set(data.settings);
|
case 'info':
|
||||||
case "info":
|
this.systemInfo.set(data.info);
|
||||||
this.systemInfo.set(data.info);
|
break;
|
||||||
break;
|
case 'mpu':
|
||||||
case "mpu":
|
this.mpu.set(data.mpu);
|
||||||
this.mpu.set(data.mpu);
|
break;
|
||||||
break;
|
case 'distances':
|
||||||
case "distances":
|
this.distances.set(data.distances);
|
||||||
this.distances.set(data.distances);
|
break;
|
||||||
break;
|
case 'battery':
|
||||||
case "battery":
|
this.battery.set(data.battery);
|
||||||
this.battery.set(data.battery);
|
break;
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new SocketService();
|
export default new SocketService();
|
||||||
|
|||||||
@@ -3,10 +3,15 @@ import { persistentStore } from '$lib/utilities';
|
|||||||
|
|
||||||
export const emulateModel = writable(true);
|
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 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()
|
export const model = writable();
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
export class throttler {
|
export class throttler {
|
||||||
private _throttlePause: boolean;
|
private _throttlePause: boolean;
|
||||||
constructor() {
|
constructor() {
|
||||||
this._throttlePause = false;
|
this._throttlePause = false;
|
||||||
}
|
}
|
||||||
throttle = (callback:Function, time:number) => {
|
throttle = (callback: Function, time: number) => {
|
||||||
if (this._throttlePause) return;
|
if (this._throttlePause) return;
|
||||||
|
|
||||||
this._throttlePause = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
callback();
|
|
||||||
this._throttlePause = false;
|
|
||||||
}, time);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this._throttlePause = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
callback();
|
||||||
|
this._throttlePause = false;
|
||||||
|
}, time);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export * from './result'
|
export * from './result';
|
||||||
export * from './string-utilities'
|
export * from './string-utilities';
|
||||||
export * from './svelte-utilities'
|
export * from './svelte-utilities';
|
||||||
export * from './math-utilities'
|
export * from './math-utilities';
|
||||||
export * from './buffer-utilities'
|
export * from './buffer-utilities';
|
||||||
export * from './model-utilities'
|
export * from './model-utilities';
|
||||||
export * from './location-utilities'
|
export * from './location-utilities';
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
const forWeb = import.meta.env.MODE === "WEB"
|
const forWeb = import.meta.env.MODE === 'WEB';
|
||||||
const mock = import.meta.env.MODE === "MOCK"
|
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}`;
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export const lerp = (start: number, end: number, amt: number) => {
|
export const lerp = (start: number, end: number, amt: number) => {
|
||||||
return (1 - amt) * start + amt * end;
|
return (1 - amt) * start + amt * end;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,32 +1,38 @@
|
|||||||
import { LoaderUtils } from "three";
|
import { LoaderUtils } from 'three';
|
||||||
import URDFLoader, { type URDFRobot } from "urdf-loader"
|
import URDFLoader, { type URDFRobot } from 'urdf-loader';
|
||||||
import { XacroLoader } from "xacro-parser"
|
import { XacroLoader } from 'xacro-parser';
|
||||||
import { Result } from "$lib/utilities";
|
import { Result } from '$lib/utilities';
|
||||||
|
|
||||||
export const loadModelAsync = async (url:string):Promise<Result<[URDFRobot, string[]], string>> => {
|
export const loadModelAsync = async (
|
||||||
return new Promise((resolve, reject) => {
|
url: string
|
||||||
const xacroLoader = new XacroLoader();
|
): Promise<Result<[URDFRobot, string[]], string>> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
xacroLoader.load(url, async (xml) => {
|
const xacroLoader = new XacroLoader();
|
||||||
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]));
|
xacroLoader.load(
|
||||||
} catch (error) {
|
url,
|
||||||
resolve(Result.err("Failed to load model", error));
|
async (xml) => {
|
||||||
}
|
const urdfLoader = new URDFLoader();
|
||||||
}, (error) => reject(error));
|
|
||||||
});
|
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)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,42 +1,42 @@
|
|||||||
export class Err<T, U> {
|
export class Err<T, U> {
|
||||||
#inner: T
|
#inner: T;
|
||||||
#exception?: U
|
#exception?: U;
|
||||||
|
|
||||||
constructor(inner: T, exception?: U) {
|
constructor(inner: T, exception?: U) {
|
||||||
this.#inner = inner
|
this.#inner = inner;
|
||||||
this.#exception = exception
|
this.#exception = exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
get inner(): T {
|
get inner(): T {
|
||||||
return this.#inner
|
return this.#inner;
|
||||||
}
|
}
|
||||||
|
|
||||||
get exception(): U | undefined {
|
get exception(): U | undefined {
|
||||||
return this.#exception;
|
return this.#exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type guard for `Ok`
|
* Type guard for `Ok`
|
||||||
* @returns `true` if `Ok`; `false` if `Err`
|
* @returns `true` if `Ok`; `false` if `Err`
|
||||||
*/
|
*/
|
||||||
isOk(): false {
|
isOk(): false {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type guard for `Err`
|
* Type guard for `Err`
|
||||||
* @returns `true` if `Err`; `false` if `Ok`
|
* @returns `true` if `Err`; `false` if `Ok`
|
||||||
*/
|
*/
|
||||||
isErr(): this is Err<T, U> {
|
isErr(): this is Err<T, U> {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an `Err`
|
* Create an `Err`
|
||||||
* @param inner
|
* @param inner
|
||||||
* @returns `Err(inner)`
|
* @returns `Err(inner)`
|
||||||
*/
|
*/
|
||||||
static new<E, F>(inner: E, exception: F): Err<E, F> {
|
static new<E, F>(inner: E, exception: F): Err<E, F> {
|
||||||
return new Err<E, F>(inner, exception)
|
return new Err<E, F>(inner, exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export * from './err'
|
export * from './err';
|
||||||
export * from './ok'
|
export * from './ok';
|
||||||
export * from './result'
|
export * from './result';
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
export class Ok<T> {
|
export class Ok<T> {
|
||||||
#inner: T
|
#inner: T;
|
||||||
|
|
||||||
constructor(inner: T) {
|
constructor(inner: T) {
|
||||||
this.#inner = inner
|
this.#inner = inner;
|
||||||
}
|
}
|
||||||
|
|
||||||
get inner(): T {
|
get inner(): T {
|
||||||
return this.#inner
|
return this.#inner;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type guard for `Ok`
|
* Type guard for `Ok`
|
||||||
* @returns `true` if `Ok`; `false` if `Err`
|
* @returns `true` if `Ok`; `false` if `Err`
|
||||||
*/
|
*/
|
||||||
isOk(): this is Ok<T> {
|
isOk(): this is Ok<T> {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type guard for `Err`
|
* Type guard for `Err`
|
||||||
* @returns `true` if `Err`; `false` if `Ok`
|
* @returns `true` if `Err`; `false` if `Ok`
|
||||||
*/
|
*/
|
||||||
isErr(): false {
|
isErr(): false {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an `Ok`
|
* Create an `Ok`
|
||||||
* @param inner
|
* @param inner
|
||||||
* @returns `Ok(inner)`
|
* @returns `Ok(inner)`
|
||||||
*/
|
*/
|
||||||
static new<T>(inner: T): Ok<T> {
|
static new<T>(inner: T): Ok<T> {
|
||||||
return new Ok<T>(inner)
|
return new Ok<T>(inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an empty `Ok`
|
* Create an empty `Ok`
|
||||||
* @returns `Ok(void)`
|
* @returns `Ok(void)`
|
||||||
*/
|
*/
|
||||||
static void(): Ok<void> {
|
static void(): Ok<void> {
|
||||||
return new Ok(undefined)
|
return new Ok(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import { Err } from './err'
|
import { Err } from './err';
|
||||||
import { Ok } from './ok'
|
import { Ok } from './ok';
|
||||||
|
|
||||||
export type Result<T = unknown, E = unknown, F = unknown> = Ok<T> | Err<E, F>
|
export type Result<T = unknown, E = unknown, F = unknown> = Ok<T> | Err<E, F>;
|
||||||
|
|
||||||
export namespace Result {
|
export namespace Result {
|
||||||
/**
|
/**
|
||||||
* @returns `Ok<T>`
|
* @returns `Ok<T>`
|
||||||
*/
|
*/
|
||||||
export function ok<T = unknown>(value: T) {
|
export function ok<T = unknown>(value: T) {
|
||||||
return Ok.new(value)
|
return Ok.new(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns `Err<E, F>`
|
* @returns `Err<E, F>`
|
||||||
*/
|
*/
|
||||||
export function err<E = unknown, F = unknown>(error: E, exception?: F) {
|
export function err<E = unknown, F = unknown>(error: E, exception?: F) {
|
||||||
return Err.new(error, exception)
|
return Err.new(error, exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export const humanFileSize = (size:number):string => {
|
export const humanFileSize = (size: number): string => {
|
||||||
const units = ['B', 'kB', 'MB', 'GB', 'TB']
|
const units = ['B', 'kB', 'MB', 'GB', 'TB'];
|
||||||
var i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
|
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];
|
return Number((size / Math.pow(1024, i)).toFixed(2)) * 1 + units[i];
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { writable } from "svelte/store";
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
export const persistentStore = (key:string, initialValue:any) => {
|
export const persistentStore = (key: string, initialValue: any) => {
|
||||||
const savedValue = JSON.parse(localStorage.getItem(key) as string);
|
const savedValue = JSON.parse(localStorage.getItem(key) as string);
|
||||||
const data = savedValue !== null ? savedValue : initialValue;
|
const data = savedValue !== null ? savedValue : initialValue;
|
||||||
const store = writable(data);
|
const store = writable(data);
|
||||||
|
|
||||||
store.subscribe(value => {
|
store.subscribe((value) => {
|
||||||
localStorage.setItem(key, JSON.stringify(value));
|
localStorage.setItem(key, JSON.stringify(value));
|
||||||
});
|
});
|
||||||
|
|
||||||
return store;
|
return store;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Stream from '$components/Views/Stream.svelte';
|
import Stream from '$components/Views/Stream.svelte';
|
||||||
import Model from '$components/Views/Model.svelte';
|
import Model from '$components/Views/Model.svelte';
|
||||||
import Controls from '$components/Controls.svelte';
|
import Controls from '$components/Controls.svelte';
|
||||||
import { emulateModel } from '$lib/store';
|
import { emulateModel } from '$lib/store';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex justify-center items-center w-full h-full">
|
<div class="flex justify-center items-center w-full h-full">
|
||||||
{#if $emulateModel}
|
{#if $emulateModel}
|
||||||
<Model />
|
<Model />
|
||||||
{:else}
|
{:else}
|
||||||
<Stream />
|
<Stream />
|
||||||
{/if}
|
{/if}
|
||||||
<Controls />
|
<Controls />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,54 +3,63 @@
|
|||||||
import Info from '../components/settings/Info.svelte';
|
import Info from '../components/settings/Info.svelte';
|
||||||
import Log from '../components/settings/Log.svelte';
|
import Log from '../components/settings/Log.svelte';
|
||||||
import Configuration from '../components/settings/Configuration.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';
|
import Calibration from '../components/settings/Calibration.svelte';
|
||||||
|
|
||||||
export const page = ""
|
export const page = '';
|
||||||
|
|
||||||
const menu = [
|
const menu = [
|
||||||
{
|
{
|
||||||
title: 'Calibration',
|
title: 'Calibration',
|
||||||
path: '/calibration',
|
path: '/calibration',
|
||||||
icon: AdjustmentsVertical,
|
icon: AdjustmentsVertical,
|
||||||
component: Calibration
|
component: Calibration
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'System info',
|
title: 'System info',
|
||||||
path: '/info',
|
path: '/info',
|
||||||
icon: InformationCircle,
|
icon: InformationCircle,
|
||||||
component: Info
|
component: Info
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Log',
|
title: 'Log',
|
||||||
path: '/log',
|
path: '/log',
|
||||||
icon: BookOpen,
|
icon: BookOpen,
|
||||||
component: Log
|
component: Log
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
icon: Cog6Tooth,
|
icon: Cog6Tooth,
|
||||||
component: Configuration
|
component: Configuration
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="pt-14 flex h-full">
|
<div class="pt-14 flex h-full">
|
||||||
<nav class="w-1/6 flex flex-col">
|
<nav class="w-1/6 flex flex-col">
|
||||||
{#each menu as link}
|
{#each menu as link}
|
||||||
<Link to={'/settings'+link.path}>
|
<Link to={'/settings' + link.path}>
|
||||||
<div class="px-4 py-2 flex gap-2 items-center">
|
<div class="px-4 py-2 flex gap-2 items-center">
|
||||||
<Icon src={link.icon} size="24" />{link.title}
|
<Icon src={link.icon} size="24" />{link.title}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
{/each}
|
{/each}
|
||||||
</nav>
|
</nav>
|
||||||
<main class="w-full h-full">
|
<main class="w-full h-full">
|
||||||
<Router>
|
<Router>
|
||||||
{#each menu as link}
|
{#each menu as link}
|
||||||
<Route path={link.path} component={link.component}></Route>
|
<Route path={link.path} component={link.component}></Route>
|
||||||
{/each}
|
{/each}
|
||||||
</Router>
|
</Router>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+16
-16
@@ -2,22 +2,22 @@
|
|||||||
export default {
|
export default {
|
||||||
content: ['./src/**/*.{html,js,ts,svelte}'],
|
content: ['./src/**/*.{html,js,ts,svelte}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
'primary': '#6200EE',
|
primary: '#6200EE',
|
||||||
'primary-variant': '#3700B3',
|
'primary-variant': '#3700B3',
|
||||||
'secondary': '#3700B3',
|
secondary: '#3700B3',
|
||||||
'secondary-variant': '#3700B3',
|
'secondary-variant': '#3700B3',
|
||||||
'background': '#1e1e1e',
|
background: '#1e1e1e',
|
||||||
'surface': '#2c2c2c',
|
surface: '#2c2c2c',
|
||||||
'error': '#B00020',
|
error: '#B00020',
|
||||||
'on-primary': '#FFFFFF',
|
'on-primary': '#FFFFFF',
|
||||||
'on-secondary': '#FFFFFF',
|
'on-secondary': '#FFFFFF',
|
||||||
'on-background': '#FFFFFF',
|
'on-background': '#FFFFFF',
|
||||||
'on-surface': '#FFFFFF',
|
'on-surface': '#FFFFFF',
|
||||||
'on-error': '#FFFFFF',
|
'on-error': '#FFFFFF'
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
plugins: []
|
plugins: []
|
||||||
};
|
};
|
||||||
|
|||||||
+29
-29
@@ -2,38 +2,38 @@ import { describe, it, expect } from 'vitest';
|
|||||||
import { Result } from '../src/lib/utilities';
|
import { Result } from '../src/lib/utilities';
|
||||||
|
|
||||||
describe('Result', () => {
|
describe('Result', () => {
|
||||||
it('should create a success result correctly', () => {
|
it('should create a success result correctly', () => {
|
||||||
const successValue = "Success value";
|
const successValue = 'Success value';
|
||||||
const result = Result.ok(successValue);
|
const result = Result.ok(successValue);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(result.isErr()).toBe(false);
|
expect(result.isErr()).toBe(false);
|
||||||
expect(result.inner).toBe(successValue);
|
expect(result.inner).toBe(successValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create an error result correctly', () => {
|
it('should create an error result correctly', () => {
|
||||||
const errorMessage = "Error message";
|
const errorMessage = 'Error message';
|
||||||
const result = Result.err(errorMessage);
|
const result = Result.err(errorMessage);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(false);
|
expect(result.isOk()).toBe(false);
|
||||||
expect(result.isErr()).toBe(true);
|
expect(result.isErr()).toBe(true);
|
||||||
expect(result.inner).toBe(errorMessage);
|
expect(result.inner).toBe(errorMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should type guard success and error results correctly', () => {
|
it('should type guard success and error results correctly', () => {
|
||||||
const successResult = Result.ok(123);
|
const successResult = Result.ok(123);
|
||||||
const errorResult = Result.err("Error");
|
const errorResult = Result.err('Error');
|
||||||
|
|
||||||
if (successResult.isOk()) {
|
if (successResult.isOk()) {
|
||||||
expect(typeof successResult.inner).toBe('number');
|
expect(typeof successResult.inner).toBe('number');
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Expected successResult to be ok');
|
throw new Error('Expected successResult to be ok');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorResult.isErr()) {
|
if (errorResult.isErr()) {
|
||||||
expect(typeof errorResult.inner).toBe('string');
|
expect(typeof errorResult.inner).toBe('string');
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Expected errorResult to be fail');
|
throw new Error('Expected errorResult to be fail');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-3
@@ -5,7 +5,7 @@
|
|||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
/**
|
/**
|
||||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||||
@@ -15,13 +15,12 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"$lib/*": ["./src/lib/*"],
|
"$lib/*": ["./src/lib/*"],
|
||||||
"$utils/*": ["./src/utils/*"],
|
"$utils/*": ["./src/utils/*"],
|
||||||
"$components/*": ["./src/components/*"],
|
"$components/*": ["./src/components/*"],
|
||||||
"$stores/*": ["./src/stores/*"]
|
"$stores/*": ["./src/stores/*"]
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
|||||||
+15
-13
@@ -2,24 +2,26 @@ import { defineConfig } from 'vite';
|
|||||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||||
import { viteSingleFile } from 'vite-plugin-singlefile';
|
import { viteSingleFile } from 'vite-plugin-singlefile';
|
||||||
import viteCompression from 'vite-plugin-compression';
|
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/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [svelte(),
|
plugins: [
|
||||||
...(forEmbedded ? [ viteSingleFile(), viteCompression({deleteOriginFile: true})]: [])],
|
svelte(),
|
||||||
|
...(forEmbedded ? [viteSingleFile(), viteCompression({ deleteOriginFile: true })] : [])
|
||||||
|
],
|
||||||
build: {
|
build: {
|
||||||
outDir: forEmbedded ? '../data': './build',
|
outDir: forEmbedded ? '../data' : './build',
|
||||||
emptyOutDir: true
|
emptyOutDir: true
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'$lib': path.resolve('./src/lib/'),
|
$lib: path.resolve('./src/lib/'),
|
||||||
'$components': path.resolve('./src/components'),
|
$components: path.resolve('./src/components'),
|
||||||
'$utils': path.resolve('./src/utils'),
|
$utils: path.resolve('./src/utils'),
|
||||||
'$stores': path.resolve('./src/stores'),
|
$stores: path.resolve('./src/stores')
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user