Compare commits
16 Commits
pre-module
...
sample-del
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de0d397dcd | ||
|
|
da424d1637 | ||
|
|
2c6fee9aad | ||
|
|
2828eaafb5 | ||
|
|
821800beb1 | ||
|
|
8533747670 | ||
|
|
160ff54368 | ||
|
|
79faa67dd8 | ||
|
|
78c4445b93 | ||
|
|
0812142359 | ||
|
|
39840f469f | ||
|
|
b581e310ed | ||
|
|
c579544351 | ||
|
|
aa08a75aef | ||
|
|
6187976b6b | ||
|
|
59e9203363 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -25,6 +25,12 @@ vite.config.ts.timestamp-*
|
||||
/src-tauri/target/
|
||||
/src-tauri/target-codex-check*/
|
||||
/src-tauri/gen/schemas/
|
||||
/src-tauri/gen/android/app/build/
|
||||
/src-tauri/gen/android/buildSrc/build/
|
||||
/src-tauri/gen/android/.gradle/
|
||||
/src-tauri/gen/android/app/.gradle/
|
||||
/src-tauri/gen/android/buildSrc/.gradle/
|
||||
/src-tauri/gen/android/build/reports/
|
||||
|
||||
/src-tauri/program.log*
|
||||
/src-tauri/recording_replay_debug_*.csv
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "eskin-finger-sdk"]
|
||||
path = eskin-finger-sdk
|
||||
url = https://gitea.e-skin.top/yanjie/eskin-finger-sdk.git
|
||||
BIN
devkit/__pycache__/sensor_server.cpython-313.pyc
Normal file
BIN
devkit/__pycache__/sensor_server.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
devkit/__pycache__/sensor_stream_pb2.cpython-313.pyc
Normal file
BIN
devkit/__pycache__/sensor_stream_pb2.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
devkit/__pycache__/sensor_stream_pb2_grpc.cpython-313.pyc
Normal file
BIN
devkit/__pycache__/sensor_stream_pb2_grpc.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
('D:\\JE-Skin\\devkit\\dist\\je-skin-devkit-server.exe',
|
||||
('D:\\Workspace\\je-skin-customer-demo\\devkit\\dist\\je-skin-devkit-server.exe',
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
'C:\\Python314\\Lib\\site-packages\\PyInstaller\\bootloader\\images\\icon-console.ico',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\PyInstaller\\bootloader\\images\\icon-console.ico',
|
||||
None,
|
||||
False,
|
||||
False,
|
||||
@@ -29,335 +29,395 @@
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\je-skin-devkit-server.pkg',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\je-skin-devkit-server.pkg',
|
||||
[('pyi-contents-directory _internal', '', 'OPTION'),
|
||||
('PYZ-00.pyz',
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\PYZ-00.pyz',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\PYZ-00.pyz',
|
||||
'PYZ'),
|
||||
('struct',
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\localpycs\\struct.pyc',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\localpycs\\struct.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod01_archive',
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod01_archive.pyc',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod01_archive.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod02_importers',
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod02_importers.pyc',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod02_importers.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod03_ctypes',
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod03_ctypes.pyc',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod03_ctypes.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod04_pywin32',
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod04_pywin32.pyc',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod04_pywin32.pyc',
|
||||
'PYMODULE'),
|
||||
('pyiboot01_bootstrap',
|
||||
'C:\\Python314\\Lib\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_inspect',
|
||||
'C:\\Python314\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_pkgutil',
|
||||
'C:\\Python314\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_multiprocessing',
|
||||
'C:\\Python314\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_multiprocessing.py',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_multiprocessing.py',
|
||||
'PYSOURCE'),
|
||||
('sensor_server', 'D:\\JE-Skin\\devkit\\sensor_server.py', 'PYSOURCE'),
|
||||
('python314.dll', 'C:\\Python314\\python314.dll', 'BINARY'),
|
||||
('sensor_server',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\sensor_server.py',
|
||||
'PYSOURCE'),
|
||||
('python313.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\python313.dll',
|
||||
'BINARY'),
|
||||
('numpy.libs\\libscipy_openblas64_-63c857e738469261263c764a36be9436.dll',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy.libs\\libscipy_openblas64_-63c857e738469261263c764a36be9436.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy.libs\\libscipy_openblas64_-63c857e738469261263c764a36be9436.dll',
|
||||
'BINARY'),
|
||||
('numpy.libs\\msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy.libs\\msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy.libs\\msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll',
|
||||
'BINARY'),
|
||||
('select.pyd', 'C:\\Python314\\DLLs\\select.pyd', 'EXTENSION'),
|
||||
('select.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\select.pyd',
|
||||
'EXTENSION'),
|
||||
('_multiprocessing.pyd',
|
||||
'C:\\Python314\\DLLs\\_multiprocessing.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_multiprocessing.pyd',
|
||||
'EXTENSION'),
|
||||
('_zstd.pyd', 'C:\\Python314\\DLLs\\_zstd.pyd', 'EXTENSION'),
|
||||
('pyexpat.pyd', 'C:\\Python314\\DLLs\\pyexpat.pyd', 'EXTENSION'),
|
||||
('_lzma.pyd', 'C:\\Python314\\DLLs\\_lzma.pyd', 'EXTENSION'),
|
||||
('_bz2.pyd', 'C:\\Python314\\DLLs\\_bz2.pyd', 'EXTENSION'),
|
||||
('_ssl.pyd', 'C:\\Python314\\DLLs\\_ssl.pyd', 'EXTENSION'),
|
||||
('_hashlib.pyd', 'C:\\Python314\\DLLs\\_hashlib.pyd', 'EXTENSION'),
|
||||
('unicodedata.pyd', 'C:\\Python314\\DLLs\\unicodedata.pyd', 'EXTENSION'),
|
||||
('_decimal.pyd', 'C:\\Python314\\DLLs\\_decimal.pyd', 'EXTENSION'),
|
||||
('_socket.pyd', 'C:\\Python314\\DLLs\\_socket.pyd', 'EXTENSION'),
|
||||
('_ctypes.pyd', 'C:\\Python314\\DLLs\\_ctypes.pyd', 'EXTENSION'),
|
||||
('_queue.pyd', 'C:\\Python314\\DLLs\\_queue.pyd', 'EXTENSION'),
|
||||
('numpy\\_core\\_multiarray_tests.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\_core\\_multiarray_tests.cp314-win_amd64.pyd',
|
||||
('pyexpat.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\pyexpat.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\_core\\_multiarray_umath.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\_core\\_multiarray_umath.cp314-win_amd64.pyd',
|
||||
('_ssl.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_ssl.pyd',
|
||||
'EXTENSION'),
|
||||
('_wmi.pyd', 'C:\\Python314\\DLLs\\_wmi.pyd', 'EXTENSION'),
|
||||
('_overlapped.pyd', 'C:\\Python314\\DLLs\\_overlapped.pyd', 'EXTENSION'),
|
||||
('_asyncio.pyd', 'C:\\Python314\\DLLs\\_asyncio.pyd', 'EXTENSION'),
|
||||
('numpy\\linalg\\_umath_linalg.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\linalg\\_umath_linalg.cp314-win_amd64.pyd',
|
||||
('_hashlib.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_hashlib.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\mtrand.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\mtrand.cp314-win_amd64.pyd',
|
||||
('unicodedata.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\unicodedata.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\bit_generator.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\bit_generator.cp314-win_amd64.pyd',
|
||||
('_decimal.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_decimal.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_sfc64.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\_sfc64.cp314-win_amd64.pyd',
|
||||
('_socket.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_socket.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_philox.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\_philox.cp314-win_amd64.pyd',
|
||||
('_lzma.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_lzma.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_pcg64.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\_pcg64.cp314-win_amd64.pyd',
|
||||
('_bz2.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_bz2.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_mt19937.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\_mt19937.cp314-win_amd64.pyd',
|
||||
('_ctypes.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_ctypes.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_generator.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\_generator.cp314-win_amd64.pyd',
|
||||
('_queue.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_queue.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_common.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\_common.cp314-win_amd64.pyd',
|
||||
('numpy\\_core\\_multiarray_tests.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\_core\\_multiarray_tests.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_bounded_integers.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\_bounded_integers.cp314-win_amd64.pyd',
|
||||
('numpy\\_core\\_multiarray_umath.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\_core\\_multiarray_umath.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\fft\\_pocketfft_umath.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\fft\\_pocketfft_umath.cp314-win_amd64.pyd',
|
||||
('_overlapped.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_overlapped.pyd',
|
||||
'EXTENSION'),
|
||||
('_elementtree.pyd', 'C:\\Python314\\DLLs\\_elementtree.pyd', 'EXTENSION'),
|
||||
('grpc\\_cython\\cygrpc.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\grpc\\_cython\\cygrpc.cp314-win_amd64.pyd',
|
||||
('_asyncio.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_asyncio.pyd',
|
||||
'EXTENSION'),
|
||||
('_wmi.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_wmi.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\linalg\\_umath_linalg.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\linalg\\_umath_linalg.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\mtrand.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\mtrand.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\bit_generator.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\bit_generator.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_sfc64.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\_sfc64.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_philox.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\_philox.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_pcg64.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\_pcg64.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_mt19937.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\_mt19937.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_generator.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\_generator.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_common.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\_common.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_bounded_integers.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\_bounded_integers.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\fft\\_pocketfft_umath.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\fft\\_pocketfft_umath.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('_elementtree.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_elementtree.pyd',
|
||||
'EXTENSION'),
|
||||
('grpc\\_cython\\cygrpc.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\grpc\\_cython\\cygrpc.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('google\\_upb\\_message.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\google\\_upb\\_message.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\google\\_upb\\_message.pyd',
|
||||
'EXTENSION'),
|
||||
('grpc_tools\\_protoc_compiler.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\grpc_tools\\_protoc_compiler.cp314-win_amd64.pyd',
|
||||
('grpc_tools\\_protoc_compiler.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\grpc_tools\\_protoc_compiler.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('api-ms-win-crt-locale-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-locale-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-convert-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-convert-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-filesystem-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-filesystem-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-heap-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-heap-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('VCRUNTIME140.dll', 'C:\\Python314\\VCRUNTIME140.dll', 'BINARY'),
|
||||
('api-ms-win-crt-conio-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-conio-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-runtime-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-runtime-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-stdio-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-stdio-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-process-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-process-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-environment-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-environment-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-math-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-math-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-time-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-time-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-runtime-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-runtime-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-locale-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-locale-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-heap-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-heap-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('zlib.dll', 'C:\\Users\\Administrator\\miniconda3\\zlib.dll', 'BINARY'),
|
||||
('api-ms-win-crt-string-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-string-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-filesystem-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-filesystem-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-environment-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-environment-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-convert-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-convert-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-stdio-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-stdio-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-conio-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-conio-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('VCRUNTIME140.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\VCRUNTIME140.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-math-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-math-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-private-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-private-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-utility-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-utility-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('VCRUNTIME140_1.dll', 'C:\\Python314\\VCRUNTIME140_1.dll', 'BINARY'),
|
||||
('libssl-3.dll', 'C:\\Python314\\DLLs\\libssl-3.dll', 'BINARY'),
|
||||
('libcrypto-3.dll', 'C:\\Python314\\DLLs\\libcrypto-3.dll', 'BINARY'),
|
||||
('libffi-8.dll', 'C:\\Python314\\DLLs\\libffi-8.dll', 'BINARY'),
|
||||
('python3.dll', 'C:\\Python314\\python3.dll', 'BINARY'),
|
||||
('VCRUNTIME140_1.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\VCRUNTIME140_1.dll',
|
||||
'BINARY'),
|
||||
('libexpat.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Library\\bin\\libexpat.dll',
|
||||
'BINARY'),
|
||||
('libssl-3-x64.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Library\\bin\\libssl-3-x64.dll',
|
||||
'BINARY'),
|
||||
('libcrypto-3-x64.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Library\\bin\\libcrypto-3-x64.dll',
|
||||
'BINARY'),
|
||||
('libmpdec-4.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Library\\bin\\libmpdec-4.dll',
|
||||
'BINARY'),
|
||||
('liblzma.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Library\\bin\\liblzma.dll',
|
||||
'BINARY'),
|
||||
('LIBBZ2.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Library\\bin\\LIBBZ2.dll',
|
||||
'BINARY'),
|
||||
('ffi.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Library\\bin\\ffi.dll',
|
||||
'BINARY'),
|
||||
('python3.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\python3.dll',
|
||||
'BINARY'),
|
||||
('ucrtbase.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\ucrtbase.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processenvironment-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-processenvironment-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l1-2-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-file-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-timezone-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-timezone-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-libraryloader-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-libraryloader-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-util-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-util-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-file-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-debug-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-debug-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l2-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-file-l2-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-namedpipe-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-namedpipe-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-sysinfo-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-sysinfo-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-interlocked-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-interlocked-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-localization-l1-2-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-localization-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-synch-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-synch-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-profile-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-profile-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-fibers-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-fibers-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-handle-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-handle-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-synch-l1-2-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-synch-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-string-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-string-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-memory-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-memory-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-heap-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-heap-l1-1-0.dll',
|
||||
('api-ms-win-core-errorhandling-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-errorhandling-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-console-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-console-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processthreads-l1-1-1.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-processthreads-l1-1-1.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-debug-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-debug-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-string-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-string-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-profile-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-profile-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-localization-l1-2-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-localization-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-file-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-timezone-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-timezone-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processenvironment-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-processenvironment-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-sysinfo-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-sysinfo-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-rtlsupport-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-rtlsupport-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-datetime-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-datetime-l1-1-0.dll',
|
||||
('api-ms-win-core-heap-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-heap-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-errorhandling-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-errorhandling-l1-1-0.dll',
|
||||
('api-ms-win-core-memory-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-memory-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l1-2-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-file-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-interlocked-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-interlocked-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-util-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-util-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-libraryloader-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-libraryloader-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-handle-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-handle-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-synch-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-synch-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-fibers-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-fibers-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processthreads-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-processthreads-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processthreads-l1-1-1.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-processthreads-l1-1-1.dll',
|
||||
('api-ms-win-core-datetime-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-datetime-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('sensor_stream_pb2.py', 'D:\\JE-Skin\\devkit\\sensor_stream_pb2.py', 'DATA'),
|
||||
('api-ms-win-core-namedpipe-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-namedpipe-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l2-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-file-l2-1-0.dll',
|
||||
'BINARY'),
|
||||
('sensor_stream_pb2.py',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\sensor_stream_pb2.py',
|
||||
'DATA'),
|
||||
('sensor_stream_pb2_grpc.py',
|
||||
'D:\\JE-Skin\\devkit\\sensor_stream_pb2_grpc.py',
|
||||
'DATA'),
|
||||
('grpc\\_cython\\_credentials\\roots.pem',
|
||||
'C:\\Python314\\Lib\\site-packages\\grpc\\_cython\\_credentials\\roots.pem',
|
||||
'DATA'),
|
||||
('grpc\\_cython\\_cygrpc\\private_key_signing\\private_key_signer_py_wrapper.h',
|
||||
'C:\\Python314\\Lib\\site-packages\\grpc\\_cython\\_cygrpc\\private_key_signing\\private_key_signer_py_wrapper.h',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\sensor_stream_pb2_grpc.py',
|
||||
'DATA'),
|
||||
('grpc\\_cython\\_cygrpc\\private_key_signing\\private_key_signer_py_wrapper.cc',
|
||||
'C:\\Python314\\Lib\\site-packages\\grpc\\_cython\\_cygrpc\\private_key_signing\\private_key_signer_py_wrapper.cc',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\grpc\\_cython\\_cygrpc\\private_key_signing\\private_key_signer_py_wrapper.cc',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\WHEEL',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\WHEEL',
|
||||
('grpc\\_cython\\_cygrpc\\private_key_signing\\private_key_signer_py_wrapper.h',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\grpc\\_cython\\_cygrpc\\private_key_signing\\private_key_signer_py_wrapper.h',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\LICENSE.txt',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\LICENSE.txt',
|
||||
('grpc\\_cython\\_credentials\\roots.pem',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\grpc\\_cython\\_credentials\\roots.pem',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\sfc64\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\sfc64\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\WHEEL',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\WHEEL',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\entry_points.txt',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\entry_points.txt',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\distributions\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\distributions\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\ma\\LICENSE',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\ma\\LICENSE',
|
||||
('numpy-2.4.6.dist-info\\REQUESTED',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\REQUESTED',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\INSTALLER',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\INSTALLER',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\mt19937\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\mt19937\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\DELVEWHEEL',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\DELVEWHEEL',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\sfc64\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\sfc64\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\pcg64\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\pcg64\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\linalg\\lapack_lite\\LICENSE.txt',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\linalg\\lapack_lite\\LICENSE.txt',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\include\\numpy\\libdivide\\LICENSE.txt',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\include\\numpy\\libdivide\\LICENSE.txt',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\random\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\random\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\npysort\\x86-simd-sort\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\npysort\\x86-simd-sort\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\INSTALLER',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\INSTALLER',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\REQUESTED',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\REQUESTED',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\multiarray\\dragon4_LICENSE.txt',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\multiarray\\dragon4_LICENSE.txt',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\multiarray\\dragon4_LICENSE.txt',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\multiarray\\dragon4_LICENSE.txt',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\fft\\pocketfft\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\fft\\pocketfft\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\common\\pythoncapi-compat\\COPYING',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\common\\pythoncapi-compat\\COPYING',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\ma\\LICENSE',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\ma\\LICENSE',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\splitmix64\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\splitmix64\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\entry_points.txt',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\entry_points.txt',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\distributions\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\distributions\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\common\\pythoncapi-compat\\COPYING',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\common\\pythoncapi-compat\\COPYING',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\umath\\svml\\LICENSE',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\umath\\svml\\LICENSE',
|
||||
('numpy-2.4.6.dist-info\\licenses\\LICENSE.txt',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\LICENSE.txt',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\random\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\random\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\splitmix64\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\splitmix64\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\METADATA',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\METADATA',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\umath\\svml\\LICENSE',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\umath\\svml\\LICENSE',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\mt19937\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\mt19937\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\include\\numpy\\libdivide\\LICENSE.txt',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\include\\numpy\\libdivide\\LICENSE.txt',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\highway\\LICENSE',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\highway\\LICENSE',
|
||||
('numpy-2.4.6.dist-info\\DELVEWHEEL',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\DELVEWHEEL',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\philox\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\philox\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\npysort\\x86-simd-sort\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\npysort\\x86-simd-sort\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\fft\\pocketfft\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\fft\\pocketfft\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\METADATA',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\METADATA',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\linalg\\lapack_lite\\LICENSE.txt',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\linalg\\lapack_lite\\LICENSE.txt',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\philox\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\philox\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\RECORD',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\RECORD',
|
||||
('numpy-2.4.6.dist-info\\RECORD',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\RECORD',
|
||||
'DATA'),
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\highway\\LICENSE',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\highway\\LICENSE',
|
||||
'DATA'),
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\pcg64\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\pcg64\\LICENSE.md',
|
||||
'DATA'),
|
||||
('base_library.zip',
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\base_library.zip',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\base_library.zip',
|
||||
'DATA')],
|
||||
[],
|
||||
False,
|
||||
False,
|
||||
1777347409,
|
||||
1780277624,
|
||||
[('run.exe',
|
||||
'C:\\Python314\\Lib\\site-packages\\PyInstaller\\bootloader\\Windows-64bit-intel\\run.exe',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\PyInstaller\\bootloader\\Windows-64bit-intel\\run.exe',
|
||||
'EXECUTABLE')],
|
||||
'C:\\Python314\\python314.dll')
|
||||
'C:\\Users\\Administrator\\miniconda3\\python313.dll')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
('D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\je-skin-devkit-server.pkg',
|
||||
('D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\je-skin-devkit-server.pkg',
|
||||
{'BINARY': True,
|
||||
'DATA': True,
|
||||
'EXECUTABLE': True,
|
||||
@@ -10,328 +10,388 @@
|
||||
'SYMLINK': False},
|
||||
[('pyi-contents-directory _internal', '', 'OPTION'),
|
||||
('PYZ-00.pyz',
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\PYZ-00.pyz',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\PYZ-00.pyz',
|
||||
'PYZ'),
|
||||
('struct',
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\localpycs\\struct.pyc',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\localpycs\\struct.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod01_archive',
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod01_archive.pyc',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod01_archive.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod02_importers',
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod02_importers.pyc',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod02_importers.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod03_ctypes',
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod03_ctypes.pyc',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod03_ctypes.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod04_pywin32',
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod04_pywin32.pyc',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\localpycs\\pyimod04_pywin32.pyc',
|
||||
'PYMODULE'),
|
||||
('pyiboot01_bootstrap',
|
||||
'C:\\Python314\\Lib\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_inspect',
|
||||
'C:\\Python314\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_pkgutil',
|
||||
'C:\\Python314\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_multiprocessing',
|
||||
'C:\\Python314\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_multiprocessing.py',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_multiprocessing.py',
|
||||
'PYSOURCE'),
|
||||
('sensor_server', 'D:\\JE-Skin\\devkit\\sensor_server.py', 'PYSOURCE'),
|
||||
('python314.dll', 'C:\\Python314\\python314.dll', 'BINARY'),
|
||||
('sensor_server',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\sensor_server.py',
|
||||
'PYSOURCE'),
|
||||
('python313.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\python313.dll',
|
||||
'BINARY'),
|
||||
('numpy.libs\\libscipy_openblas64_-63c857e738469261263c764a36be9436.dll',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy.libs\\libscipy_openblas64_-63c857e738469261263c764a36be9436.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy.libs\\libscipy_openblas64_-63c857e738469261263c764a36be9436.dll',
|
||||
'BINARY'),
|
||||
('numpy.libs\\msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy.libs\\msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy.libs\\msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll',
|
||||
'BINARY'),
|
||||
('select.pyd', 'C:\\Python314\\DLLs\\select.pyd', 'EXTENSION'),
|
||||
('select.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\select.pyd',
|
||||
'EXTENSION'),
|
||||
('_multiprocessing.pyd',
|
||||
'C:\\Python314\\DLLs\\_multiprocessing.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_multiprocessing.pyd',
|
||||
'EXTENSION'),
|
||||
('_zstd.pyd', 'C:\\Python314\\DLLs\\_zstd.pyd', 'EXTENSION'),
|
||||
('pyexpat.pyd', 'C:\\Python314\\DLLs\\pyexpat.pyd', 'EXTENSION'),
|
||||
('_lzma.pyd', 'C:\\Python314\\DLLs\\_lzma.pyd', 'EXTENSION'),
|
||||
('_bz2.pyd', 'C:\\Python314\\DLLs\\_bz2.pyd', 'EXTENSION'),
|
||||
('_ssl.pyd', 'C:\\Python314\\DLLs\\_ssl.pyd', 'EXTENSION'),
|
||||
('_hashlib.pyd', 'C:\\Python314\\DLLs\\_hashlib.pyd', 'EXTENSION'),
|
||||
('unicodedata.pyd', 'C:\\Python314\\DLLs\\unicodedata.pyd', 'EXTENSION'),
|
||||
('_decimal.pyd', 'C:\\Python314\\DLLs\\_decimal.pyd', 'EXTENSION'),
|
||||
('_socket.pyd', 'C:\\Python314\\DLLs\\_socket.pyd', 'EXTENSION'),
|
||||
('_ctypes.pyd', 'C:\\Python314\\DLLs\\_ctypes.pyd', 'EXTENSION'),
|
||||
('_queue.pyd', 'C:\\Python314\\DLLs\\_queue.pyd', 'EXTENSION'),
|
||||
('numpy\\_core\\_multiarray_tests.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\_core\\_multiarray_tests.cp314-win_amd64.pyd',
|
||||
('pyexpat.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\pyexpat.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\_core\\_multiarray_umath.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\_core\\_multiarray_umath.cp314-win_amd64.pyd',
|
||||
('_ssl.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_ssl.pyd',
|
||||
'EXTENSION'),
|
||||
('_wmi.pyd', 'C:\\Python314\\DLLs\\_wmi.pyd', 'EXTENSION'),
|
||||
('_overlapped.pyd', 'C:\\Python314\\DLLs\\_overlapped.pyd', 'EXTENSION'),
|
||||
('_asyncio.pyd', 'C:\\Python314\\DLLs\\_asyncio.pyd', 'EXTENSION'),
|
||||
('numpy\\linalg\\_umath_linalg.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\linalg\\_umath_linalg.cp314-win_amd64.pyd',
|
||||
('_hashlib.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_hashlib.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\mtrand.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\mtrand.cp314-win_amd64.pyd',
|
||||
('unicodedata.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\unicodedata.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\bit_generator.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\bit_generator.cp314-win_amd64.pyd',
|
||||
('_decimal.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_decimal.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_sfc64.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\_sfc64.cp314-win_amd64.pyd',
|
||||
('_socket.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_socket.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_philox.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\_philox.cp314-win_amd64.pyd',
|
||||
('_lzma.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_lzma.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_pcg64.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\_pcg64.cp314-win_amd64.pyd',
|
||||
('_bz2.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_bz2.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_mt19937.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\_mt19937.cp314-win_amd64.pyd',
|
||||
('_ctypes.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_ctypes.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_generator.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\_generator.cp314-win_amd64.pyd',
|
||||
('_queue.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_queue.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_common.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\_common.cp314-win_amd64.pyd',
|
||||
('numpy\\_core\\_multiarray_tests.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\_core\\_multiarray_tests.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_bounded_integers.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\random\\_bounded_integers.cp314-win_amd64.pyd',
|
||||
('numpy\\_core\\_multiarray_umath.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\_core\\_multiarray_umath.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\fft\\_pocketfft_umath.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy\\fft\\_pocketfft_umath.cp314-win_amd64.pyd',
|
||||
('_overlapped.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_overlapped.pyd',
|
||||
'EXTENSION'),
|
||||
('_elementtree.pyd', 'C:\\Python314\\DLLs\\_elementtree.pyd', 'EXTENSION'),
|
||||
('grpc\\_cython\\cygrpc.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\grpc\\_cython\\cygrpc.cp314-win_amd64.pyd',
|
||||
('_asyncio.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_asyncio.pyd',
|
||||
'EXTENSION'),
|
||||
('_wmi.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_wmi.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\linalg\\_umath_linalg.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\linalg\\_umath_linalg.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\mtrand.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\mtrand.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\bit_generator.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\bit_generator.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_sfc64.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\_sfc64.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_philox.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\_philox.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_pcg64.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\_pcg64.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_mt19937.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\_mt19937.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_generator.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\_generator.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_common.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\_common.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\random\\_bounded_integers.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\random\\_bounded_integers.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('numpy\\fft\\_pocketfft_umath.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy\\fft\\_pocketfft_umath.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('_elementtree.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\DLLs\\_elementtree.pyd',
|
||||
'EXTENSION'),
|
||||
('grpc\\_cython\\cygrpc.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\grpc\\_cython\\cygrpc.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('google\\_upb\\_message.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\google\\_upb\\_message.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\google\\_upb\\_message.pyd',
|
||||
'EXTENSION'),
|
||||
('grpc_tools\\_protoc_compiler.cp314-win_amd64.pyd',
|
||||
'C:\\Python314\\Lib\\site-packages\\grpc_tools\\_protoc_compiler.cp314-win_amd64.pyd',
|
||||
('grpc_tools\\_protoc_compiler.cp313-win_amd64.pyd',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\grpc_tools\\_protoc_compiler.cp313-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('api-ms-win-crt-locale-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-locale-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-convert-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-convert-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-filesystem-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-filesystem-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-heap-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-heap-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('VCRUNTIME140.dll', 'C:\\Python314\\VCRUNTIME140.dll', 'BINARY'),
|
||||
('api-ms-win-crt-conio-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-conio-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-runtime-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-runtime-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-stdio-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-stdio-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-process-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-process-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-environment-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-environment-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-math-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-math-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-time-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-time-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-runtime-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-runtime-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-locale-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-locale-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-heap-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-heap-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('zlib.dll', 'C:\\Users\\Administrator\\miniconda3\\zlib.dll', 'BINARY'),
|
||||
('api-ms-win-crt-string-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-string-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-filesystem-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-filesystem-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-environment-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-environment-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-convert-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-convert-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-stdio-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-stdio-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-conio-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-conio-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('VCRUNTIME140.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\VCRUNTIME140.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-math-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-math-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-private-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-private-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-utility-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-crt-utility-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('VCRUNTIME140_1.dll', 'C:\\Python314\\VCRUNTIME140_1.dll', 'BINARY'),
|
||||
('libssl-3.dll', 'C:\\Python314\\DLLs\\libssl-3.dll', 'BINARY'),
|
||||
('libcrypto-3.dll', 'C:\\Python314\\DLLs\\libcrypto-3.dll', 'BINARY'),
|
||||
('libffi-8.dll', 'C:\\Python314\\DLLs\\libffi-8.dll', 'BINARY'),
|
||||
('python3.dll', 'C:\\Python314\\python3.dll', 'BINARY'),
|
||||
('VCRUNTIME140_1.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\VCRUNTIME140_1.dll',
|
||||
'BINARY'),
|
||||
('libexpat.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Library\\bin\\libexpat.dll',
|
||||
'BINARY'),
|
||||
('libssl-3-x64.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Library\\bin\\libssl-3-x64.dll',
|
||||
'BINARY'),
|
||||
('libcrypto-3-x64.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Library\\bin\\libcrypto-3-x64.dll',
|
||||
'BINARY'),
|
||||
('libmpdec-4.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Library\\bin\\libmpdec-4.dll',
|
||||
'BINARY'),
|
||||
('liblzma.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Library\\bin\\liblzma.dll',
|
||||
'BINARY'),
|
||||
('LIBBZ2.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Library\\bin\\LIBBZ2.dll',
|
||||
'BINARY'),
|
||||
('ffi.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Library\\bin\\ffi.dll',
|
||||
'BINARY'),
|
||||
('python3.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\python3.dll',
|
||||
'BINARY'),
|
||||
('ucrtbase.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\ucrtbase.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processenvironment-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-processenvironment-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l1-2-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-file-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-timezone-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-timezone-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-libraryloader-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-libraryloader-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-util-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-util-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-file-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-debug-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-debug-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l2-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-file-l2-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-namedpipe-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-namedpipe-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-sysinfo-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-sysinfo-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-interlocked-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-interlocked-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-localization-l1-2-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-localization-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-synch-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-synch-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-profile-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-profile-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-fibers-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-fibers-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-handle-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-handle-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-synch-l1-2-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-synch-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-string-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-string-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-memory-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-memory-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-heap-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-heap-l1-1-0.dll',
|
||||
('api-ms-win-core-errorhandling-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-errorhandling-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-console-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-console-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processthreads-l1-1-1.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-processthreads-l1-1-1.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-debug-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-debug-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-string-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-string-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-profile-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-profile-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-localization-l1-2-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-localization-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-file-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-timezone-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-timezone-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processenvironment-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-processenvironment-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-sysinfo-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-sysinfo-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-rtlsupport-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-rtlsupport-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-datetime-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-datetime-l1-1-0.dll',
|
||||
('api-ms-win-core-heap-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-heap-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-errorhandling-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-errorhandling-l1-1-0.dll',
|
||||
('api-ms-win-core-memory-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-memory-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l1-2-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-file-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-interlocked-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-interlocked-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-util-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-util-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-libraryloader-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-libraryloader-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-handle-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-handle-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-synch-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-synch-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-fibers-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-fibers-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processthreads-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-processthreads-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processthreads-l1-1-1.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-processthreads-l1-1-1.dll',
|
||||
('api-ms-win-core-datetime-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-datetime-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('sensor_stream_pb2.py', 'D:\\JE-Skin\\devkit\\sensor_stream_pb2.py', 'DATA'),
|
||||
('api-ms-win-core-namedpipe-l1-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-namedpipe-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l2-1-0.dll',
|
||||
'C:\\Users\\Administrator\\miniconda3\\api-ms-win-core-file-l2-1-0.dll',
|
||||
'BINARY'),
|
||||
('sensor_stream_pb2.py',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\sensor_stream_pb2.py',
|
||||
'DATA'),
|
||||
('sensor_stream_pb2_grpc.py',
|
||||
'D:\\JE-Skin\\devkit\\sensor_stream_pb2_grpc.py',
|
||||
'DATA'),
|
||||
('grpc\\_cython\\_credentials\\roots.pem',
|
||||
'C:\\Python314\\Lib\\site-packages\\grpc\\_cython\\_credentials\\roots.pem',
|
||||
'DATA'),
|
||||
('grpc\\_cython\\_cygrpc\\private_key_signing\\private_key_signer_py_wrapper.h',
|
||||
'C:\\Python314\\Lib\\site-packages\\grpc\\_cython\\_cygrpc\\private_key_signing\\private_key_signer_py_wrapper.h',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\sensor_stream_pb2_grpc.py',
|
||||
'DATA'),
|
||||
('grpc\\_cython\\_cygrpc\\private_key_signing\\private_key_signer_py_wrapper.cc',
|
||||
'C:\\Python314\\Lib\\site-packages\\grpc\\_cython\\_cygrpc\\private_key_signing\\private_key_signer_py_wrapper.cc',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\grpc\\_cython\\_cygrpc\\private_key_signing\\private_key_signer_py_wrapper.cc',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\WHEEL',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\WHEEL',
|
||||
('grpc\\_cython\\_cygrpc\\private_key_signing\\private_key_signer_py_wrapper.h',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\grpc\\_cython\\_cygrpc\\private_key_signing\\private_key_signer_py_wrapper.h',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\LICENSE.txt',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\LICENSE.txt',
|
||||
('grpc\\_cython\\_credentials\\roots.pem',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\grpc\\_cython\\_credentials\\roots.pem',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\sfc64\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\sfc64\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\WHEEL',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\WHEEL',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\entry_points.txt',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\entry_points.txt',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\distributions\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\distributions\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\ma\\LICENSE',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\ma\\LICENSE',
|
||||
('numpy-2.4.6.dist-info\\REQUESTED',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\REQUESTED',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\INSTALLER',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\INSTALLER',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\mt19937\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\mt19937\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\DELVEWHEEL',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\DELVEWHEEL',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\sfc64\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\sfc64\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\pcg64\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\pcg64\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\linalg\\lapack_lite\\LICENSE.txt',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\linalg\\lapack_lite\\LICENSE.txt',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\include\\numpy\\libdivide\\LICENSE.txt',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\include\\numpy\\libdivide\\LICENSE.txt',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\random\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\random\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\npysort\\x86-simd-sort\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\npysort\\x86-simd-sort\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\INSTALLER',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\INSTALLER',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\REQUESTED',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\REQUESTED',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\multiarray\\dragon4_LICENSE.txt',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\multiarray\\dragon4_LICENSE.txt',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\multiarray\\dragon4_LICENSE.txt',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\multiarray\\dragon4_LICENSE.txt',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\fft\\pocketfft\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\fft\\pocketfft\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\common\\pythoncapi-compat\\COPYING',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\common\\pythoncapi-compat\\COPYING',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\ma\\LICENSE',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\ma\\LICENSE',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\splitmix64\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\splitmix64\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\entry_points.txt',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\entry_points.txt',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\distributions\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\distributions\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\common\\pythoncapi-compat\\COPYING',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\common\\pythoncapi-compat\\COPYING',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\umath\\svml\\LICENSE',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\umath\\svml\\LICENSE',
|
||||
('numpy-2.4.6.dist-info\\licenses\\LICENSE.txt',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\LICENSE.txt',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\random\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\random\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\splitmix64\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\splitmix64\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\METADATA',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\METADATA',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\umath\\svml\\LICENSE',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\umath\\svml\\LICENSE',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\mt19937\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\mt19937\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\include\\numpy\\libdivide\\LICENSE.txt',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\include\\numpy\\libdivide\\LICENSE.txt',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\highway\\LICENSE',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\_core\\src\\highway\\LICENSE',
|
||||
('numpy-2.4.6.dist-info\\DELVEWHEEL',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\DELVEWHEEL',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\philox\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\random\\src\\philox\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\npysort\\x86-simd-sort\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\npysort\\x86-simd-sort\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\fft\\pocketfft\\LICENSE.md',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\fft\\pocketfft\\LICENSE.md',
|
||||
('numpy-2.4.6.dist-info\\METADATA',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\METADATA',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\licenses\\numpy\\linalg\\lapack_lite\\LICENSE.txt',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\licenses\\numpy\\linalg\\lapack_lite\\LICENSE.txt',
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\philox\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\philox\\LICENSE.md',
|
||||
'DATA'),
|
||||
('numpy-2.4.4.dist-info\\RECORD',
|
||||
'C:\\Python314\\Lib\\site-packages\\numpy-2.4.4.dist-info\\RECORD',
|
||||
('numpy-2.4.6.dist-info\\RECORD',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\RECORD',
|
||||
'DATA'),
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\highway\\LICENSE',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\_core\\src\\highway\\LICENSE',
|
||||
'DATA'),
|
||||
('numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\pcg64\\LICENSE.md',
|
||||
'C:\\Users\\Administrator\\miniconda3\\Lib\\site-packages\\numpy-2.4.6.dist-info\\licenses\\numpy\\random\\src\\pcg64\\LICENSE.md',
|
||||
'DATA'),
|
||||
('base_library.zip',
|
||||
'D:\\JE-Skin\\devkit\\build\\je-skin-devkit-server\\base_library.zip',
|
||||
'D:\\Workspace\\je-skin-customer-demo\\devkit\\build\\je-skin-devkit-server\\base_library.zip',
|
||||
'DATA')],
|
||||
'python314.dll',
|
||||
'python313.dll',
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,35 +14,36 @@ Types of import:
|
||||
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
|
||||
tracking down the missing module yourself. Thanks!
|
||||
|
||||
missing module named pwd - imported by posixpath (delayed, conditional, optional), subprocess (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (optional), netrc (delayed, optional), http.server (delayed, optional)
|
||||
missing module named grp - imported by subprocess (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (optional)
|
||||
missing module named 'collections.abc' - imported by typing (top-level), tracemalloc (top-level), traceback (top-level), _colorize (top-level), selectors (top-level), logging (top-level), http.client (top-level), importlib.resources.readers (top-level), inspect (top-level), multiprocessing.managers (top-level), typing_extensions (top-level), asyncio.base_events (top-level), asyncio.coroutines (top-level), grpc.aio._metadata (top-level), google.protobuf.internal.containers (top-level), google.protobuf.internal.well_known_types (top-level), numpy._typing._array_like (top-level), numpy._typing._nested_sequence (conditional), numpy._typing._shape (top-level), numpy._typing._dtype_like (top-level), numpy.lib._function_base_impl (top-level), _pyrepl.types (top-level), numpy.lib._npyio_impl (top-level), numpy.random._common (top-level), numpy.random._generator (top-level), numpy.random.bit_generator (top-level), numpy.random.mtrand (top-level), numpy.polynomial._polybase (top-level), xml.etree.ElementTree (top-level)
|
||||
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib._local (optional), subprocess (delayed, conditional, optional), http.server (delayed, optional), netrc (delayed, optional), getpass (delayed, optional)
|
||||
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib._local (optional), subprocess (delayed, conditional, optional)
|
||||
missing module named 'collections.abc' - imported by traceback (top-level), typing (top-level), inspect (top-level), logging (top-level), importlib.resources.readers (top-level), selectors (top-level), tracemalloc (top-level), http.client (top-level), typing_extensions (top-level), asyncio.base_events (top-level), asyncio.coroutines (top-level), grpc.aio._metadata (top-level), google.protobuf.internal.containers (top-level), google.protobuf.internal.well_known_types (top-level), numpy._typing._array_like (top-level), numpy._typing._nested_sequence (conditional), numpy._typing._shape (top-level), numpy._typing._dtype_like (top-level), numpy.lib._function_base_impl (top-level), numpy.lib._npyio_impl (top-level), numpy.random._common (top-level), numpy.random._generator (top-level), numpy.random.bit_generator (top-level), numpy.random.mtrand (top-level), numpy.polynomial._polybase (top-level), xml.etree.ElementTree (top-level)
|
||||
missing module named _posixsubprocess - imported by subprocess (conditional), multiprocessing.util (delayed)
|
||||
missing module named fcntl - imported by subprocess (optional), pathlib._os (optional)
|
||||
missing module named fcntl - imported by subprocess (optional)
|
||||
missing module named _posixshmem - imported by multiprocessing.resource_tracker (conditional), multiprocessing.shared_memory (conditional)
|
||||
missing module named _scproxy - imported by urllib.request (conditional)
|
||||
missing module named posix - imported by os (conditional, optional), posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional), pathlib._os (optional), _pyrepl.trace (conditional)
|
||||
missing module named resource - imported by posix (top-level)
|
||||
missing module named termios - imported by tty (top-level), _pyrepl.pager (delayed, optional), getpass (optional)
|
||||
missing module named multiprocessing.BufferTooShort - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
||||
missing module named multiprocessing.AuthenticationError - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
||||
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional), zipimport (top-level)
|
||||
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional), zipimport (top-level)
|
||||
missing module named multiprocessing.BufferTooShort - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
||||
missing module named multiprocessing.AuthenticationError - imported by multiprocessing (top-level), multiprocessing.forkserver (top-level), multiprocessing.connection (top-level)
|
||||
missing module named posix - imported by os (conditional, optional), posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional)
|
||||
missing module named resource - imported by posix (top-level)
|
||||
missing module named multiprocessing.get_context - imported by multiprocessing (top-level), multiprocessing.pool (top-level), multiprocessing.managers (top-level), multiprocessing.sharedctypes (top-level)
|
||||
missing module named multiprocessing.TimeoutError - imported by multiprocessing (top-level), multiprocessing.pool (top-level)
|
||||
missing module named multiprocessing.set_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
|
||||
missing module named multiprocessing.get_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
|
||||
missing module named pyimod02_importers - imported by C:\Python314\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed)
|
||||
missing module named pyimod02_importers - imported by C:\Users\Administrator\miniconda3\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed)
|
||||
missing module named _dummy_thread - imported by numpy._core.arrayprint (optional)
|
||||
missing module named asyncio.DefaultEventLoopPolicy - imported by asyncio (delayed, conditional), asyncio.events (delayed, conditional)
|
||||
missing module named annotationlib - imported by typing_extensions (conditional)
|
||||
missing module named 'numpy_distutils.cpuinfo' - imported by numpy.f2py.diagnose (delayed, conditional, optional)
|
||||
missing module named 'numpy_distutils.fcompiler' - imported by numpy.f2py.diagnose (delayed, conditional, optional)
|
||||
missing module named 'numpy_distutils.command' - imported by numpy.f2py.diagnose (delayed, conditional, optional)
|
||||
missing module named numpy_distutils - imported by numpy.f2py.diagnose (delayed, optional)
|
||||
missing module named charset_normalizer - imported by numpy.f2py.crackfortran (optional)
|
||||
missing module named vms_lib - imported by platform (delayed, optional)
|
||||
missing module named 'java.lang' - imported by platform (delayed, optional)
|
||||
missing module named java - imported by platform (delayed)
|
||||
missing module named psutil - imported by numpy.testing._private.utils (delayed, optional)
|
||||
missing module named termios - imported by tty (top-level), _pyrepl.pager (delayed, optional)
|
||||
missing module named readline - imported by cmd (delayed, conditional, optional), code (delayed, conditional, optional), pdb (delayed, conditional, optional), rlcompleter (optional)
|
||||
missing module named win32pdh - imported by numpy.testing._private.utils (delayed, conditional)
|
||||
missing module named _typeshed - imported by numpy.random.bit_generator (top-level)
|
||||
@@ -84,7 +85,7 @@ missing module named numpy._core.sinh - imported by numpy._core (conditional), n
|
||||
missing module named numpy._core.single - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.signedinteger - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.signbit - imported by numpy._core (delayed), numpy.testing._private.utils (delayed), numpy (conditional)
|
||||
missing module named numpy._core.sign - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.sign - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.short - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.rint - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.right_shift - imported by numpy._core (conditional), numpy (conditional)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
BIN
devkit/dist/je-skin-devkit-server.exe
vendored
BIN
devkit/dist/je-skin-devkit-server.exe
vendored
Binary file not shown.
@@ -5,8 +5,8 @@ a = Analysis(
|
||||
['sensor_server.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('sensor_stream_pb2.py', '.'), ('sensor_stream_pb2_grpc.py', '.')],
|
||||
hiddenimports=['grpc', 'openpyxl', 'numpy'],
|
||||
datas=[('sensor_stream_pb2*.py', '.')],
|
||||
hiddenimports=['grpc', 'openpyxl'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13sensor_stream.proto\x12\rsensor_stream\"\x85\x01\n\x0bSensorFrame\x12\x0b\n\x03seq\x18\x01 \x01(\x04\x12\x14\n\x0ctimestamp_ms\x18\x02 \x01(\x04\x12\x0c\n\x04rows\x18\x03 \x01(\r\x12\x0c\n\x04\x63ols\x18\x04 \x01(\r\x12\x0e\n\x06matrix\x18\x05 \x03(\r\x12\x17\n\x0fresultant_force\x18\x06 \x01(\x01\x12\x0e\n\x06\x64ts_ms\x18\x07 \x01(\r\"q\n\x10PztAngleResponse\x12\x0b\n\x03seq\x18\x01 \x01(\x04\x12\x14\n\x0ctimestamp_ms\x18\x02 \x01(\x04\x12\r\n\x05\x61ngle\x18\x03 \x01(\x02\x12\x0e\n\x06\x64ts_ms\x18\x04 \x01(\r\x12\n\n\x02ok\x18\x05 \x01(\x08\x12\x0f\n\x07message\x18\x06 \x01(\t\"8\n\x0eProcessRequest\x12\x10\n\x08\x63sv_path\x18\x01 \x01(\t\x12\x14\n\x0csave_as_xlsx\x18\x02 \x01(\x08\"\xa6\x01\n\x0fProcessResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\x13\n\x0boutput_path\x18\x02 \x01(\t\x12\x13\n\x0bgroups_used\x18\x03 \x01(\r\x12\x12\n\nmean_value\x18\x04 \x01(\x01\x12\x11\n\tthreshold\x18\x05 \x01(\x01\x12\x12\n\nrows_total\x18\x06 \x01(\r\x12\x11\n\trows_kept\x18\x07 \x01(\r\x12\x0f\n\x07message\x18\x08 \x01(\t2W\n\nSensorPush\x12I\n\x06Upload\x12\x1a.sensor_stream.SensorFrame\x1a\x1f.sensor_stream.PztAngleResponse(\x01\x30\x01\x32_\n\x0f\x45xportProcessor\x12L\n\x0bProcessFile\x12\x1d.sensor_stream.ProcessRequest\x1a\x1e.sensor_stream.ProcessResponseb\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13sensor_stream.proto\x12\rsensor_stream\"\x85\x01\n\x0bSensorFrame\x12\x0b\n\x03seq\x18\x01 \x01(\x04\x12\x14\n\x0ctimestamp_ms\x18\x02 \x01(\x04\x12\x0c\n\x04rows\x18\x03 \x01(\r\x12\x0c\n\x04\x63ols\x18\x04 \x01(\r\x12\x0e\n\x06matrix\x18\x05 \x03(\r\x12\x17\n\x0fresultant_force\x18\x06 \x01(\x01\x12\x0e\n\x06\x64ts_ms\x18\x07 \x01(\r\"\xf9\x01\n\x10PztAngleResponse\x12\x0b\n\x03seq\x18\x01 \x01(\x04\x12\x14\n\x0ctimestamp_ms\x18\x02 \x01(\x04\x12\r\n\x05\x61ngle\x18\x03 \x01(\x02\x12\x0e\n\x06\x64ts_ms\x18\x04 \x01(\r\x12\n\n\x02ok\x18\x05 \x01(\x08\x12\x0f\n\x07message\x18\x06 \x01(\t\x12\x11\n\tmagnitude\x18\x07 \x01(\x02\x12\r\n\x05state\x18\x08 \x01(\x05\x12\r\n\x05\x63op_x\x18\t \x01(\x02\x12\r\n\x05\x63op_y\x18\n \x01(\x02\x12\x0e\n\x06\x62\x61se_x\x18\x0b \x01(\x02\x12\x0e\n\x06\x62\x61se_y\x18\x0c \x01(\x02\x12\x13\n\x0btotal_press\x18\r \x01(\x02\x12\x11\n\tthreshold\x18\x0e \x01(\x02\"8\n\x0eProcessRequest\x12\x10\n\x08\x63sv_path\x18\x01 \x01(\t\x12\x14\n\x0csave_as_xlsx\x18\x02 \x01(\x08\"\xa6\x01\n\x0fProcessResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\x13\n\x0boutput_path\x18\x02 \x01(\t\x12\x13\n\x0bgroups_used\x18\x03 \x01(\r\x12\x12\n\nmean_value\x18\x04 \x01(\x01\x12\x11\n\tthreshold\x18\x05 \x01(\x01\x12\x12\n\nrows_total\x18\x06 \x01(\r\x12\x11\n\trows_kept\x18\x07 \x01(\r\x12\x0f\n\x07message\x18\x08 \x01(\t2W\n\nSensorPush\x12I\n\x06Upload\x12\x1a.sensor_stream.SensorFrame\x1a\x1f.sensor_stream.PztAngleResponse(\x01\x30\x01\x32_\n\x0f\x45xportProcessor\x12L\n\x0bProcessFile\x12\x1d.sensor_stream.ProcessRequest\x1a\x1e.sensor_stream.ProcessResponseb\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
@@ -33,14 +33,14 @@ if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_SENSORFRAME']._serialized_start=39
|
||||
_globals['_SENSORFRAME']._serialized_end=172
|
||||
_globals['_PZTANGLERESPONSE']._serialized_start=174
|
||||
_globals['_PZTANGLERESPONSE']._serialized_end=287
|
||||
_globals['_PROCESSREQUEST']._serialized_start=289
|
||||
_globals['_PROCESSREQUEST']._serialized_end=345
|
||||
_globals['_PROCESSRESPONSE']._serialized_start=348
|
||||
_globals['_PROCESSRESPONSE']._serialized_end=514
|
||||
_globals['_SENSORPUSH']._serialized_start=516
|
||||
_globals['_SENSORPUSH']._serialized_end=603
|
||||
_globals['_EXPORTPROCESSOR']._serialized_start=605
|
||||
_globals['_EXPORTPROCESSOR']._serialized_end=700
|
||||
_globals['_PZTANGLERESPONSE']._serialized_start=175
|
||||
_globals['_PZTANGLERESPONSE']._serialized_end=424
|
||||
_globals['_PROCESSREQUEST']._serialized_start=426
|
||||
_globals['_PROCESSREQUEST']._serialized_end=482
|
||||
_globals['_PROCESSRESPONSE']._serialized_start=485
|
||||
_globals['_PROCESSRESPONSE']._serialized_end=651
|
||||
_globals['_SENSORPUSH']._serialized_start=653
|
||||
_globals['_SENSORPUSH']._serialized_end=740
|
||||
_globals['_EXPORTPROCESSOR']._serialized_start=742
|
||||
_globals['_EXPORTPROCESSOR']._serialized_end=837
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
0
devkit/server-start.err.log
Normal file
0
devkit/server-start.err.log
Normal file
33231
devkit/server-start.out.log
Normal file
33231
devkit/server-start.out.log
Normal file
File diff suppressed because it is too large
Load Diff
32
docs/CHANGELOG.md
Normal file
32
docs/CHANGELOG.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 更新日志
|
||||
|
||||
## 2026-06-09 - 送样分支
|
||||
|
||||
分支:`sample-delivery`
|
||||
|
||||
提交:`160ff54 Prepare sample delivery UI`
|
||||
|
||||
### 显示与交互
|
||||
|
||||
- 合力曲线改为 Canvas 渲染,保留原有 HUD 风格。
|
||||
- 合力曲线 panel 宽度调整为与三维力 panel 一致。
|
||||
- 卸力后合力曲线继续接收并渲染后端 0 值数据,panel 延迟 5 秒消失。
|
||||
- 5 秒内重新施力会取消合力曲线的消失计时。
|
||||
- 三维力 panel 改为卸力后延迟 5 秒消失。
|
||||
- 三维力箭头仍然实时跟随后端数据,卸力后箭头立即消失,不冻结最后方向。
|
||||
|
||||
### 后端数据
|
||||
|
||||
|
||||
- 合力低于显示阈值时,后端不再清空 summary 数组,而是持续写入 `0.0`。
|
||||
- 压力矩阵在低于显示阈值时仍然清零。
|
||||
- 前端可以通过 summary 数组是否存在和最新值大小来判断释放状态。
|
||||
|
||||
### 显示范围
|
||||
|
||||
- 合力与压力数值显示增加最大显示值处理:超过 `25.6` 时显示为 `25.6+`。
|
||||
|
||||
### 验证
|
||||
|
||||
- `npm.cmd run check` 通过,0 errors,保留 13 个既有 warnings。
|
||||
- `cargo check` 通过。
|
||||
1
eskin-finger-sdk
Submodule
1
eskin-finger-sdk
Submodule
Submodule eskin-finger-sdk added at 705375085f
106
package-lock.json
generated
106
package-lock.json
generated
@@ -6,10 +6,10 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "JE-Skin",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/api": "^2.11.0",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-process": "^2.3.1",
|
||||
"@tauri-apps/plugin-updater": "^2.10.1",
|
||||
@@ -19,7 +19,7 @@
|
||||
"@sveltejs/adapter-static": "^3.0.6",
|
||||
"@sveltejs/kit": "^2.9.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@tauri-apps/cli": "^2.11.2",
|
||||
"@types/three": "^0.183.1",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
@@ -1032,9 +1032,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/api": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz",
|
||||
"integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==",
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.0.tgz",
|
||||
"integrity": "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -1042,9 +1042,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.1.tgz",
|
||||
"integrity": "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==",
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.2.tgz",
|
||||
"integrity": "sha512-bk3HemqvGRoy+5D/dVMUQHKMYLglD0jVnMm/0iGMH6ufZ+p8r14m6BpIixwij3PBvZdvORUp1YifTD8QxVZ1Nw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"bin": {
|
||||
@@ -1058,23 +1058,23 @@
|
||||
"url": "https://opencollective.com/tauri"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tauri-apps/cli-darwin-arm64": "2.10.1",
|
||||
"@tauri-apps/cli-darwin-x64": "2.10.1",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.10.1",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.10.1",
|
||||
"@tauri-apps/cli-linux-riscv64-gnu": "2.10.1",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.10.1",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.10.1",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.10.1",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.10.1",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.10.1"
|
||||
"@tauri-apps/cli-darwin-arm64": "2.11.2",
|
||||
"@tauri-apps/cli-darwin-x64": "2.11.2",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.11.2",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.11.2",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.11.2",
|
||||
"@tauri-apps/cli-linux-riscv64-gnu": "2.11.2",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.11.2",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.11.2",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.11.2",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.11.2",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.11.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz",
|
||||
"integrity": "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==",
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.2.tgz",
|
||||
"integrity": "sha512-+4UZzLt+eOAEQCwgd+TqKgyUJMrvx+BgdXLLaqJYmPqzP+nE6YZr/hY6CWLYGQb8jFn99jEkmC6uA3tNvamA1w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1089,9 +1089,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz",
|
||||
"integrity": "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==",
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.2.tgz",
|
||||
"integrity": "sha512-VjYYtZUPqDMLutSfJEyxFE3Bz+DPi7c8wC3imckgvciLDZLq4qwKJxBicg0BXGhXjJsl8vKWgWRFNMPELQ+Xyg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1106,9 +1106,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz",
|
||||
"integrity": "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==",
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.2.tgz",
|
||||
"integrity": "sha512-yMemD6f4i95AQriS8EazyOFzbE34yjnP16i3IOzpHGQvBoy2DjypFMFBq0NtPuITURv/cOGguRtHR5d79/9CSA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -1123,9 +1123,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz",
|
||||
"integrity": "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==",
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.2.tgz",
|
||||
"integrity": "sha512-cgI91D2wL8GSgoWwZXDqt+DwnuZCP2/bz03QAE4TrhgAKIsrB4hX26W/H1EONPUUNkqrsgeCD0wU6pcNjV/5kw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1143,9 +1143,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.1.tgz",
|
||||
"integrity": "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==",
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.2.tgz",
|
||||
"integrity": "sha512-X1rm0BERqAAggtYTESSgXrS3sz4Sb/OiPiz54UqISlXW+GkR3vNIGnsy/lejNmoXGVqri3Q53BCfQiclOIyRPw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1163,9 +1163,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.1.tgz",
|
||||
"integrity": "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==",
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.2.tgz",
|
||||
"integrity": "sha512-usbMLJbT3KtkOrBMDVeGYNM35aTHXx38SJSzTMSqqjeUIOQ+iVPjb2yAGNAE+KqmBbAx4FOFIyMeKXx2M/JKGQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -1183,9 +1183,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.1.tgz",
|
||||
"integrity": "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==",
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.2.tgz",
|
||||
"integrity": "sha512-Ru4gwJKPG0ctVGchRGpRup4Y4lW2SSfFnrbQcyHhCliKy4g8Qz97TrUgCur4CbWyAgKxvGh3SjrkA0LDYzDGiw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1203,9 +1203,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.1.tgz",
|
||||
"integrity": "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==",
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.2.tgz",
|
||||
"integrity": "sha512-eUm7T6clN1MMmNSRQ9gaWsQdyehQx2Gmn5hht/QUlqZQI/qcP2OJK5dnaxqwFzCr2HdsEo9ydxaqcS1oJzMvUw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1223,9 +1223,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.1.tgz",
|
||||
"integrity": "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==",
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.2.tgz",
|
||||
"integrity": "sha512-HeeZW80jU+gVTOEX4X/hC6NVSAdDVXajwP5fxIZ/3z9WvUC7qrudX2GMTilYq6Dg0e0sk0XgsAJD1hZ5wPBXUA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1240,9 +1240,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.1.tgz",
|
||||
"integrity": "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==",
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.2.tgz",
|
||||
"integrity": "sha512-YhjQNZcXfbkCLyazSv1nPnJ9iRFE1wm6kc51FDbU10/Dk09io+6PAGMLjkxnX2GdM0qMnDmTjstY8mTDVvtKeA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -1257,9 +1257,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.1.tgz",
|
||||
"integrity": "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg==",
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.2.tgz",
|
||||
"integrity": "sha512-d2JchlFIpZevZVReyqhQOekJmb1UH3rhZ5VX6sH3ty9ETE0TKQavpihvoScUXfKKpW6HZC0MrFGRU0ZtD+w3gA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/api": "^2.11.0",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-process": "^2.3.1",
|
||||
"@tauri-apps/plugin-updater": "^2.10.1",
|
||||
@@ -25,7 +25,7 @@
|
||||
"@sveltejs/adapter-static": "^3.0.6",
|
||||
"@sveltejs/kit": "^2.9.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@tauri-apps/cli": "^2.11.2",
|
||||
"@types/three": "^0.183.1",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[registries.kellnr]
|
||||
index = "sparse+http://crates.huangyanjie.com/api/v1/crates/"
|
||||
1345
src-tauri/Cargo.lock
generated
1345
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,8 @@ name = "tauri_demo_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["multi-dim"]
|
||||
debug = []
|
||||
devkit = ["dep:tonic", "dep:prost", "dep:prost-types", "dep:async-stream", "dep:dirs"]
|
||||
multi-dim = ["dep:ndarray"]
|
||||
|
||||
@@ -49,11 +50,10 @@ crc = "3.4.0"
|
||||
axum = { version = "0.8", features = ["ws"] }
|
||||
tower-http = { version = "0.6", features = ["cors"] }
|
||||
futures-util = "0.3"
|
||||
uuid = { version = "1.23", features = ["v4", "serde"] }
|
||||
uuid = { version = "1", features = ["v4", "serde"] }
|
||||
rand = "0.8"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||
ndarray = { version = "0.15", optional = true }
|
||||
eskin-finger-sdk = { version = "0.1.0", registry = "kellnr" }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-updater = "2"
|
||||
|
||||
5
src-tauri/gen/android/app/proguard-tauri.pro
Normal file
5
src-tauri/gen/android/app/proguard-tauri.pro
Normal file
@@ -0,0 +1,5 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
|
||||
-keep class com.lenn.tauri_serial.TauriActivity {
|
||||
public app.tauri.plugin.PluginManager getPluginManager();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"https://schema.tauri.app/config/2","productName":"JE-Skin","version":"0.4.0","identifier":"com.lenn.tauri-serial","app":{"windows":[{"label":"main","create":true,"url":"index.html","dragDropEnabled":true,"center":false,"width":1366.0,"height":860.0,"resizable":true,"maximizable":true,"minimizable":true,"closable":true,"title":"JE-Skin","fullscreen":false,"focus":true,"focusable":true,"transparent":false,"maximized":false,"visible":true,"decorations":false,"alwaysOnBottom":false,"alwaysOnTop":false,"visibleOnAllWorkspaces":false,"contentProtected":false,"skipTaskbar":false,"titleBarStyle":"Visible","hiddenTitle":false,"acceptFirstMouse":false,"shadow":true,"incognito":false,"zoomHotkeysEnabled":false,"browserExtensionsEnabled":false,"useHttpsScheme":false,"javascriptDisabled":false,"allowLinkPreview":true,"disableInputAccessoryView":false,"scrollBarStyle":"default"}],"security":{"freezePrototype":false,"dangerousDisableAssetCspModification":false,"assetProtocol":{"scope":[],"enable":false},"pattern":{"use":"brownfield"},"capabilities":[]},"macOSPrivateApi":false,"withGlobalTauri":false,"enableGTKAppId":false},"build":{"devUrl":"http://localhost:1420/","frontendDist":"../build","beforeDevCommand":"npm run dev","beforeBuildCommand":"npm run build","removeUnusedCommands":false,"additionalWatchFolders":[]},"bundle":{"active":true,"targets":"all","createUpdaterArtifacts":true,"icon":["icons/32x32.png","icons/128x128.png","icons/128x128@2x.png","icons/icon.icns","icons/icon.ico"],"resources":["resources/je-skin-devkit-server.exe"],"useLocalToolsDir":false,"windows":{"digestAlgorithm":null,"certificateThumbprint":null,"timestampUrl":null,"tsp":false,"webviewInstallMode":{"type":"downloadBootstrapper","silent":true},"allowDowngrades":true,"wix":null,"nsis":{"template":"nsis/installer.nsi","headerImage":null,"sidebarImage":null,"installerIcon":"icons/icon.ico","installMode":"both","languages":null,"customLanguageFiles":null,"displayLanguageSelector":false,"compression":"lzma","startMenuFolder":null,"installerHooks":null,"minimumWebview2Version":null},"signCommand":null},"linux":{"appimage":{"bundleMediaFramework":false,"files":{}},"deb":{"files":{}},"rpm":{"release":"1","epoch":0,"files":{}}},"macOS":{"files":{},"minimumSystemVersion":"10.13","hardenedRuntime":true,"dmg":{"windowSize":{"width":660,"height":400},"appPosition":{"x":180,"y":170},"applicationFolderPosition":{"x":480,"y":170}}},"iOS":{"minimumSystemVersion":"14.0"},"android":{"minSdkVersion":24,"autoIncrementVersionCode":false}},"plugins":{}}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* THIS FILE IS AUTO-GENERATED. DO NOT MODIFY!! */
|
||||
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.lenn.tauri_serial
|
||||
|
||||
import android.webkit.*
|
||||
|
||||
class Ipc(val webViewClient: RustWebViewClient) {
|
||||
@JavascriptInterface
|
||||
fun postMessage(message: String?) {
|
||||
message?.let {m ->
|
||||
// we're not using WebView::getUrl() here because it needs to be executed on the main thread
|
||||
// and it would slow down the Ipc
|
||||
// so instead we track the current URL on the webview client
|
||||
this.ipc(webViewClient.currentUrl, m)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("tauri_demo_lib")
|
||||
}
|
||||
}
|
||||
|
||||
private external fun ipc(url: String, message: String)
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/* THIS FILE IS AUTO-GENERATED. DO NOT MODIFY!! */
|
||||
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.lenn.tauri_serial
|
||||
|
||||
// taken from https://github.com/ionic-team/capacitor/blob/6658bca41e78239347e458175b14ca8bd5c1d6e8/android/capacitor/src/main/java/com/getcapacitor/Logger.java
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
|
||||
class Logger {
|
||||
companion object {
|
||||
private const val LOG_TAG_CORE = "Tauri"
|
||||
|
||||
fun tags(vararg subtags: String): String {
|
||||
return if (subtags.isNotEmpty()) {
|
||||
LOG_TAG_CORE + "/" + TextUtils.join("/", subtags)
|
||||
} else LOG_TAG_CORE
|
||||
}
|
||||
|
||||
fun verbose(message: String) {
|
||||
verbose(LOG_TAG_CORE, message)
|
||||
}
|
||||
|
||||
private fun verbose(tag: String, message: String) {
|
||||
if (!shouldLog()) {
|
||||
return
|
||||
}
|
||||
Log.v(tag, message)
|
||||
}
|
||||
|
||||
fun debug(message: String) {
|
||||
debug(LOG_TAG_CORE, message)
|
||||
}
|
||||
|
||||
fun debug(tag: String, message: String) {
|
||||
if (!shouldLog()) {
|
||||
return
|
||||
}
|
||||
Log.d(tag, message)
|
||||
}
|
||||
|
||||
fun info(message: String) {
|
||||
info(LOG_TAG_CORE, message)
|
||||
}
|
||||
|
||||
fun info(tag: String, message: String) {
|
||||
if (!shouldLog()) {
|
||||
return
|
||||
}
|
||||
Log.i(tag, message)
|
||||
}
|
||||
|
||||
fun warn(message: String) {
|
||||
warn(LOG_TAG_CORE, message)
|
||||
}
|
||||
|
||||
fun warn(tag: String, message: String) {
|
||||
if (!shouldLog()) {
|
||||
return
|
||||
}
|
||||
Log.w(tag, message)
|
||||
}
|
||||
|
||||
fun error(message: String) {
|
||||
error(LOG_TAG_CORE, message, null)
|
||||
}
|
||||
|
||||
fun error(message: String, e: Throwable?) {
|
||||
error(LOG_TAG_CORE, message, e)
|
||||
}
|
||||
|
||||
fun error(tag: String, message: String, e: Throwable?) {
|
||||
if (!shouldLog()) {
|
||||
return
|
||||
}
|
||||
Log.e(tag, message, e)
|
||||
}
|
||||
|
||||
private fun shouldLog(): Boolean {
|
||||
return BuildConfig.DEBUG
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/* THIS FILE IS AUTO-GENERATED. DO NOT MODIFY!! */
|
||||
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package com.lenn.tauri_serial
|
||||
|
||||
// taken from https://github.com/ionic-team/capacitor/blob/6658bca41e78239347e458175b14ca8bd5c1d6e8/android/capacitor/src/main/java/com/getcapacitor/PermissionHelper.java
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.app.ActivityCompat
|
||||
import java.util.ArrayList
|
||||
|
||||
object PermissionHelper {
|
||||
/**
|
||||
* Checks if a list of given permissions are all granted by the user
|
||||
*
|
||||
* @param permissions Permissions to check.
|
||||
* @return True if all permissions are granted, false if at least one is not.
|
||||
*/
|
||||
fun hasPermissions(context: Context?, permissions: Array<String>): Boolean {
|
||||
for (perm in permissions) {
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
context!!,
|
||||
perm
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given permission has been defined in the AndroidManifest.xml
|
||||
*
|
||||
* @param permission A permission to check.
|
||||
* @return True if the permission has been defined in the Manifest, false if not.
|
||||
*/
|
||||
fun hasDefinedPermission(context: Context, permission: String): Boolean {
|
||||
var hasPermission = false
|
||||
val requestedPermissions = getManifestPermissions(context)
|
||||
if (!requestedPermissions.isNullOrEmpty()) {
|
||||
val requestedPermissionsList = listOf(*requestedPermissions)
|
||||
val requestedPermissionsArrayList = ArrayList(requestedPermissionsList)
|
||||
if (requestedPermissionsArrayList.contains(permission)) {
|
||||
hasPermission = true
|
||||
}
|
||||
}
|
||||
return hasPermission
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether all of the given permissions have been defined in the AndroidManifest.xml
|
||||
* @param context the app context
|
||||
* @param permissions a list of permissions
|
||||
* @return true only if all permissions are defined in the AndroidManifest.xml
|
||||
*/
|
||||
fun hasDefinedPermissions(context: Context, permissions: Array<String>): Boolean {
|
||||
for (permission in permissions) {
|
||||
if (!hasDefinedPermission(context, permission)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the permissions defined in AndroidManifest.xml
|
||||
*
|
||||
* @return The permissions defined in AndroidManifest.xml
|
||||
*/
|
||||
private fun getManifestPermissions(context: Context): Array<String>? {
|
||||
var requestedPermissions: Array<String>? = null
|
||||
try {
|
||||
val pm = context.packageManager
|
||||
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
pm.getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong()))
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
pm.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS)
|
||||
}
|
||||
if (packageInfo != null) {
|
||||
requestedPermissions = packageInfo.requestedPermissions
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
return requestedPermissions
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of permissions, return a new list with the ones not present in AndroidManifest.xml
|
||||
*
|
||||
* @param neededPermissions The permissions needed.
|
||||
* @return The permissions not present in AndroidManifest.xml
|
||||
*/
|
||||
fun getUndefinedPermissions(context: Context, neededPermissions: Array<String?>): Array<String?> {
|
||||
val undefinedPermissions = ArrayList<String?>()
|
||||
val requestedPermissions = getManifestPermissions(context)
|
||||
if (!requestedPermissions.isNullOrEmpty()) {
|
||||
val requestedPermissionsList = listOf(*requestedPermissions)
|
||||
val requestedPermissionsArrayList = ArrayList(requestedPermissionsList)
|
||||
for (permission in neededPermissions) {
|
||||
if (!requestedPermissionsArrayList.contains(permission)) {
|
||||
undefinedPermissions.add(permission)
|
||||
}
|
||||
}
|
||||
var undefinedPermissionArray = arrayOfNulls<String>(undefinedPermissions.size)
|
||||
undefinedPermissionArray = undefinedPermissions.toArray(undefinedPermissionArray)
|
||||
return undefinedPermissionArray
|
||||
}
|
||||
return neededPermissions
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
/* THIS FILE IS AUTO-GENERATED. DO NOT MODIFY!! */
|
||||
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
@file:Suppress("ObsoleteSdkInt", "RedundantOverride", "QueryPermissionsNeeded", "SimpleDateFormat")
|
||||
|
||||
package com.lenn.tauri_serial
|
||||
|
||||
// taken from https://github.com/ionic-team/capacitor/blob/6658bca41e78239347e458175b14ca8bd5c1d6e8/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.view.View
|
||||
import android.webkit.*
|
||||
import android.widget.EditText
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.FileProvider
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class RustWebChromeClient(appActivity: WryActivity) : WebChromeClient() {
|
||||
private interface PermissionListener {
|
||||
fun onPermissionSelect(isGranted: Boolean?)
|
||||
}
|
||||
|
||||
private interface ActivityResultListener {
|
||||
fun onActivityResult(result: ActivityResult?)
|
||||
}
|
||||
|
||||
private val activity: WryActivity
|
||||
private var permissionLauncher: ActivityResultLauncher<Array<String>>
|
||||
private var activityLauncher: ActivityResultLauncher<Intent>
|
||||
private var permissionListener: PermissionListener? = null
|
||||
private var activityListener: ActivityResultListener? = null
|
||||
|
||||
init {
|
||||
activity = appActivity
|
||||
val permissionCallback =
|
||||
ActivityResultCallback { isGranted: Map<String, Boolean> ->
|
||||
if (permissionListener != null) {
|
||||
var granted = true
|
||||
for ((_, value) in isGranted) {
|
||||
if (!value) granted = false
|
||||
}
|
||||
permissionListener!!.onPermissionSelect(granted)
|
||||
}
|
||||
}
|
||||
permissionLauncher =
|
||||
activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions(), permissionCallback)
|
||||
activityLauncher = activity.registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (activityListener != null) {
|
||||
activityListener!!.onActivityResult(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render web content in `view`.
|
||||
*
|
||||
* Both this method and [.onHideCustomView] are required for
|
||||
* rendering web content in full screen.
|
||||
*
|
||||
* @see [](https://developer.android.com/reference/android/webkit/WebChromeClient.onShowCustomView
|
||||
) */
|
||||
override fun onShowCustomView(view: View, callback: CustomViewCallback) {
|
||||
callback.onCustomViewHidden()
|
||||
super.onShowCustomView(view, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render web content in the original Web View again.
|
||||
*
|
||||
* Do not remove this method--@see #onShowCustomView(View, CustomViewCallback).
|
||||
*/
|
||||
override fun onHideCustomView() {
|
||||
super.onHideCustomView()
|
||||
}
|
||||
|
||||
override fun onPermissionRequest(request: PermissionRequest) {
|
||||
val isRequestPermissionRequired = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
val permissionList: MutableList<String> = ArrayList()
|
||||
if (listOf(*request.resources).contains("android.webkit.resource.VIDEO_CAPTURE")) {
|
||||
permissionList.add(Manifest.permission.CAMERA)
|
||||
}
|
||||
if (listOf(*request.resources).contains("android.webkit.resource.AUDIO_CAPTURE")) {
|
||||
permissionList.add(Manifest.permission.MODIFY_AUDIO_SETTINGS)
|
||||
permissionList.add(Manifest.permission.RECORD_AUDIO)
|
||||
}
|
||||
if (permissionList.isNotEmpty() && isRequestPermissionRequired) {
|
||||
val permissions = permissionList.toTypedArray()
|
||||
permissionListener = object : PermissionListener {
|
||||
override fun onPermissionSelect(isGranted: Boolean?) {
|
||||
if (isGranted == true) {
|
||||
request.grant(request.resources)
|
||||
} else {
|
||||
request.deny()
|
||||
}
|
||||
}
|
||||
}
|
||||
permissionLauncher.launch(permissions)
|
||||
} else {
|
||||
request.grant(request.resources)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the browser alert modal
|
||||
* @param view
|
||||
* @param url
|
||||
* @param message
|
||||
* @param result
|
||||
* @return
|
||||
*/
|
||||
override fun onJsAlert(view: WebView, url: String, message: String, result: JsResult): Boolean {
|
||||
if (activity.isFinishing) {
|
||||
return true
|
||||
}
|
||||
val builder = AlertDialog.Builder(view.context)
|
||||
builder
|
||||
.setMessage(message)
|
||||
.setPositiveButton(
|
||||
"OK"
|
||||
) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
result.confirm()
|
||||
}
|
||||
.setOnCancelListener { dialog: DialogInterface ->
|
||||
dialog.dismiss()
|
||||
result.cancel()
|
||||
}
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the browser confirm modal
|
||||
* @param view
|
||||
* @param url
|
||||
* @param message
|
||||
* @param result
|
||||
* @return
|
||||
*/
|
||||
override fun onJsConfirm(view: WebView, url: String, message: String, result: JsResult): Boolean {
|
||||
if (activity.isFinishing) {
|
||||
return true
|
||||
}
|
||||
val builder = AlertDialog.Builder(view.context)
|
||||
builder
|
||||
.setMessage(message)
|
||||
.setPositiveButton(
|
||||
"OK"
|
||||
) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
result.confirm()
|
||||
}
|
||||
.setNegativeButton(
|
||||
"Cancel"
|
||||
) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
result.cancel()
|
||||
}
|
||||
.setOnCancelListener { dialog: DialogInterface ->
|
||||
dialog.dismiss()
|
||||
result.cancel()
|
||||
}
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the browser prompt modal
|
||||
* @param view
|
||||
* @param url
|
||||
* @param message
|
||||
* @param defaultValue
|
||||
* @param result
|
||||
* @return
|
||||
*/
|
||||
override fun onJsPrompt(
|
||||
view: WebView,
|
||||
url: String,
|
||||
message: String,
|
||||
defaultValue: String,
|
||||
result: JsPromptResult
|
||||
): Boolean {
|
||||
if (activity.isFinishing) {
|
||||
return true
|
||||
}
|
||||
val builder = AlertDialog.Builder(view.context)
|
||||
val input = EditText(view.context)
|
||||
builder
|
||||
.setMessage(message)
|
||||
.setView(input)
|
||||
.setPositiveButton(
|
||||
"OK"
|
||||
) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
val inputText1 = input.text.toString().trim { it <= ' ' }
|
||||
result.confirm(inputText1)
|
||||
}
|
||||
.setNegativeButton(
|
||||
"Cancel"
|
||||
) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
result.cancel()
|
||||
}
|
||||
.setOnCancelListener { dialog: DialogInterface ->
|
||||
dialog.dismiss()
|
||||
result.cancel()
|
||||
}
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the browser geolocation permission prompt
|
||||
* @param origin
|
||||
* @param callback
|
||||
*/
|
||||
override fun onGeolocationPermissionsShowPrompt(
|
||||
origin: String,
|
||||
callback: GeolocationPermissions.Callback
|
||||
) {
|
||||
super.onGeolocationPermissionsShowPrompt(origin, callback)
|
||||
Logger.debug("onGeolocationPermissionsShowPrompt: DOING IT HERE FOR ORIGIN: $origin")
|
||||
val geoPermissions =
|
||||
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
if (!PermissionHelper.hasPermissions(activity, geoPermissions)) {
|
||||
permissionListener = object : PermissionListener {
|
||||
override fun onPermissionSelect(isGranted: Boolean?) {
|
||||
if (isGranted == true) {
|
||||
callback.invoke(origin, true, false)
|
||||
} else {
|
||||
val coarsePermission =
|
||||
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
||||
PermissionHelper.hasPermissions(activity, coarsePermission)
|
||||
) {
|
||||
callback.invoke(origin, true, false)
|
||||
} else {
|
||||
callback.invoke(origin, false, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
permissionLauncher.launch(geoPermissions)
|
||||
} else {
|
||||
// permission is already granted
|
||||
callback.invoke(origin, true, false)
|
||||
Logger.debug("onGeolocationPermissionsShowPrompt: has required permission")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShowFileChooser(
|
||||
webView: WebView,
|
||||
filePathCallback: ValueCallback<Array<Uri?>?>,
|
||||
fileChooserParams: FileChooserParams
|
||||
): Boolean {
|
||||
val acceptTypes = listOf(*fileChooserParams.acceptTypes)
|
||||
val captureEnabled = fileChooserParams.isCaptureEnabled
|
||||
val capturePhoto = captureEnabled && acceptTypes.contains("image/*")
|
||||
val captureVideo = captureEnabled && acceptTypes.contains("video/*")
|
||||
if (capturePhoto || captureVideo) {
|
||||
if (isMediaCaptureSupported) {
|
||||
showMediaCaptureOrFilePicker(filePathCallback, fileChooserParams, captureVideo)
|
||||
} else {
|
||||
permissionListener = object : PermissionListener {
|
||||
override fun onPermissionSelect(isGranted: Boolean?) {
|
||||
if (isGranted == true) {
|
||||
showMediaCaptureOrFilePicker(filePathCallback, fileChooserParams, captureVideo)
|
||||
} else {
|
||||
Logger.warn(Logger.tags("FileChooser"), "Camera permission not granted")
|
||||
filePathCallback.onReceiveValue(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
val camPermission = arrayOf(Manifest.permission.CAMERA)
|
||||
permissionLauncher.launch(camPermission)
|
||||
}
|
||||
} else {
|
||||
showFilePicker(filePathCallback, fileChooserParams)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private val isMediaCaptureSupported: Boolean
|
||||
get() {
|
||||
val permissions = arrayOf(Manifest.permission.CAMERA)
|
||||
return PermissionHelper.hasPermissions(activity, permissions) ||
|
||||
!PermissionHelper.hasDefinedPermission(activity, Manifest.permission.CAMERA)
|
||||
}
|
||||
|
||||
private fun showMediaCaptureOrFilePicker(
|
||||
filePathCallback: ValueCallback<Array<Uri?>?>,
|
||||
fileChooserParams: FileChooserParams,
|
||||
isVideo: Boolean
|
||||
) {
|
||||
val isVideoCaptureSupported = true
|
||||
val shown = if (isVideo && isVideoCaptureSupported) {
|
||||
showVideoCapturePicker(filePathCallback)
|
||||
} else {
|
||||
showImageCapturePicker(filePathCallback)
|
||||
}
|
||||
if (!shown) {
|
||||
Logger.warn(
|
||||
Logger.tags("FileChooser"),
|
||||
"Media capture intent could not be launched. Falling back to default file picker."
|
||||
)
|
||||
showFilePicker(filePathCallback, fileChooserParams)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showImageCapturePicker(filePathCallback: ValueCallback<Array<Uri?>?>): Boolean {
|
||||
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
|
||||
if (takePictureIntent.resolveActivity(activity.packageManager) == null) {
|
||||
return false
|
||||
}
|
||||
val imageFileUri: Uri = try {
|
||||
createImageFileUri()
|
||||
} catch (ex: Exception) {
|
||||
Logger.error("Unable to create temporary media capture file: " + ex.message)
|
||||
return false
|
||||
}
|
||||
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri)
|
||||
activityListener = object : ActivityResultListener {
|
||||
override fun onActivityResult(result: ActivityResult?) {
|
||||
var res: Array<Uri?>? = null
|
||||
if (result?.resultCode == Activity.RESULT_OK) {
|
||||
res = arrayOf(imageFileUri)
|
||||
}
|
||||
filePathCallback.onReceiveValue(res)
|
||||
}
|
||||
}
|
||||
activityLauncher.launch(takePictureIntent)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun showVideoCapturePicker(filePathCallback: ValueCallback<Array<Uri?>?>): Boolean {
|
||||
val takeVideoIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
|
||||
if (takeVideoIntent.resolveActivity(activity.packageManager) == null) {
|
||||
return false
|
||||
}
|
||||
activityListener = object : ActivityResultListener {
|
||||
override fun onActivityResult(result: ActivityResult?) {
|
||||
var res: Array<Uri?>? = null
|
||||
if (result?.resultCode == Activity.RESULT_OK) {
|
||||
res = arrayOf(result.data!!.data)
|
||||
}
|
||||
filePathCallback.onReceiveValue(res)
|
||||
}
|
||||
}
|
||||
activityLauncher.launch(takeVideoIntent)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun showFilePicker(
|
||||
filePathCallback: ValueCallback<Array<Uri?>?>,
|
||||
fileChooserParams: FileChooserParams
|
||||
) {
|
||||
val intent = fileChooserParams.createIntent()
|
||||
if (fileChooserParams.mode == FileChooserParams.MODE_OPEN_MULTIPLE) {
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||
}
|
||||
if (fileChooserParams.acceptTypes.size > 1 || intent.type!!.startsWith(".")) {
|
||||
val validTypes = getValidTypes(fileChooserParams.acceptTypes)
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, validTypes)
|
||||
if (intent.type!!.startsWith(".")) {
|
||||
intent.type = validTypes[0]
|
||||
}
|
||||
}
|
||||
try {
|
||||
activityListener = object : ActivityResultListener {
|
||||
override fun onActivityResult(result: ActivityResult?) {
|
||||
val res: Array<Uri?>?
|
||||
val resultIntent = result?.data
|
||||
if (result?.resultCode == Activity.RESULT_OK && resultIntent!!.clipData != null) {
|
||||
val numFiles = resultIntent.clipData!!.itemCount
|
||||
res = arrayOfNulls(numFiles)
|
||||
for (i in 0 until numFiles) {
|
||||
res[i] = resultIntent.clipData!!.getItemAt(i).uri
|
||||
}
|
||||
} else {
|
||||
res = FileChooserParams.parseResult(
|
||||
result?.resultCode ?: 0,
|
||||
resultIntent
|
||||
)
|
||||
}
|
||||
filePathCallback.onReceiveValue(res)
|
||||
}
|
||||
}
|
||||
activityLauncher.launch(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
filePathCallback.onReceiveValue(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getValidTypes(currentTypes: Array<String>): Array<String> {
|
||||
val validTypes: MutableList<String> = ArrayList()
|
||||
val mtm = MimeTypeMap.getSingleton()
|
||||
for (mime in currentTypes) {
|
||||
if (mime.startsWith(".")) {
|
||||
val extension = mime.substring(1)
|
||||
val extensionMime = mtm.getMimeTypeFromExtension(extension)
|
||||
if (extensionMime != null && !validTypes.contains(extensionMime)) {
|
||||
validTypes.add(extensionMime)
|
||||
}
|
||||
} else if (!validTypes.contains(mime)) {
|
||||
validTypes.add(mime)
|
||||
}
|
||||
}
|
||||
val validObj: Array<Any> = validTypes.toTypedArray()
|
||||
return Arrays.copyOf(
|
||||
validObj, validObj.size,
|
||||
Array<String>::class.java
|
||||
)
|
||||
}
|
||||
|
||||
override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
|
||||
val tag: String = Logger.tags("Console")
|
||||
if (consoleMessage.message() != null && isValidMsg(consoleMessage.message())) {
|
||||
val msg = String.format(
|
||||
"File: %s - Line %d - Msg: %s",
|
||||
consoleMessage.sourceId(),
|
||||
consoleMessage.lineNumber(),
|
||||
consoleMessage.message()
|
||||
)
|
||||
val level = consoleMessage.messageLevel().name
|
||||
if ("ERROR".equals(level, ignoreCase = true)) {
|
||||
Logger.error(tag, msg, null)
|
||||
} else if ("WARNING".equals(level, ignoreCase = true)) {
|
||||
Logger.warn(tag, msg)
|
||||
} else if ("TIP".equals(level, ignoreCase = true)) {
|
||||
Logger.debug(tag, msg)
|
||||
} else {
|
||||
Logger.info(tag, msg)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isValidMsg(msg: String): Boolean {
|
||||
return !(msg.contains("%cresult %c") ||
|
||||
msg.contains("%cnative %c") ||
|
||||
msg.equals("[object Object]", ignoreCase = true) ||
|
||||
msg.equals("console.groupEnd", ignoreCase = true))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun createImageFileUri(): Uri {
|
||||
val photoFile = createImageFile(activity)
|
||||
return FileProvider.getUriForFile(
|
||||
activity,
|
||||
activity.packageName.toString() + ".fileprovider",
|
||||
photoFile
|
||||
)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun createImageFile(activity: Activity): File {
|
||||
// Create an image file name
|
||||
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
|
||||
val imageFileName = "JPEG_" + timeStamp + "_"
|
||||
val storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
|
||||
return File.createTempFile(imageFileName, ".jpg", storageDir)
|
||||
}
|
||||
|
||||
override fun onReceivedTitle(
|
||||
view: WebView,
|
||||
title: String
|
||||
) {
|
||||
handleReceivedTitle(view, title)
|
||||
}
|
||||
|
||||
private external fun handleReceivedTitle(webview: WebView, title: String)
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/* THIS FILE IS AUTO-GENERATED. DO NOT MODIFY!! */
|
||||
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
@file:Suppress("unused", "SetJavaScriptEnabled")
|
||||
|
||||
package com.lenn.tauri_serial
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.webkit.*
|
||||
import android.content.Context
|
||||
import androidx.webkit.WebViewCompat
|
||||
import androidx.webkit.WebViewFeature
|
||||
import kotlin.collections.Map
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
class RustWebView(context: Context, val initScripts: Array<String>, val id: String): WebView(context) {
|
||||
val isDocumentStartScriptEnabled: Boolean
|
||||
|
||||
init {
|
||||
settings.javaScriptEnabled = true
|
||||
settings.domStorageEnabled = true
|
||||
settings.setGeolocationEnabled(true)
|
||||
settings.databaseEnabled = true
|
||||
settings.mediaPlaybackRequiresUserGesture = false
|
||||
settings.javaScriptCanOpenWindowsAutomatically = true
|
||||
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
|
||||
isDocumentStartScriptEnabled = true
|
||||
for (script in initScripts) {
|
||||
WebViewCompat.addDocumentStartJavaScript(this, script, setOf("*"));
|
||||
}
|
||||
} else {
|
||||
isDocumentStartScriptEnabled = false
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
fun loadUrlMainThread(url: String) {
|
||||
post {
|
||||
loadUrl(url)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadUrlMainThread(url: String, additionalHttpHeaders: Map<String, String>) {
|
||||
post {
|
||||
loadUrl(url, additionalHttpHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadUrl(url: String) {
|
||||
if (!shouldOverride(url)) {
|
||||
super.loadUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadUrl(url: String, additionalHttpHeaders: Map<String, String>) {
|
||||
if (!shouldOverride(url)) {
|
||||
super.loadUrl(url, additionalHttpHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
fun loadHTMLMainThread(html: String) {
|
||||
post {
|
||||
super.loadData(html, "text/html", null)
|
||||
}
|
||||
}
|
||||
|
||||
fun evalScript(id: Int, script: String) {
|
||||
post {
|
||||
super.evaluateJavascript(script) { result ->
|
||||
onEval(id, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearAllBrowsingData() {
|
||||
try {
|
||||
super.getContext().deleteDatabase("webviewCache.db")
|
||||
super.getContext().deleteDatabase("webview.db")
|
||||
super.clearCache(true)
|
||||
super.clearHistory()
|
||||
super.clearFormData()
|
||||
} catch (ex: Exception) {
|
||||
Logger.error("Unable to create temporary media capture file: " + ex.message)
|
||||
}
|
||||
}
|
||||
|
||||
fun getCookies(url: String): String {
|
||||
val cookieManager = CookieManager.getInstance()
|
||||
return cookieManager.getCookie(url)
|
||||
}
|
||||
|
||||
private external fun shouldOverride(url: String): Boolean
|
||||
private external fun onEval(id: Int, result: String)
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/* THIS FILE IS AUTO-GENERATED. DO NOT MODIFY!! */
|
||||
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package com.lenn.tauri_serial
|
||||
|
||||
import android.net.Uri
|
||||
import android.webkit.*
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.webkit.WebViewAssetLoader
|
||||
|
||||
class RustWebViewClient(context: Context): WebViewClient() {
|
||||
private val interceptedState = mutableMapOf<String, Boolean>()
|
||||
var currentUrl: String = "about:blank"
|
||||
private var lastInterceptedUrl: Uri? = null
|
||||
private var pendingUrlRedirect: String? = null
|
||||
|
||||
private val assetLoader = WebViewAssetLoader.Builder()
|
||||
.setDomain(assetLoaderDomain())
|
||||
.addPathHandler("/", WebViewAssetLoader.AssetsPathHandler(context))
|
||||
.build()
|
||||
|
||||
override fun shouldInterceptRequest(
|
||||
view: WebView,
|
||||
request: WebResourceRequest
|
||||
): WebResourceResponse? {
|
||||
pendingUrlRedirect?.let {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
view.loadUrl(it)
|
||||
}
|
||||
pendingUrlRedirect = null
|
||||
return null
|
||||
}
|
||||
|
||||
lastInterceptedUrl = request.url
|
||||
return if (withAssetLoader()) {
|
||||
assetLoader.shouldInterceptRequest(request.url)
|
||||
} else {
|
||||
val rustWebview = view as RustWebView;
|
||||
val response = handleRequest(rustWebview.id, request, rustWebview.isDocumentStartScriptEnabled)
|
||||
interceptedState[request.url.toString()] = response != null
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldOverrideUrlLoading(
|
||||
view: WebView,
|
||||
request: WebResourceRequest
|
||||
): Boolean {
|
||||
return shouldOverride(request.url.toString())
|
||||
}
|
||||
|
||||
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
|
||||
currentUrl = url
|
||||
if (interceptedState[url] == false) {
|
||||
val webView = view as RustWebView
|
||||
for (script in webView.initScripts) {
|
||||
view.evaluateJavascript(script, null)
|
||||
}
|
||||
}
|
||||
return onPageLoading(url)
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
onPageLoaded(url)
|
||||
}
|
||||
|
||||
override fun onReceivedError(
|
||||
view: WebView,
|
||||
request: WebResourceRequest,
|
||||
error: WebResourceError
|
||||
) {
|
||||
// we get a net::ERR_CONNECTION_REFUSED when an external URL redirects to a custom protocol
|
||||
// e.g. oauth flow, because shouldInterceptRequest is not called on redirects
|
||||
// so we must force retry here with loadUrl() to get a chance of the custom protocol to kick in
|
||||
if (error.errorCode == ERROR_CONNECT && request.isForMainFrame && request.url != lastInterceptedUrl) {
|
||||
// prevent the default error page from showing
|
||||
view.stopLoading()
|
||||
// without this initial loadUrl the app is stuck
|
||||
view.loadUrl(request.url.toString())
|
||||
// ensure the URL is actually loaded - for some reason there's a race condition and we need to call loadUrl() again later
|
||||
pendingUrlRedirect = request.url.toString()
|
||||
} else {
|
||||
super.onReceivedError(view, request, error)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("tauri_demo_lib")
|
||||
}
|
||||
}
|
||||
|
||||
private external fun assetLoaderDomain(): String
|
||||
private external fun withAssetLoader(): Boolean
|
||||
private external fun handleRequest(webviewId: String, request: WebResourceRequest, isDocumentStartScriptEnabled: Boolean): WebResourceResponse?
|
||||
private external fun shouldOverride(url: String): Boolean
|
||||
private external fun onPageLoading(url: String)
|
||||
private external fun onPageLoaded(url: String)
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/* THIS FILE IS AUTO-GENERATED. DO NOT MODIFY!! */
|
||||
|
||||
package com.lenn.tauri_serial
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import app.tauri.plugin.PluginManager
|
||||
|
||||
abstract class TauriActivity : WryActivity() {
|
||||
var pluginManager: PluginManager = PluginManager(this)
|
||||
override val handleBackNavigation: Boolean = false
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
pluginManager.onNewIntent(intent)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
pluginManager.onResume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
pluginManager.onPause()
|
||||
}
|
||||
|
||||
override fun onRestart() {
|
||||
super.onRestart()
|
||||
pluginManager.onRestart()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
pluginManager.onStop()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
pluginManager.onDestroy()
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
pluginManager.onConfigurationChanged(newConfig)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/* THIS FILE IS AUTO-GENERATED. DO NOT MODIFY!! */
|
||||
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package com.lenn.tauri_serial
|
||||
|
||||
import com.lenn.tauri_serial.RustWebView
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.webkit.WebView
|
||||
import android.view.KeyEvent
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
abstract class WryActivity : AppCompatActivity() {
|
||||
private lateinit var mWebView: RustWebView
|
||||
open val handleBackNavigation: Boolean = true
|
||||
|
||||
open fun onWebViewCreate(webView: WebView) { }
|
||||
|
||||
fun setWebView(webView: RustWebView) {
|
||||
mWebView = webView
|
||||
|
||||
if (handleBackNavigation) {
|
||||
val callback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (this@WryActivity.mWebView.canGoBack()) {
|
||||
this@WryActivity.mWebView.goBack()
|
||||
} else {
|
||||
this.isEnabled = false
|
||||
this@WryActivity.onBackPressed()
|
||||
this.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
onBackPressedDispatcher.addCallback(this, callback)
|
||||
}
|
||||
|
||||
onWebViewCreate(webView)
|
||||
}
|
||||
|
||||
val version: String
|
||||
@SuppressLint("WebViewApiAvailability", "ObsoleteSdkInt")
|
||||
get() {
|
||||
// Check getCurrentWebViewPackage() directly if above Android 8
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return WebView.getCurrentWebViewPackage()?.versionName ?: ""
|
||||
}
|
||||
|
||||
// Otherwise manually check WebView versions
|
||||
var webViewPackage = "com.google.android.webview"
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
webViewPackage = "com.android.chrome"
|
||||
}
|
||||
try {
|
||||
@Suppress("DEPRECATION")
|
||||
val info = packageManager.getPackageInfo(webViewPackage, 0)
|
||||
return info.versionName.toString()
|
||||
} catch (ex: Exception) {
|
||||
Logger.warn("Unable to get package info for '$webViewPackage'$ex")
|
||||
}
|
||||
|
||||
try {
|
||||
@Suppress("DEPRECATION")
|
||||
val info = packageManager.getPackageInfo("com.android.webview", 0)
|
||||
return info.versionName.toString()
|
||||
} catch (ex: Exception) {
|
||||
Logger.warn("Unable to get package info for 'com.android.webview'$ex")
|
||||
}
|
||||
|
||||
// Could not detect any webview, return empty string
|
||||
return ""
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
create(this)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
start()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
resume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
pause()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
stop()
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
focus(hasFocus)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
save()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
destroy()
|
||||
onActivityDestroy()
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
super.onLowMemory()
|
||||
memory()
|
||||
}
|
||||
|
||||
fun getAppClass(name: String): Class<*> {
|
||||
return Class.forName(name)
|
||||
}
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("tauri_demo_lib")
|
||||
}
|
||||
}
|
||||
|
||||
private external fun create(activity: WryActivity)
|
||||
private external fun start()
|
||||
private external fun resume()
|
||||
private external fun pause()
|
||||
private external fun stop()
|
||||
private external fun save()
|
||||
private external fun destroy()
|
||||
private external fun onActivityDestroy()
|
||||
private external fun memory()
|
||||
private external fun focus(focus: Boolean)
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# THIS FILE IS AUTO-GENERATED. DO NOT MODIFY!!
|
||||
|
||||
# Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
-keep class com.lenn.tauri_serial.* {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
-keep class com.lenn.tauri_serial.WryActivity {
|
||||
public <init>(...);
|
||||
|
||||
void setWebView(com.lenn.tauri_serial.RustWebView);
|
||||
java.lang.Class getAppClass(...);
|
||||
java.lang.String getVersion();
|
||||
}
|
||||
|
||||
-keep class com.lenn.tauri_serial.Ipc {
|
||||
public <init>(...);
|
||||
|
||||
@android.webkit.JavascriptInterface public <methods>;
|
||||
}
|
||||
|
||||
-keep class com.lenn.tauri_serial.RustWebView {
|
||||
public <init>(...);
|
||||
|
||||
void loadUrlMainThread(...);
|
||||
void loadHTMLMainThread(...);
|
||||
void evalScript(...);
|
||||
}
|
||||
|
||||
-keep class com.lenn.tauri_serial.RustWebChromeClient,com.lenn.tauri_serial.RustWebViewClient {
|
||||
public <init>(...);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/home/lenn/Workspace/JE-Skin/src-tauri/target/aarch64-linux-android/release/libtauri_demo_lib.so
|
||||
@@ -0,0 +1 @@
|
||||
/home/lenn/Workspace/JE-Skin/src-tauri/target/armv7-linux-androideabi/release/libtauri_demo_lib.so
|
||||
@@ -0,0 +1 @@
|
||||
/home/lenn/Workspace/JE-Skin/src-tauri/target/i686-linux-android/release/libtauri_demo_lib.so
|
||||
@@ -0,0 +1 @@
|
||||
/home/lenn/Workspace/JE-Skin/src-tauri/target/x86_64-linux-android/release/libtauri_demo_lib.so
|
||||
6
src-tauri/gen/android/app/tauri.build.gradle.kts
Normal file
6
src-tauri/gen/android/app/tauri.build.gradle.kts
Normal file
@@ -0,0 +1,6 @@
|
||||
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
val implementation by configurations
|
||||
dependencies {
|
||||
implementation(project(":tauri-android"))
|
||||
implementation(project(":tauri-plugin-opener"))
|
||||
}
|
||||
3
src-tauri/gen/android/app/tauri.properties
Normal file
3
src-tauri/gen/android/app/tauri.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
tauri.android.versionName=0.4.0
|
||||
tauri.android.versionCode=4000
|
||||
5
src-tauri/gen/android/tauri.settings.gradle
Normal file
5
src-tauri/gen/android/tauri.settings.gradle
Normal file
@@ -0,0 +1,5 @@
|
||||
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
include ':tauri-android'
|
||||
project(':tauri-android').projectDir = new File("/home/lenn/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tauri-2.10.3/mobile/android")
|
||||
include ':tauri-plugin-opener'
|
||||
project(':tauri-plugin-opener').projectDir = new File("/home/lenn/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tauri-plugin-opener-2.5.3/android")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,14 @@ message PztAngleResponse {
|
||||
uint32 dts_ms = 4;
|
||||
bool ok = 5;
|
||||
string message = 6;
|
||||
float magnitude = 7;
|
||||
int32 state = 8;
|
||||
float cop_x = 9;
|
||||
float cop_y = 10;
|
||||
float base_x = 11;
|
||||
float base_y = 12;
|
||||
float total_press = 13;
|
||||
float threshold = 14;
|
||||
}
|
||||
|
||||
message ProcessRequest {
|
||||
|
||||
Binary file not shown.
@@ -1,217 +0,0 @@
|
||||
{
|
||||
"scaler_mean": [
|
||||
1748.7541486595198,
|
||||
1292.5704664084863,
|
||||
669.8700117864961,
|
||||
1617.8798712839798,
|
||||
2104.589811228976,
|
||||
3267.658809002638,
|
||||
3366.4000112252343,
|
||||
2660.981740285495,
|
||||
2656.615909898786,
|
||||
1747.1196048717518,
|
||||
3093.4178032216423,
|
||||
3107.599371386878,
|
||||
4138.929019101607,
|
||||
3778.3928270752654,
|
||||
3495.851920450506,
|
||||
3110.5580063983834,
|
||||
2310.8518456156107,
|
||||
2899.8918261585377,
|
||||
3286.6881442816784,
|
||||
3601.237076948981,
|
||||
2590.9553048586554,
|
||||
2555.2781425978933,
|
||||
2004.8764850049579,
|
||||
1333.8961665824775,
|
||||
2090.217507623805,
|
||||
0.363302046990876,
|
||||
0.2506597877765041,
|
||||
0.12741811820991292,
|
||||
0.32195020821212794,
|
||||
0.43317540002685884,
|
||||
0.7725988160553472,
|
||||
0.791227193907261,
|
||||
0.5957799875116326,
|
||||
0.5873844015441929,
|
||||
0.35855586659016336,
|
||||
0.7267512979672636,
|
||||
0.7214172326166498,
|
||||
1.0,
|
||||
0.9089476753706724,
|
||||
0.8226695360434777,
|
||||
0.7208819781157673,
|
||||
0.5152795489332506,
|
||||
0.6711736481838434,
|
||||
0.7782925265622518,
|
||||
0.8648282061576593,
|
||||
0.5787625095682526,
|
||||
0.5752349727514727,
|
||||
0.43456864805018935,
|
||||
0.27668525082454587,
|
||||
0.47414670304783574,
|
||||
4138.929019101607,
|
||||
64531.08183195824,
|
||||
175620.92531477427,
|
||||
22.847729696357412,
|
||||
14.671691561018095,
|
||||
0.07533558084489102,
|
||||
12446.865764906175,
|
||||
47945.287047950456,
|
||||
2.8973185436828195,
|
||||
10.774373017335268,
|
||||
3.472192991899253,
|
||||
-0.013941562889309035,
|
||||
0.09672681097411825,
|
||||
0.5067195499928454,
|
||||
0.755407246398865,
|
||||
0.03711810817384146,
|
||||
11.154421806888552,
|
||||
64500.8986854629
|
||||
],
|
||||
"scaler_scale": [
|
||||
1458.5456651154973,
|
||||
1319.8585484401115,
|
||||
798.8535944732339,
|
||||
1467.8233720347457,
|
||||
1637.8964913406842,
|
||||
1330.3349975112737,
|
||||
1391.430499849884,
|
||||
1444.166940848846,
|
||||
1630.948040054198,
|
||||
1406.2203759964518,
|
||||
1289.9699402243327,
|
||||
1442.0533616965101,
|
||||
1437.7214049715994,
|
||||
1393.522474091575,
|
||||
1468.6421185157626,
|
||||
1449.3479990930084,
|
||||
1293.2464048717598,
|
||||
1331.2560392843097,
|
||||
1326.1289536453178,
|
||||
1357.3405110533047,
|
||||
1452.4854193036483,
|
||||
1348.4425883366337,
|
||||
1318.1429721243371,
|
||||
1059.93845215709,
|
||||
1114.1647557935548,
|
||||
0.2395898634701691,
|
||||
0.21706962815914935,
|
||||
0.13523106483202163,
|
||||
0.23880331588910964,
|
||||
0.24830003478347082,
|
||||
0.1464527498295455,
|
||||
0.15391677914992113,
|
||||
0.18125664726966026,
|
||||
0.2326879002599809,
|
||||
0.23502163992653513,
|
||||
0.13026800431597335,
|
||||
0.15563022147466685,
|
||||
1.0,
|
||||
0.09922737602626737,
|
||||
0.18291931318098986,
|
||||
0.15401181704844932,
|
||||
0.2143892844194339,
|
||||
0.16856049162074294,
|
||||
0.15902500893917185,
|
||||
0.18285009098439925,
|
||||
0.17264751056304276,
|
||||
0.21090366624550771,
|
||||
0.16802111677577075,
|
||||
0.19264329284433157,
|
||||
0.19589977001187556,
|
||||
1437.7214049715994,
|
||||
32602.413979370118,
|
||||
95845.11969895993,
|
||||
3.426376344472427,
|
||||
3.408382770733738,
|
||||
0.033353666248921464,
|
||||
5505.629576226806,
|
||||
25703.01200969283,
|
||||
0.4599551450527747,
|
||||
2.978321440052941,
|
||||
0.3916581766443181,
|
||||
0.06096090153067211,
|
||||
0.07864618660494935,
|
||||
0.0344984508436715,
|
||||
0.17668176728315207,
|
||||
0.18905119470509504,
|
||||
5352.30503788098,
|
||||
32297.31796957845
|
||||
],
|
||||
"ridge_coef": [
|
||||
7.4424310127566695,
|
||||
13.345966730219576,
|
||||
2.351840055857306,
|
||||
6.088230738742203,
|
||||
-10.030964629299273,
|
||||
3.876136979406362,
|
||||
-11.251608537526174,
|
||||
16.84502390958064,
|
||||
-2.093552796584439,
|
||||
-5.784923711493545,
|
||||
-6.67830546424787,
|
||||
-4.654052249161928,
|
||||
6.038218458133514,
|
||||
9.82412450487401,
|
||||
-6.200667839175651,
|
||||
-0.3133364534713342,
|
||||
-8.75036029102127,
|
||||
12.785901861589027,
|
||||
-3.7296377182327123,
|
||||
6.546167384121816,
|
||||
-4.984129287282208,
|
||||
8.311396481777527,
|
||||
-0.6248790895663127,
|
||||
2.69008779623183,
|
||||
12.996047839696784,
|
||||
-2.2609944767610504,
|
||||
-5.131537716982507,
|
||||
0.3988922195665723,
|
||||
-5.197736884253156,
|
||||
4.556854888903703,
|
||||
-0.8642438099006351,
|
||||
6.327731485629085,
|
||||
-5.157281763422745,
|
||||
0.10691827520622764,
|
||||
4.656962972053113,
|
||||
3.2628870750114887,
|
||||
4.033159141354671,
|
||||
0.0,
|
||||
-2.9206404009765268,
|
||||
1.8683691849941264,
|
||||
2.408006875407745,
|
||||
7.250310827671452,
|
||||
-3.97015207422554,
|
||||
0.7316093212194048,
|
||||
-3.459346094204882,
|
||||
2.4407660203169255,
|
||||
-2.872982666400644,
|
||||
1.8797071977799857,
|
||||
-1.3374700235689694,
|
||||
-7.9533345474852295,
|
||||
6.038063637368508,
|
||||
1.615806581558555,
|
||||
95785.62883805836,
|
||||
0.12233606167692031,
|
||||
-0.1515900264871255,
|
||||
2.2023033069961873,
|
||||
8.776787743985668,
|
||||
-0.16714060634667535,
|
||||
-2.751671223554021,
|
||||
0.2511944267079865,
|
||||
6.13561607395193,
|
||||
2.85703108671782,
|
||||
-0.11255626089468472,
|
||||
-0.9017242341101542,
|
||||
-0.627291200283328,
|
||||
3.4664885582435883,
|
||||
0.02591345630626686,
|
||||
0.5530407299425606
|
||||
],
|
||||
"ridge_intercept": 175620.9253147744,
|
||||
"n_features": 68,
|
||||
"noise_threshold": 15.0,
|
||||
"contact_threshold": 20.0,
|
||||
"ema_alpha": 0.9
|
||||
}
|
||||
@@ -2,11 +2,26 @@
|
||||
//!
|
||||
//! 仅在 `devkit` feature 启用时编译。
|
||||
|
||||
use serde::Serialize;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use tauri::State;
|
||||
#[cfg(feature = "devkit")]
|
||||
use tauri::AppHandle;
|
||||
|
||||
use crate::devkit::{DevKitConfig, DevKitState, DevKitStatusSnapshot, ExportProcessResult};
|
||||
use crate::devkit::{
|
||||
proto::SensorFrame, DevKitConfig, DevKitState, DevKitStatusSnapshot, ExportProcessResult,
|
||||
};
|
||||
|
||||
static REPLAY_SEQ_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DevKitReplayFramePushResult {
|
||||
pub seq: u64,
|
||||
pub timestamp_ms: u64,
|
||||
pub dts_ms: u32,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn devkit_status(state: State<'_, DevKitState>) -> DevKitStatusSnapshot {
|
||||
@@ -51,3 +66,55 @@ pub async fn devkit_process_export(
|
||||
let use_xlsx = save_as_xlsx.unwrap_or(config.save_as_xlsx);
|
||||
state.process_export(&csv_path, use_xlsx).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn devkit_push_replay_frame(
|
||||
state: State<'_, DevKitState>,
|
||||
values: Vec<i32>,
|
||||
dts_ms: u32,
|
||||
seq: Option<u64>,
|
||||
) -> Result<DevKitReplayFramePushResult, String> {
|
||||
if values.len() != 84 {
|
||||
return Err(format!("InvalidReplayMatrixLength: {}", values.len()));
|
||||
}
|
||||
|
||||
if !state.running.load(Ordering::Relaxed) {
|
||||
return Err("NotRunning".to_string());
|
||||
}
|
||||
|
||||
let timestamp_ms = now_millis();
|
||||
let seq = seq.unwrap_or_else(|| build_replay_seq(timestamp_ms));
|
||||
let resultant_force = values.iter().copied().sum::<i32>().max(0) as f64;
|
||||
let matrix = values
|
||||
.into_iter()
|
||||
.map(|value| value.max(0) as u32)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
state.push_frame(SensorFrame {
|
||||
seq,
|
||||
timestamp_ms,
|
||||
rows: 12,
|
||||
cols: 7,
|
||||
matrix,
|
||||
resultant_force,
|
||||
dts_ms,
|
||||
});
|
||||
|
||||
Ok(DevKitReplayFramePushResult {
|
||||
seq,
|
||||
timestamp_ms,
|
||||
dts_ms,
|
||||
})
|
||||
}
|
||||
|
||||
fn now_millis() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| duration.as_millis() as u64)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn build_replay_seq(timestamp_ms: u64) -> u64 {
|
||||
let counter = REPLAY_SEQ_COUNTER.fetch_add(1, Ordering::Relaxed) % 1000;
|
||||
timestamp_ms.saturating_mul(1000).saturating_add(counter)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use crate::serial_core::codecs::tactile_a::{
|
||||
export_recording_csv, TactileACodec, TactileACsvImporter, TactileAHandler,
|
||||
export_recording_csv, TactileACodec, TactileACsvImporter, TactileADataPacket, TactileAHandler,
|
||||
};
|
||||
use crate::serial_core::error::SerialError;
|
||||
use crate::serial_core::model::HudSpatialForce;
|
||||
#[cfg(feature = "multi-dim")]
|
||||
use crate::serial_core::multi_dim_force::PztProcessor;
|
||||
use crate::serial_core::record::CsvImporter;
|
||||
use crate::serial_core::serial::{PollMode, TactileAPollRequester};
|
||||
use crate::serial_core::{serial, TactileARecording};
|
||||
@@ -44,6 +47,7 @@ pub struct SerialExportResponse {
|
||||
pub struct SerialImportFrame {
|
||||
pub data: Vec<i32>,
|
||||
pub dts_ms: u64,
|
||||
pub spatial_force: Option<HudSpatialForce>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -118,8 +122,7 @@ pub fn serial_enum() -> Result<Vec<String>, SerialError> {
|
||||
.into_iter()
|
||||
.filter_map(|p| {
|
||||
let name = p.port_name;
|
||||
#[cfg(unix)]
|
||||
if !name.contains("USB") {
|
||||
if !should_include_serial_port(&name) {
|
||||
return None;
|
||||
}
|
||||
Some(name)
|
||||
@@ -129,6 +132,21 @@ pub fn serial_enum() -> Result<Vec<String>, SerialError> {
|
||||
Ok(ports)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn should_include_serial_port(name: &str) -> bool {
|
||||
name.to_ascii_lowercase().contains("usb")
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
fn should_include_serial_port(name: &str) -> bool {
|
||||
name.contains("USB")
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn should_include_serial_port(_name: &str) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn serial_connect(
|
||||
app: AppHandle,
|
||||
@@ -181,7 +199,7 @@ pub async fn serial_connect(
|
||||
)
|
||||
.await
|
||||
{
|
||||
eprintln!("serial task exited with error: {error}");
|
||||
log::error!("serial task exited with error: {error}");
|
||||
}
|
||||
|
||||
let manager = task_app.state::<SerialConnectionState>();
|
||||
@@ -322,13 +340,7 @@ pub fn serial_import_csv(
|
||||
|
||||
let channel_count = packets.first().map(|item| item.data.len()).unwrap_or(0);
|
||||
let frame_count = packets.len();
|
||||
let frames = packets
|
||||
.into_iter()
|
||||
.map(|packet| SerialImportFrame {
|
||||
data: packet.data,
|
||||
dts_ms: packet.dts_ms,
|
||||
})
|
||||
.collect();
|
||||
let frames = build_import_frames(packets);
|
||||
|
||||
Ok(SerialImportResponse {
|
||||
file_name,
|
||||
@@ -353,6 +365,44 @@ pub fn serial_import_csv_from_path(file_path: String) -> Result<SerialImportResp
|
||||
serial_import_csv(file_name, csv_content)
|
||||
}
|
||||
|
||||
fn build_import_frames(packets: Vec<TactileADataPacket>) -> Vec<SerialImportFrame> {
|
||||
#[cfg(feature = "multi-dim")]
|
||||
let mut pzt_processor = PztProcessor::new();
|
||||
|
||||
packets
|
||||
.into_iter()
|
||||
.map(|packet| {
|
||||
#[cfg(feature = "multi-dim")]
|
||||
let spatial_force = replay_spatial_force(&mut pzt_processor, &packet.data);
|
||||
|
||||
#[cfg(not(feature = "multi-dim"))]
|
||||
let spatial_force = None;
|
||||
|
||||
SerialImportFrame {
|
||||
data: packet.data,
|
||||
dts_ms: packet.dts_ms,
|
||||
spatial_force,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(feature = "multi-dim")]
|
||||
fn replay_spatial_force(processor: &mut PztProcessor, values: &[i32]) -> Option<HudSpatialForce> {
|
||||
let pzt_values = values.iter().map(|value| *value as f32).collect::<Vec<f32>>();
|
||||
let analysis = processor.get_pzt_analysis(&pzt_values).ok()?;
|
||||
|
||||
if !PztProcessor::should_report(&analysis) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(HudSpatialForce {
|
||||
angle_deg: analysis.angle_deg,
|
||||
magnitude: analysis.magnitude,
|
||||
confidence: analysis.confidence,
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_record_for_export(
|
||||
state: &State<'_, SerialConnectionState>,
|
||||
) -> Result<SharedTactileRecording, SerialError> {
|
||||
|
||||
@@ -35,6 +35,6 @@ pub async fn win_close(
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
app.exit(0);
|
||||
Ok(())
|
||||
main_window(&app)?.close().map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,14 @@ struct DevKitPztAngleEvent {
|
||||
timestamp_ms: u64,
|
||||
dts_ms: u32,
|
||||
angle: f32,
|
||||
magnitude: f32,
|
||||
state: i32,
|
||||
cop_x: f32,
|
||||
cop_y: f32,
|
||||
base_x: f32,
|
||||
base_y: f32,
|
||||
total_press: f32,
|
||||
threshold: f32,
|
||||
}
|
||||
|
||||
// ── DevKit 配置 ────────────────────────────────────────────────────
|
||||
@@ -276,12 +284,28 @@ async fn run_grpc_upload(
|
||||
timestamp_ms: message.timestamp_ms,
|
||||
dts_ms: message.dts_ms,
|
||||
angle: message.angle,
|
||||
magnitude: message.magnitude,
|
||||
state: message.state,
|
||||
cop_x: message.cop_x,
|
||||
cop_y: message.cop_y,
|
||||
base_x: message.base_x,
|
||||
base_y: message.base_y,
|
||||
total_press: message.total_press,
|
||||
threshold: message.threshold,
|
||||
};
|
||||
::log::debug!(
|
||||
"python pzt angle: seq={} dts_ms={} angle={:.2}",
|
||||
"python pzt angle: seq={} dts_ms={} angle={:.2} magnitude={:.2} state={} cop=({:.2},{:.2}) base=({:.2},{:.2}) total_press={:.2} threshold={:.2}",
|
||||
message.seq,
|
||||
message.dts_ms,
|
||||
message.angle
|
||||
message.angle,
|
||||
message.magnitude,
|
||||
message.state,
|
||||
message.cop_x,
|
||||
message.cop_y,
|
||||
message.base_x,
|
||||
message.base_y,
|
||||
message.total_press,
|
||||
message.threshold
|
||||
);
|
||||
app.emit("devkit_pzt_angle", payload)?;
|
||||
} else {
|
||||
|
||||
@@ -154,7 +154,8 @@ pub fn run() {
|
||||
commands::devkit::devkit_stop,
|
||||
commands::devkit::devkit_get_config,
|
||||
commands::devkit::devkit_set_config,
|
||||
commands::devkit::devkit_process_export
|
||||
commands::devkit::devkit_process_export,
|
||||
commands::devkit::devkit_push_replay_frame
|
||||
]);
|
||||
|
||||
#[cfg(not(feature = "devkit"))]
|
||||
|
||||
@@ -3,7 +3,7 @@ use fern::{
|
||||
Dispatch,
|
||||
};
|
||||
use log::debug;
|
||||
use std::{path::{Path, PathBuf}, time::SystemTime};
|
||||
use std::{path::PathBuf, time::SystemTime};
|
||||
|
||||
fn log_directory() -> PathBuf {
|
||||
let base_dir = std::env::var_os("LOCALAPPDATA")
|
||||
@@ -67,6 +67,7 @@ pub fn setup_logger() {
|
||||
|
||||
Dispatch::new()
|
||||
.level(log::LevelFilter::Debug)
|
||||
.level_for("h2", log::LevelFilter::Info)
|
||||
.chain(console_config)
|
||||
.chain(file_config)
|
||||
.apply()
|
||||
|
||||
@@ -1,397 +0,0 @@
|
||||
//! 7×12 柔性压力点阵力估计 - Rust 实现
|
||||
//!
|
||||
//! 与 Python `basin_feature_extractor.py` 完全对齐。
|
||||
//! 内嵌 `model_params.json`,对每帧 7×12 传感器数据提取 68 维特征并用
|
||||
//! StandardScaler + Ridge 回归估计法向力 Fz。
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
// ───────────────── 常量 ─────────────────
|
||||
|
||||
const ROWS: usize = 7;
|
||||
const COLS: usize = 12;
|
||||
const ROI_RADIUS: usize = 2;
|
||||
const ROI_SIZE: usize = 2 * ROI_RADIUS + 1; // 5
|
||||
const N_FEATURES: usize = 68; // 25 + 25 + 18
|
||||
|
||||
// ───────────────── 模型参数 JSON(编译时嵌入)─────────────────
|
||||
|
||||
const MODEL_PARAMS_JSON: &str = include_str!("../../resources/model_params.json");
|
||||
|
||||
// ───────────────── 模型参数反序列化 ─────────────────
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ModelParams {
|
||||
scaler_mean: Vec<f64>,
|
||||
scaler_scale: Vec<f64>,
|
||||
ridge_coef: Vec<f64>,
|
||||
ridge_intercept: f64,
|
||||
n_features: usize,
|
||||
noise_threshold: f64,
|
||||
contact_threshold: f64,
|
||||
ema_alpha: f64,
|
||||
}
|
||||
|
||||
// ───────────────── 估算器 ─────────────────
|
||||
|
||||
pub struct BasinForceEstimator {
|
||||
// 模型参数
|
||||
scaler_mean: [f64; N_FEATURES],
|
||||
scaler_scale: [f64; N_FEATURES],
|
||||
ridge_coef: [f64; N_FEATURES],
|
||||
ridge_intercept: f64,
|
||||
// 超参数
|
||||
noise_threshold: f64,
|
||||
contact_threshold: f64,
|
||||
ema_alpha: f64,
|
||||
// 时序状态(需要可变)
|
||||
prev_roi_sum: f64,
|
||||
ema_sum: f64,
|
||||
first_frame: bool,
|
||||
}
|
||||
|
||||
impl BasinForceEstimator {
|
||||
/// 使用编译时内嵌的 model_params.json 创建估算器
|
||||
pub fn new() -> Self {
|
||||
Self::from_json_str(MODEL_PARAMS_JSON)
|
||||
.expect("内嵌 model_params.json 加载失败")
|
||||
}
|
||||
|
||||
pub fn from_json_str(json: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let p: ModelParams = serde_json::from_str(json)?;
|
||||
if p.n_features != N_FEATURES {
|
||||
return Err(format!(
|
||||
"模型特征维度不匹配: 期望 {}, 实际 {}",
|
||||
N_FEATURES, p.n_features
|
||||
)
|
||||
.into());
|
||||
}
|
||||
let mut scaler_mean = [0.0; N_FEATURES];
|
||||
let mut scaler_scale = [0.0; N_FEATURES];
|
||||
let mut ridge_coef = [0.0; N_FEATURES];
|
||||
scaler_mean.copy_from_slice(&p.scaler_mean);
|
||||
scaler_scale.copy_from_slice(&p.scaler_scale);
|
||||
ridge_coef.copy_from_slice(&p.ridge_coef);
|
||||
|
||||
Ok(Self {
|
||||
scaler_mean,
|
||||
scaler_scale,
|
||||
ridge_coef,
|
||||
ridge_intercept: p.ridge_intercept,
|
||||
noise_threshold: p.noise_threshold,
|
||||
contact_threshold: p.contact_threshold,
|
||||
ema_alpha: p.ema_alpha,
|
||||
prev_roi_sum: 0.0,
|
||||
ema_sum: 0.0,
|
||||
first_frame: true,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.prev_roi_sum = 0.0;
|
||||
self.ema_sum = 0.0;
|
||||
self.first_frame = true;
|
||||
}
|
||||
|
||||
pub fn predict_frame(&mut self, frame: &[f64; 84]) -> f64 {
|
||||
let features = self.extract_features(frame);
|
||||
self.ridge_predict(&features)
|
||||
}
|
||||
|
||||
// ───────────── 特征提取 ─────────────
|
||||
|
||||
fn extract_features(&mut self, raw: &[f64; 84]) -> [f64; N_FEATURES] {
|
||||
let mut x = [[0.0f64; COLS]; ROWS];
|
||||
let mut max_value = 0.0f64;
|
||||
for r in 0..ROWS {
|
||||
for c in 0..COLS {
|
||||
let v = raw[r * COLS + c].max(0.0);
|
||||
x[r][c] = v;
|
||||
if v > max_value {
|
||||
max_value = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if max_value < self.contact_threshold {
|
||||
self.update_temporal(0.0);
|
||||
return [0.0; N_FEATURES];
|
||||
}
|
||||
|
||||
let mut peak_row = 0usize;
|
||||
let mut peak_col = 0usize;
|
||||
for r in 0..ROWS {
|
||||
for c in 0..COLS {
|
||||
if x[r][c] >= x[peak_row][peak_col] {
|
||||
peak_row = r;
|
||||
peak_col = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let roi = self.extract_roi(&x, peak_row, peak_col);
|
||||
self.compute_features(&x, &roi, max_value, peak_row, peak_col)
|
||||
}
|
||||
|
||||
fn extract_roi(
|
||||
&self,
|
||||
x: &[[f64; COLS]; ROWS],
|
||||
pr: usize,
|
||||
pc: usize,
|
||||
) -> [[f64; ROI_SIZE]; ROI_SIZE] {
|
||||
let r = ROI_RADIUS as isize;
|
||||
let mut roi = [[0.0f64; ROI_SIZE]; ROI_SIZE];
|
||||
|
||||
let r_start = (pr as isize - r).max(0) as usize;
|
||||
let r_end = (pr + ROI_RADIUS + 1).min(ROWS);
|
||||
let c_start = (pc as isize - r).max(0) as usize;
|
||||
let c_end = (pc + ROI_RADIUS + 1).min(COLS);
|
||||
|
||||
let roi_r_start = (r_start as isize - (pr as isize - r)).max(0) as usize;
|
||||
let roi_c_start = (c_start as isize - (pc as isize - r)).max(0) as usize;
|
||||
|
||||
for (i, ri) in (r_start..r_end).enumerate() {
|
||||
for (j, ci) in (c_start..c_end).enumerate() {
|
||||
roi[roi_r_start + i][roi_c_start + j] = x[ri][ci];
|
||||
}
|
||||
}
|
||||
roi
|
||||
}
|
||||
|
||||
fn compute_features(
|
||||
&mut self,
|
||||
x: &[[f64; COLS]; ROWS],
|
||||
roi: &[[f64; ROI_SIZE]; ROI_SIZE],
|
||||
max_value: f64,
|
||||
peak_row: usize,
|
||||
peak_col: usize,
|
||||
) -> [f64; N_FEATURES] {
|
||||
let center = ROI_RADIUS;
|
||||
let mut feat = [0.0f64; N_FEATURES];
|
||||
let mut idx = 0;
|
||||
|
||||
// ROI 原始值 (25维)
|
||||
for r in 0..ROI_SIZE {
|
||||
for c in 0..ROI_SIZE {
|
||||
feat[idx] = roi[r][c];
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ROI 归一化形状 (25维)
|
||||
for r in 0..ROI_SIZE {
|
||||
for c in 0..ROI_SIZE {
|
||||
feat[idx] = if max_value > 0.0 {
|
||||
roi[r][c] / max_value
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// roi_sum, global_sum
|
||||
let mut roi_sum = 0.0f64;
|
||||
for r in 0..ROI_SIZE {
|
||||
for c in 0..ROI_SIZE {
|
||||
roi_sum += roi[r][c];
|
||||
}
|
||||
}
|
||||
let mut global_sum = 0.0f64;
|
||||
for r in 0..ROWS {
|
||||
for c in 0..COLS {
|
||||
global_sum += x[r][c];
|
||||
}
|
||||
}
|
||||
|
||||
// active_area
|
||||
let thr = self.noise_threshold.max(0.05 * max_value);
|
||||
let mut active_area = 0.0f64;
|
||||
for r in 0..ROI_SIZE {
|
||||
for c in 0..ROI_SIZE {
|
||||
if roi[r][c] > thr {
|
||||
active_area += 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let participation = if max_value > 0.0 {
|
||||
roi_sum / max_value
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let concentration = if roi_sum > 0.0 {
|
||||
max_value / roi_sum
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// ring1_sum (上下左右4点)
|
||||
let ring1_positions = [
|
||||
(center - 1, center),
|
||||
(center + 1, center),
|
||||
(center, center - 1),
|
||||
(center, center + 1),
|
||||
];
|
||||
let ring1_sum: f64 = ring1_positions.iter().map(|&(r, c)| roi[r][c]).sum();
|
||||
|
||||
// ring2_sum (除中心和ring1外)
|
||||
let mut ring2_sum = 0.0f64;
|
||||
for r in 0..ROI_SIZE {
|
||||
for c in 0..ROI_SIZE {
|
||||
if (r, c) == (center, center) {
|
||||
continue;
|
||||
}
|
||||
if ring1_positions.contains(&(r, c)) {
|
||||
continue;
|
||||
}
|
||||
ring2_sum += roi[r][c];
|
||||
}
|
||||
}
|
||||
|
||||
let ring1_ratio = if max_value > 0.0 {
|
||||
ring1_sum / max_value
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let ring2_ratio = if max_value > 0.0 {
|
||||
ring2_sum / max_value
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// spread
|
||||
let spread = if roi_sum > 0.0 {
|
||||
let mut s = 0.0f64;
|
||||
for r in 0..ROI_SIZE {
|
||||
for c in 0..ROI_SIZE {
|
||||
let dr = r as f64 - center as f64;
|
||||
let dc = c as f64 - center as f64;
|
||||
s += (dr * dr + dc * dc) * roi[r][c];
|
||||
}
|
||||
}
|
||||
s / roi_sum
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// asym_x
|
||||
let mut left_sum = 0.0f64;
|
||||
let mut right_sum = 0.0f64;
|
||||
for r in 0..ROI_SIZE {
|
||||
for c in 0..center {
|
||||
left_sum += roi[r][c];
|
||||
}
|
||||
for c in (center + 1)..ROI_SIZE {
|
||||
right_sum += roi[r][c];
|
||||
}
|
||||
}
|
||||
let asym_x = if roi_sum > 0.0 {
|
||||
(right_sum - left_sum) / roi_sum
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// asym_y
|
||||
let mut up_sum = 0.0f64;
|
||||
let mut down_sum = 0.0f64;
|
||||
for r in 0..center {
|
||||
for c in 0..ROI_SIZE {
|
||||
up_sum += roi[r][c];
|
||||
}
|
||||
}
|
||||
for r in (center + 1)..ROI_SIZE {
|
||||
for c in 0..ROI_SIZE {
|
||||
down_sum += roi[r][c];
|
||||
}
|
||||
}
|
||||
let asym_y = if roi_sum > 0.0 {
|
||||
(down_sum - up_sum) / roi_sum
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// 位置
|
||||
let peak_row_norm = peak_row as f64 / (ROWS - 1) as f64;
|
||||
let peak_col_norm = peak_col as f64 / (COLS - 1) as f64;
|
||||
|
||||
// near_edge
|
||||
let r = ROI_RADIUS as isize;
|
||||
let near_edge = if (peak_row as isize) < r
|
||||
|| peak_row >= ROWS - ROI_RADIUS
|
||||
|| (peak_col as isize) < r
|
||||
|| peak_col >= COLS - ROI_RADIUS
|
||||
{
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// 时序特征
|
||||
let delta_sum = roi_sum - self.prev_roi_sum;
|
||||
if self.first_frame {
|
||||
self.ema_sum = roi_sum;
|
||||
self.first_frame = false;
|
||||
} else {
|
||||
self.ema_sum = self.ema_alpha * self.ema_sum + (1.0 - self.ema_alpha) * roi_sum;
|
||||
}
|
||||
self.prev_roi_sum = roi_sum;
|
||||
|
||||
let scalars = [
|
||||
max_value,
|
||||
roi_sum,
|
||||
global_sum,
|
||||
active_area,
|
||||
participation,
|
||||
concentration,
|
||||
ring1_sum,
|
||||
ring2_sum,
|
||||
ring1_ratio,
|
||||
ring2_ratio,
|
||||
spread,
|
||||
asym_x,
|
||||
asym_y,
|
||||
peak_row_norm,
|
||||
peak_col_norm,
|
||||
near_edge,
|
||||
delta_sum,
|
||||
self.ema_sum,
|
||||
];
|
||||
for &v in &scalars {
|
||||
feat[idx] = v;
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
debug_assert_eq!(idx, N_FEATURES);
|
||||
feat
|
||||
}
|
||||
|
||||
fn update_temporal(&mut self, roi_sum: f64) {
|
||||
self.prev_roi_sum = roi_sum;
|
||||
if self.first_frame {
|
||||
self.ema_sum = roi_sum;
|
||||
self.first_frame = false;
|
||||
} else {
|
||||
self.ema_sum = self.ema_alpha * self.ema_sum + (1.0 - self.ema_alpha) * roi_sum;
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────── 推理 ─────────────
|
||||
|
||||
fn ridge_predict(&self, features: &[f64; N_FEATURES]) -> f64 {
|
||||
let mut scaled = [0.0f64; N_FEATURES];
|
||||
for i in 0..N_FEATURES {
|
||||
let s = self.scaler_scale[i];
|
||||
scaled[i] = if s.abs() > 1e-12 {
|
||||
(features[i] - self.scaler_mean[i]) / s
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
}
|
||||
let mut y = self.ridge_intercept;
|
||||
for i in 0..N_FEATURES {
|
||||
y += self.ridge_coef[i] * scaled[i];
|
||||
}
|
||||
y
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ use async_trait::async_trait;
|
||||
use csv::StringRecord;
|
||||
use anyhow::anyhow;
|
||||
use std::io::Read;
|
||||
use log::{debug, info};
|
||||
use log::debug;
|
||||
|
||||
const FRAME_BUFFER_MIN_LENGTH: usize = 15;
|
||||
|
||||
@@ -226,7 +226,6 @@ impl Codec<TactileAFrame> for TactileACodec {
|
||||
req_bytes.extend_from_slice((f.meta.except_data_len as u16).to_le_bytes().as_slice());
|
||||
let checksum = calc_crc8_itu(req_bytes.as_slice());
|
||||
req_bytes.push(checksum);
|
||||
info!("send: {:02X?}", req_bytes);
|
||||
Ok(req_bytes)
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -62,7 +62,6 @@ impl Codec<TestFrame> for TestCodec {
|
||||
continue;
|
||||
}
|
||||
let dts = elapsed_millis(session_started_at);
|
||||
println!("dts_ms: {dts}");
|
||||
frames.push(TestFrame {
|
||||
header: [0xAA, 0x55],
|
||||
cmd: cmd,
|
||||
@@ -244,11 +243,10 @@ mod tests {
|
||||
fn test_read_csv_basic() -> anyhow::Result<()> {
|
||||
let mut rdr = Reader::from_path("recording_20260329_125238.csv")?;
|
||||
let headers = rdr.headers()?;
|
||||
println!("headers: {:?}", headers);
|
||||
|
||||
for result in rdr.records() {
|
||||
let record = result?;
|
||||
println!("record: {:?}", record);
|
||||
let _ = record;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -13,7 +13,6 @@ pub mod record;
|
||||
pub mod utils;
|
||||
#[cfg(feature = "multi-dim")]
|
||||
pub mod multi_dim_force;
|
||||
pub mod basin_force_estimator;
|
||||
|
||||
pub type TestRecording = Recording<TestFrame>;
|
||||
pub type TactileARecording = Recording<TactileAFrame>;
|
||||
|
||||
@@ -13,6 +13,7 @@ pub struct HudPacket {
|
||||
pub panels: Vec<HudSignalPanel>,
|
||||
pub summary: HudSummary,
|
||||
pub pressure_matrix: Option<Vec<f32>>,
|
||||
pub spatial_force: Option<HudSpatialForce>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, Clone)]
|
||||
@@ -74,6 +75,14 @@ pub struct HudSignalIcon {
|
||||
pub tone: HudTone,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct HudSpatialForce {
|
||||
pub angle_deg: f32,
|
||||
pub magnitude: f32,
|
||||
pub confidence: f32,
|
||||
}
|
||||
|
||||
struct HudPanelUpdate {
|
||||
source_id: String,
|
||||
values: Vec<f32>,
|
||||
@@ -89,6 +98,7 @@ pub struct HudChartState {
|
||||
order: Vec<String>,
|
||||
summary_points: Vec<f32>,
|
||||
pressure_matrix: Option<Vec<f32>>,
|
||||
spatial_force: Option<HudSpatialForce>,
|
||||
last_frame_seen: Option<Instant>,
|
||||
}
|
||||
|
||||
@@ -99,6 +109,7 @@ impl HudChartState {
|
||||
order: Vec::new(),
|
||||
summary_points: Vec::new(),
|
||||
pressure_matrix: None,
|
||||
spatial_force: None,
|
||||
last_frame_seen: None,
|
||||
}
|
||||
}
|
||||
@@ -107,6 +118,10 @@ impl HudChartState {
|
||||
push_summary_point(&mut self.summary_points, value);
|
||||
}
|
||||
|
||||
pub fn clear_summary(&mut self) {
|
||||
self.summary_points.clear();
|
||||
}
|
||||
|
||||
pub fn record_pressure_matrix(&mut self, values: &[i32]) {
|
||||
if values.is_empty() {
|
||||
return;
|
||||
@@ -115,6 +130,10 @@ impl HudChartState {
|
||||
self.pressure_matrix = Some(values.iter().map(|value| *value as f32).collect());
|
||||
}
|
||||
|
||||
pub fn record_spatial_force(&mut self, spatial_force: Option<HudSpatialForce>) {
|
||||
self.spatial_force = spatial_force;
|
||||
}
|
||||
|
||||
pub fn apply_frame(&mut self, frame: &TestFrame, decoded_values: Option<&[i32]>) -> HudPacket {
|
||||
let now = Instant::now();
|
||||
self.last_frame_seen = Some(now);
|
||||
@@ -130,9 +149,15 @@ impl HudChartState {
|
||||
pub fn prune_stale(&mut self) -> Option<HudPacket> {
|
||||
let before = self.panels.len();
|
||||
let summary_points_before = self.summary_points.len();
|
||||
let had_pressure_matrix = self.pressure_matrix.is_some();
|
||||
let had_spatial_force = self.spatial_force.is_some();
|
||||
self.prune_stale_at(Instant::now());
|
||||
|
||||
if before == self.panels.len() && summary_points_before == self.summary_points.len() {
|
||||
if before == self.panels.len()
|
||||
&& summary_points_before == self.summary_points.len()
|
||||
&& had_pressure_matrix == self.pressure_matrix.is_some()
|
||||
&& had_spatial_force == self.spatial_force.is_some()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -187,6 +212,7 @@ impl HudChartState {
|
||||
if summary_stale {
|
||||
self.summary_points.clear();
|
||||
self.pressure_matrix = None;
|
||||
self.spatial_force = None;
|
||||
self.last_frame_seen = None;
|
||||
}
|
||||
}
|
||||
@@ -205,6 +231,7 @@ impl HudChartState {
|
||||
panels,
|
||||
summary: build_summary(&self.summary_points),
|
||||
pressure_matrix: self.pressure_matrix.clone(),
|
||||
spatial_force: self.spatial_force.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,122 +1,506 @@
|
||||
use ndarray::Array2;
|
||||
|
||||
const TOTAL_PRESSURE_LOW_THRESHOLD: usize = 500;
|
||||
const COP_STABILITY_FRAMES_REQUIRED: usize = 5;
|
||||
const SENSOR_ROWS: usize = 12;
|
||||
const SENSOR_COLS: usize = 7;
|
||||
const SENSOR_COUNT: usize = SENSOR_ROWS * SENSOR_COLS;
|
||||
|
||||
const NOISE_COLLECT_MS: f32 = 300.0;
|
||||
const THRESH_K: f32 = 5.0;
|
||||
const MIN_THRESHOLD: f32 = 50.0;
|
||||
const CONTACT_CONFIRM_MS: f32 = 20.0;
|
||||
const RELEASE_CONFIRM_MS: f32 = 50.0;
|
||||
const INIT_COLLECT_MS: f32 = 80.0;
|
||||
const SNAP_CENTER_X: f32 = 3.0;
|
||||
const SNAP_CENTER_Y: f32 = 5.5;
|
||||
const SNAP_RANGE_X: f32 = 0.25;
|
||||
const SNAP_RANGE_Y: f32 = 0.25;
|
||||
const POST_REFINE_WINDOW_MS: f32 = 800.0;
|
||||
const POST_STABLE_MS: f32 = 200.0;
|
||||
const POST_STABLE_THRESH: f32 = 0.1;
|
||||
const COP_LPF_ALPHA: f32 = 0.25;
|
||||
const EPSILON: f32 = 1e-8;
|
||||
const DISPLAY_ANGLE_OFFSET_DEG: f32 = -90.0;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PztSpatialAnalysis {
|
||||
pub angle_deg: f32,
|
||||
pub magnitude: f32,
|
||||
pub planar_x: f32,
|
||||
pub planar_y: f32,
|
||||
pub confidence: f32,
|
||||
pub contact_active: bool,
|
||||
pub reportable: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum CoPState {
|
||||
NoContact = 0,
|
||||
InitCollecting = 1,
|
||||
PostRefining = 2,
|
||||
Ready = 3,
|
||||
}
|
||||
|
||||
pub struct PztProcessor {
|
||||
first_frame: Option<Vec<f32>>,
|
||||
dynamic_thresh: Option<f32>,
|
||||
noise_samples: Vec<f32>,
|
||||
noise_start_ms: Option<f32>,
|
||||
first_contact_cop_x: Option<f32>,
|
||||
first_contact_cop_y: Option<f32>,
|
||||
contact_initialized: bool,
|
||||
total_pressure_low_counter: usize,
|
||||
state: CoPState,
|
||||
init_x_buf: Vec<f32>,
|
||||
init_y_buf: Vec<f32>,
|
||||
init_start_ms: Option<f32>,
|
||||
post_start_ms: Option<f32>,
|
||||
post_stable_start_ms: Option<f32>,
|
||||
post_cand_x: Option<f32>,
|
||||
post_cand_y: Option<f32>,
|
||||
post_refined: bool,
|
||||
contact_candidate_start_ms: Option<f32>,
|
||||
release_candidate_start_ms: Option<f32>,
|
||||
filtered_cop_x: Option<f32>,
|
||||
filtered_cop_y: Option<f32>,
|
||||
synthetic_timestamp_ms: f32,
|
||||
}
|
||||
|
||||
impl PztProcessor {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
first_frame: None,
|
||||
let mut processor = Self {
|
||||
dynamic_thresh: None,
|
||||
noise_samples: Vec::new(),
|
||||
noise_start_ms: None,
|
||||
first_contact_cop_x: None,
|
||||
first_contact_cop_y: None,
|
||||
contact_initialized: false,
|
||||
total_pressure_low_counter: 0,
|
||||
}
|
||||
state: CoPState::NoContact,
|
||||
init_x_buf: Vec::new(),
|
||||
init_y_buf: Vec::new(),
|
||||
init_start_ms: None,
|
||||
post_start_ms: None,
|
||||
post_stable_start_ms: None,
|
||||
post_cand_x: None,
|
||||
post_cand_y: None,
|
||||
post_refined: false,
|
||||
contact_candidate_start_ms: None,
|
||||
release_candidate_start_ms: None,
|
||||
filtered_cop_x: None,
|
||||
filtered_cop_y: None,
|
||||
synthetic_timestamp_ms: 0.0,
|
||||
};
|
||||
processor.reset_all();
|
||||
processor
|
||||
}
|
||||
|
||||
fn subtract_baseline(&mut self, current_frame: &[f32]) -> Vec<f32> {
|
||||
if self.first_frame.is_none() {
|
||||
self.first_frame = Some(current_frame.to_vec());
|
||||
}
|
||||
|
||||
let baseline = self.first_frame.as_ref().unwrap();
|
||||
current_frame
|
||||
.iter()
|
||||
.zip(baseline.iter())
|
||||
.map(|(c, b)| (c - b).max(0.0))
|
||||
.collect()
|
||||
fn reset_all(&mut self) {
|
||||
self.dynamic_thresh = None;
|
||||
self.noise_samples.clear();
|
||||
self.noise_start_ms = None;
|
||||
self.reset_contact_state();
|
||||
}
|
||||
|
||||
fn reset_cop_state(&mut self) {
|
||||
fn reset_contact_state(&mut self) {
|
||||
self.first_contact_cop_x = None;
|
||||
self.first_contact_cop_y = None;
|
||||
self.contact_initialized = false;
|
||||
self.total_pressure_low_counter = 0;
|
||||
self.state = CoPState::NoContact;
|
||||
self.init_x_buf.clear();
|
||||
self.init_y_buf.clear();
|
||||
self.init_start_ms = None;
|
||||
self.post_start_ms = None;
|
||||
self.post_stable_start_ms = None;
|
||||
self.post_cand_x = None;
|
||||
self.post_cand_y = None;
|
||||
self.post_refined = false;
|
||||
self.contact_candidate_start_ms = None;
|
||||
self.release_candidate_start_ms = None;
|
||||
self.filtered_cop_x = None;
|
||||
self.filtered_cop_y = None;
|
||||
}
|
||||
|
||||
fn compute_pressure_direction(&mut self, frame: &[f32]) -> (f32, f32) {
|
||||
let frame2d = Array2::from_shape_vec((SENSOR_ROWS, SENSOR_COLS), frame.to_vec()).unwrap();
|
||||
let total_pressure: f32 = frame2d.sum();
|
||||
if total_pressure < TOTAL_PRESSURE_LOW_THRESHOLD as f32 {
|
||||
self.total_pressure_low_counter += 1;
|
||||
} else {
|
||||
self.total_pressure_low_counter = 0;
|
||||
}
|
||||
|
||||
if self.total_pressure_low_counter >= COP_STABILITY_FRAMES_REQUIRED {
|
||||
self.reset_cop_state();
|
||||
return (0.0, 0.0);
|
||||
}
|
||||
|
||||
if total_pressure == 0.0 {
|
||||
return (0.0, 0.0);
|
||||
}
|
||||
|
||||
let mut sum_x = 0.0;
|
||||
let mut sum_y = 0.0;
|
||||
|
||||
for r in 0..SENSOR_ROWS {
|
||||
for c in 0..SENSOR_COLS {
|
||||
let val = frame2d[(r, c)];
|
||||
sum_x += val * c as f32;
|
||||
sum_y += val * r as f32;
|
||||
}
|
||||
}
|
||||
|
||||
let cop_x = sum_x / total_pressure;
|
||||
let cop_y = sum_y / total_pressure;
|
||||
|
||||
if !self.contact_initialized {
|
||||
self.first_contact_cop_x = Some(cop_x);
|
||||
self.first_contact_cop_y = Some(cop_y);
|
||||
self.contact_initialized = true;
|
||||
return (0.0, 0.0);
|
||||
}
|
||||
|
||||
let dx = cop_x - self.first_contact_cop_x.unwrap();
|
||||
let dy = cop_y - self.first_contact_cop_y.unwrap();
|
||||
|
||||
(dx, dy)
|
||||
}
|
||||
|
||||
fn compute_vector_angle(x: f32, y: f32) -> (f32, f32) {
|
||||
let epsilon = 1e-8;
|
||||
let mag = (x * x + y * y).sqrt();
|
||||
let mut angle = (y).atan2(x + epsilon).to_degrees();
|
||||
if angle < 0.0 {
|
||||
angle += 360.0;
|
||||
}
|
||||
(angle, mag)
|
||||
}
|
||||
|
||||
fn compute_pzt_angle(px: f32, py: f32) -> (f32, f32) {
|
||||
Self::compute_vector_angle(px, -py)
|
||||
}
|
||||
|
||||
pub fn get_pzt_angle(&mut self, adc_data: &[f32]) -> Result<f32, &'static str> {
|
||||
if adc_data.len() != 84 {
|
||||
pub fn get_pzt_analysis_at(
|
||||
&mut self,
|
||||
adc_data: &[f32],
|
||||
timestamp_ms: f32,
|
||||
) -> Result<PztSpatialAnalysis, &'static str> {
|
||||
if adc_data.len() != SENSOR_COUNT {
|
||||
return Err("ADC data length must be 84");
|
||||
}
|
||||
|
||||
let baseline = self.subtract_baseline(adc_data);
|
||||
let (dx, dy) = self.compute_pressure_direction(&baseline);
|
||||
let (angle, _) = Self::compute_pzt_angle(dx, dy);
|
||||
let frame = Self::prepare_frame(adc_data);
|
||||
let total_pressure = frame.iter().sum::<f32>();
|
||||
self.update_dynamic_threshold(total_pressure, timestamp_ms);
|
||||
|
||||
Ok(angle)
|
||||
let raw_contact = self.is_raw_contact(total_pressure);
|
||||
let contact_valid = self.debounce_contact(raw_contact, timestamp_ms);
|
||||
if !contact_valid {
|
||||
self.handle_no_contact();
|
||||
return Ok(Self::empty_analysis(false));
|
||||
}
|
||||
|
||||
let Some((cop_x, cop_y)) = Self::compute_cop(&frame, total_pressure) else {
|
||||
return Ok(Self::empty_analysis(false));
|
||||
};
|
||||
let (cop_x, cop_y) = self.filter_cop(cop_x, cop_y);
|
||||
self.update_state_machine(cop_x, cop_y, timestamp_ms);
|
||||
|
||||
let (base_x, base_y, dx, dy) = match (self.first_contact_cop_x, self.first_contact_cop_y) {
|
||||
(Some(base_x), Some(base_y)) => (base_x, base_y, cop_x - base_x, base_y - cop_y),
|
||||
_ => (cop_x, cop_y, 0.0, 0.0),
|
||||
};
|
||||
let _ = (base_x, base_y);
|
||||
|
||||
let (angle_deg, magnitude) = Self::compute_vector_angle(dx, dy);
|
||||
let reportable = self.state != CoPState::NoContact && magnitude > 0.0;
|
||||
|
||||
Ok(PztSpatialAnalysis {
|
||||
angle_deg,
|
||||
magnitude,
|
||||
planar_x: dx,
|
||||
planar_y: dy,
|
||||
confidence: (self.state as i32 as f32 / CoPState::Ready as i32 as f32).clamp(0.0, 1.0),
|
||||
contact_active: self.state != CoPState::NoContact,
|
||||
reportable,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_pzt_analysis(
|
||||
&mut self,
|
||||
adc_data: &[f32],
|
||||
) -> Result<PztSpatialAnalysis, &'static str> {
|
||||
self.synthetic_timestamp_ms += 16.667;
|
||||
self.get_pzt_analysis_at(adc_data, self.synthetic_timestamp_ms)
|
||||
}
|
||||
|
||||
pub fn get_pzt_angle(&mut self, adc_data: &[f32]) -> Result<f32, &'static str> {
|
||||
Ok(self.get_pzt_analysis(adc_data)?.angle_deg)
|
||||
}
|
||||
|
||||
pub fn should_report(analysis: &PztSpatialAnalysis) -> bool {
|
||||
analysis.reportable
|
||||
}
|
||||
|
||||
pub fn reset_baseline(&mut self) {
|
||||
self.first_frame = None;
|
||||
self.reset_cop_state();
|
||||
self.reset_all();
|
||||
self.synthetic_timestamp_ms = 0.0;
|
||||
}
|
||||
|
||||
fn prepare_frame(adc_data: &[f32]) -> [f32; SENSOR_COUNT] {
|
||||
let mut frame = [0.0; SENSOR_COUNT];
|
||||
for (target, value) in frame.iter_mut().zip(adc_data.iter().copied()) {
|
||||
*target = value.max(0.0);
|
||||
}
|
||||
frame
|
||||
}
|
||||
|
||||
fn update_dynamic_threshold(&mut self, total_pressure: f32, timestamp_ms: f32) {
|
||||
if self.dynamic_thresh.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.noise_start_ms.is_none() {
|
||||
self.noise_start_ms = Some(timestamp_ms);
|
||||
}
|
||||
|
||||
self.noise_samples.push(total_pressure);
|
||||
let noise_start = self.noise_start_ms.unwrap_or(timestamp_ms);
|
||||
if timestamp_ms - noise_start >= NOISE_COLLECT_MS {
|
||||
let mean = mean(&self.noise_samples);
|
||||
let std = stddev(&self.noise_samples, mean);
|
||||
self.dynamic_thresh = Some((mean + THRESH_K * std).max(MIN_THRESHOLD));
|
||||
}
|
||||
}
|
||||
|
||||
fn is_raw_contact(&self, total_pressure: f32) -> bool {
|
||||
self.dynamic_thresh
|
||||
.map(|threshold| total_pressure >= threshold)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn debounce_contact(&mut self, raw_contact: bool, timestamp_ms: f32) -> bool {
|
||||
let currently_in_contact = self.state != CoPState::NoContact;
|
||||
|
||||
if raw_contact {
|
||||
self.release_candidate_start_ms = None;
|
||||
|
||||
if currently_in_contact {
|
||||
return true;
|
||||
}
|
||||
|
||||
let contact_start = *self.contact_candidate_start_ms.get_or_insert(timestamp_ms);
|
||||
return timestamp_ms - contact_start >= CONTACT_CONFIRM_MS;
|
||||
}
|
||||
|
||||
self.contact_candidate_start_ms = None;
|
||||
if !currently_in_contact {
|
||||
return false;
|
||||
}
|
||||
|
||||
let release_start = *self.release_candidate_start_ms.get_or_insert(timestamp_ms);
|
||||
timestamp_ms - release_start < RELEASE_CONFIRM_MS
|
||||
}
|
||||
|
||||
fn handle_no_contact(&mut self) {
|
||||
if self.state != CoPState::NoContact {
|
||||
self.reset_contact_state();
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_cop(frame: &[f32; SENSOR_COUNT], total_pressure: f32) -> Option<(f32, f32)> {
|
||||
if total_pressure <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut weighted_col_sum = 0.0;
|
||||
let mut weighted_row_sum = 0.0;
|
||||
for row in 0..SENSOR_ROWS {
|
||||
for col in 0..SENSOR_COLS {
|
||||
let value = frame[row * SENSOR_COLS + col];
|
||||
weighted_col_sum += value * col as f32;
|
||||
weighted_row_sum += value * row as f32;
|
||||
}
|
||||
}
|
||||
|
||||
Some((
|
||||
weighted_col_sum / total_pressure,
|
||||
weighted_row_sum / total_pressure,
|
||||
))
|
||||
}
|
||||
|
||||
fn filter_cop(&mut self, cop_x: f32, cop_y: f32) -> (f32, f32) {
|
||||
if COP_LPF_ALPHA <= 0.0 {
|
||||
return (cop_x, cop_y);
|
||||
}
|
||||
|
||||
match (self.filtered_cop_x, self.filtered_cop_y) {
|
||||
(Some(prev_x), Some(prev_y)) => {
|
||||
let next_x = COP_LPF_ALPHA * cop_x + (1.0 - COP_LPF_ALPHA) * prev_x;
|
||||
let next_y = COP_LPF_ALPHA * cop_y + (1.0 - COP_LPF_ALPHA) * prev_y;
|
||||
self.filtered_cop_x = Some(next_x);
|
||||
self.filtered_cop_y = Some(next_y);
|
||||
(next_x, next_y)
|
||||
}
|
||||
_ => {
|
||||
self.filtered_cop_x = Some(cop_x);
|
||||
self.filtered_cop_y = Some(cop_y);
|
||||
(cop_x, cop_y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_state_machine(&mut self, cop_x: f32, cop_y: f32, timestamp_ms: f32) {
|
||||
if self.state == CoPState::NoContact {
|
||||
self.state = CoPState::InitCollecting;
|
||||
self.init_start_ms = Some(timestamp_ms);
|
||||
self.init_x_buf.clear();
|
||||
self.init_y_buf.clear();
|
||||
}
|
||||
|
||||
if self.state == CoPState::InitCollecting {
|
||||
self.init_x_buf.push(cop_x);
|
||||
self.init_y_buf.push(cop_y);
|
||||
|
||||
let init_start = self.init_start_ms.unwrap_or(timestamp_ms);
|
||||
if timestamp_ms - init_start >= INIT_COLLECT_MS {
|
||||
let mut base_x = median(&self.init_x_buf);
|
||||
let mut base_y = median(&self.init_y_buf);
|
||||
(base_x, base_y) = Self::apply_center_snap(base_x, base_y);
|
||||
|
||||
self.first_contact_cop_x = Some(base_x);
|
||||
self.first_contact_cop_y = Some(base_y);
|
||||
self.post_start_ms = Some(timestamp_ms);
|
||||
self.post_cand_x = None;
|
||||
self.post_cand_y = None;
|
||||
self.post_stable_start_ms = None;
|
||||
self.state = CoPState::PostRefining;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if self.state == CoPState::PostRefining {
|
||||
self.post_refine(cop_x, cop_y, timestamp_ms);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_center_snap(base_x: f32, base_y: f32) -> (f32, f32) {
|
||||
if (base_x - SNAP_CENTER_X).abs() <= SNAP_RANGE_X
|
||||
&& (base_y - SNAP_CENTER_Y).abs() <= SNAP_RANGE_Y
|
||||
{
|
||||
return (SNAP_CENTER_X, SNAP_CENTER_Y);
|
||||
}
|
||||
|
||||
(base_x, base_y)
|
||||
}
|
||||
|
||||
fn post_refine(&mut self, cop_x: f32, cop_y: f32, timestamp_ms: f32) {
|
||||
let post_start = *self.post_start_ms.get_or_insert(timestamp_ms);
|
||||
if timestamp_ms - post_start >= POST_REFINE_WINDOW_MS {
|
||||
self.post_refined = true;
|
||||
self.state = CoPState::Ready;
|
||||
return;
|
||||
}
|
||||
|
||||
let (Some(cand_x), Some(cand_y)) = (self.post_cand_x, self.post_cand_y) else {
|
||||
self.post_cand_x = Some(cop_x);
|
||||
self.post_cand_y = Some(cop_y);
|
||||
self.post_stable_start_ms = Some(timestamp_ms);
|
||||
return;
|
||||
};
|
||||
|
||||
let dist = ((cop_x - cand_x).powi(2) + (cop_y - cand_y).powi(2)).sqrt();
|
||||
if dist <= POST_STABLE_THRESH {
|
||||
let stable_start = *self.post_stable_start_ms.get_or_insert(timestamp_ms);
|
||||
if timestamp_ms - stable_start >= POST_STABLE_MS {
|
||||
let (refined_x, refined_y) = Self::apply_center_snap(cand_x, cand_y);
|
||||
self.first_contact_cop_x = Some(refined_x);
|
||||
self.first_contact_cop_y = Some(refined_y);
|
||||
self.post_refined = true;
|
||||
self.state = CoPState::Ready;
|
||||
}
|
||||
} else {
|
||||
self.post_cand_x = Some(cop_x);
|
||||
self.post_cand_y = Some(cop_y);
|
||||
self.post_stable_start_ms = Some(timestamp_ms);
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_vector_angle(x: f32, y: f32) -> (f32, f32) {
|
||||
let magnitude = (x * x + y * y).sqrt();
|
||||
let angle = (y.atan2(x + EPSILON).to_degrees() + DISPLAY_ANGLE_OFFSET_DEG).rem_euclid(360.0);
|
||||
(angle, magnitude)
|
||||
}
|
||||
|
||||
fn empty_analysis(contact_active: bool) -> PztSpatialAnalysis {
|
||||
PztSpatialAnalysis {
|
||||
angle_deg: 0.0,
|
||||
magnitude: 0.0,
|
||||
planar_x: 0.0,
|
||||
planar_y: 0.0,
|
||||
confidence: 0.0,
|
||||
contact_active,
|
||||
reportable: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mean(values: &[f32]) -> f32 {
|
||||
if values.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
values.iter().sum::<f32>() / values.len() as f32
|
||||
}
|
||||
|
||||
fn stddev(values: &[f32], mean: f32) -> f32 {
|
||||
if values.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
let variance = values
|
||||
.iter()
|
||||
.map(|value| {
|
||||
let delta = value - mean;
|
||||
delta * delta
|
||||
})
|
||||
.sum::<f32>()
|
||||
/ values.len() as f32;
|
||||
variance.sqrt()
|
||||
}
|
||||
|
||||
fn median(values: &[f32]) -> f32 {
|
||||
if values.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let mut sorted = values.to_vec();
|
||||
sorted.sort_by(|left, right| left.partial_cmp(right).unwrap_or(std::cmp::Ordering::Equal));
|
||||
let mid = sorted.len() / 2;
|
||||
if sorted.len() % 2 == 0 {
|
||||
(sorted[mid - 1] + sorted[mid]) * 0.5
|
||||
} else {
|
||||
sorted[mid]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{PztProcessor, SENSOR_COLS, SENSOR_ROWS};
|
||||
|
||||
fn index(row: usize, col: usize) -> usize {
|
||||
row * SENSOR_COLS + col
|
||||
}
|
||||
|
||||
fn make_frame(active: &[(usize, usize, f32)]) -> [f32; SENSOR_ROWS * SENSOR_COLS] {
|
||||
let mut frame = [0.0; SENSOR_ROWS * SENSOR_COLS];
|
||||
for (row, col, value) in active {
|
||||
frame[index(*row, *col)] = *value;
|
||||
}
|
||||
frame
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn idle_frame_does_not_report_contact() {
|
||||
let mut processor = PztProcessor::new();
|
||||
let frame = [0.0; SENSOR_ROWS * SENSOR_COLS];
|
||||
let analysis = processor.get_pzt_analysis_at(&frame, 0.0).unwrap();
|
||||
assert!(!analysis.contact_active);
|
||||
assert!(!analysis.reportable);
|
||||
assert_eq!(analysis.magnitude, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rightward_cop_shift_reports_rightward_angle() {
|
||||
let mut processor = PztProcessor::new();
|
||||
let quiet = [0.0; SENSOR_ROWS * SENSOR_COLS];
|
||||
for step in 0..20 {
|
||||
let _ = processor
|
||||
.get_pzt_analysis_at(&quiet, step as f32 * 20.0)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let center_contact = make_frame(&[
|
||||
(5, 2, 120.0),
|
||||
(5, 3, 180.0),
|
||||
(5, 4, 120.0),
|
||||
(6, 2, 120.0),
|
||||
(6, 3, 180.0),
|
||||
(6, 4, 120.0),
|
||||
]);
|
||||
for step in 20..70 {
|
||||
let _ = processor
|
||||
.get_pzt_analysis_at(¢er_contact, step as f32 * 20.0)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let right_contact = make_frame(&[
|
||||
(5, 3, 120.0),
|
||||
(5, 4, 220.0),
|
||||
(5, 5, 260.0),
|
||||
(6, 3, 120.0),
|
||||
(6, 4, 220.0),
|
||||
(6, 5, 260.0),
|
||||
]);
|
||||
let analysis = processor
|
||||
.get_pzt_analysis_at(&right_contact, 1_500.0)
|
||||
.unwrap();
|
||||
|
||||
assert!(analysis.contact_active);
|
||||
assert!(analysis.reportable);
|
||||
assert!(analysis.magnitude > 0.0);
|
||||
assert!(analysis.angle_deg >= 225.0 && analysis.angle_deg <= 315.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_debounce_holds_short_gap() {
|
||||
let mut processor = PztProcessor::new();
|
||||
let quiet = [0.0; SENSOR_ROWS * SENSOR_COLS];
|
||||
for step in 0..20 {
|
||||
let _ = processor
|
||||
.get_pzt_analysis_at(&quiet, step as f32 * 20.0)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let contact = make_frame(&[(5, 3, 300.0), (5, 4, 300.0), (6, 3, 300.0), (6, 4, 300.0)]);
|
||||
for step in 20..70 {
|
||||
let _ = processor
|
||||
.get_pzt_analysis_at(&contact, step as f32 * 20.0)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let weak = make_frame(&[(5, 3, 10.0), (5, 4, 10.0), (6, 3, 10.0), (6, 4, 10.0)]);
|
||||
let analysis = processor.get_pzt_analysis_at(&weak, 1_410.0).unwrap();
|
||||
assert!(analysis.contact_active);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
use crate::serial_core::basin_force_estimator::BasinForceEstimator;
|
||||
#[cfg(feature = "devkit")]
|
||||
use crate::devkit::{proto::SensorFrame, DevKitState};
|
||||
use crate::serial_core::codec::Codec;
|
||||
use crate::serial_core::codecs::tactile_a::TactileACodec;
|
||||
use crate::serial_core::frame::{FrameHandler, TactileAFrame, TestFrame};
|
||||
use crate::serial_core::model::{HudChartState, HudPacket};
|
||||
use crate::serial_core::model::{HudChartState, HudPacket, HudSpatialForce};
|
||||
#[cfg(feature = "multi-dim")]
|
||||
use crate::serial_core::multi_dim_force::PztProcessor;
|
||||
use crate::serial_core::record::Recording;
|
||||
use crate::serial_core::record::{FrameTiming, RecordedFrame};
|
||||
#[cfg(feature = "devkit")]
|
||||
use crate::devkit::{proto::SensorFrame, DevKitState};
|
||||
use anyhow::Result;
|
||||
use log::debug;
|
||||
use std::future::pending;
|
||||
#[cfg(feature = "devkit")]
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Instant;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
#[cfg(feature = "devkit")]
|
||||
use tauri::Manager;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::time::{self, Duration, MissedTickBehavior};
|
||||
use tokio_serial::SerialStream;
|
||||
@@ -34,6 +32,7 @@ pub enum PollMode<F> {
|
||||
struct PendingSubFrame<F> {
|
||||
frame: F,
|
||||
values: Vec<i32>,
|
||||
spatial_force: Option<HudSpatialForce>,
|
||||
}
|
||||
|
||||
pub trait SerialFrame: Clone + Send + 'static {
|
||||
@@ -234,7 +233,6 @@ where
|
||||
let mut prune_interval = time::interval(Duration::from_millis(450));
|
||||
#[cfg(feature = "multi-dim")]
|
||||
let mut pzt_processor = PztProcessor::new();
|
||||
let mut force_estimator = BasinForceEstimator::new();
|
||||
let mut pending_sub_frame: Option<PendingSubFrame<F>> = None;
|
||||
prune_interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
|
||||
|
||||
@@ -268,6 +266,7 @@ where
|
||||
let display_values = build_display_values(
|
||||
&mut chart_state,
|
||||
pending.values.as_slice(),
|
||||
pending.spatial_force,
|
||||
);
|
||||
|
||||
if let Some(packet) = pending
|
||||
@@ -311,33 +310,37 @@ where
|
||||
drop(record);
|
||||
|
||||
if let Some(vals) = decode_res {
|
||||
// Basin force estimation (pre-force)
|
||||
if vals.len() == 84 {
|
||||
let mut frame_f64 = [0.0f64; 84];
|
||||
for (i, v) in vals.iter().enumerate() {
|
||||
frame_f64[i] = *v as f64;
|
||||
}
|
||||
let pre_force = force_estimator.predict_frame(&frame_f64);
|
||||
debug!("pre-force: {:.2}", pre_force);
|
||||
}
|
||||
|
||||
#[cfg(feature = "multi-dim")]
|
||||
let mut spatial_force = None;
|
||||
#[cfg(not(feature = "multi-dim"))]
|
||||
let spatial_force = None;
|
||||
#[cfg(feature = "multi-dim")]
|
||||
{
|
||||
let pzt_values = vals.iter().map(|value| *value as f32).collect::<Vec<f32>>();
|
||||
if let Ok(angle) = pzt_processor.get_pzt_angle(&pzt_values) {
|
||||
// debug!("pzt angle: {:.2}", angle);
|
||||
if let Ok(analysis) = pzt_processor.get_pzt_analysis_at(&pzt_values, frame.dts_ms() as f32) {
|
||||
if PztProcessor::should_report(&analysis) {
|
||||
spatial_force = Some(HudSpatialForce {
|
||||
angle_deg: analysis.angle_deg,
|
||||
magnitude: analysis.magnitude,
|
||||
confidence: analysis.confidence,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "devkit")]
|
||||
{
|
||||
let summary = vals.iter().copied().sum::<i32>();
|
||||
let force = raw_to_g1(summary as u32);
|
||||
push_devkit_frame(&app, vals.as_slice(), frame.dts_ms(), force);
|
||||
}
|
||||
// #[cfg(feature = "devkit")]
|
||||
// {
|
||||
// let summary = vals.iter().copied().sum::<i32>();
|
||||
// #[cfg(feature = "debug")]
|
||||
// let force = raw_to_g1(summary as u32);
|
||||
// #[cfg(not(feature = "debug"))]
|
||||
// let force = summary as f64;
|
||||
// push_devkit_frame(&app, vals.as_slice(), frame.dts_ms(), force);
|
||||
// }
|
||||
|
||||
pending_sub_frame = Some(PendingSubFrame {
|
||||
frame: frame.clone(),
|
||||
values: vals,
|
||||
spatial_force,
|
||||
});
|
||||
} else if let Some(packet) = frame.to_hud_packet(&mut chart_state, None) {
|
||||
app.emit("hud_stream", packet)?;
|
||||
@@ -349,14 +352,30 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_display_values(chart_state: &mut HudChartState, values: &[i32]) -> Option<Vec<i32>> {
|
||||
fn build_display_values(
|
||||
chart_state: &mut HudChartState,
|
||||
values: &[i32],
|
||||
spatial_force: Option<HudSpatialForce>,
|
||||
) -> Option<Vec<i32>> {
|
||||
let summary = values.iter().copied().sum::<i32>();
|
||||
let force = raw_to_g1(summary as u32);
|
||||
chart_state.record_summary(force as f32);
|
||||
chart_state.record_spatial_force(spatial_force);
|
||||
|
||||
let x = raw_to_g1(summary as u32).min(MAX_DISPLAY_FORCE);
|
||||
if x <= MIN_DISPLAY_FORCE {
|
||||
let zero_values = vec![0; values.len()];
|
||||
chart_state.record_summary(0.0);
|
||||
chart_state.record_pressure_matrix(&zero_values);
|
||||
return Some(vec![0]);
|
||||
}
|
||||
|
||||
chart_state.record_pressure_matrix(values);
|
||||
Some(vec![summary])
|
||||
chart_state.record_summary(x as f32);
|
||||
Some(vec![x.round() as i32])
|
||||
}
|
||||
|
||||
const MIN_DISPLAY_FORCE: f64 = 0.1;
|
||||
const MAX_DISPLAY_FORCE: f64 = 25.6;
|
||||
|
||||
#[cfg(feature = "devkit")]
|
||||
fn push_devkit_frame(app: &AppHandle, values: &[i32], dts_ms: u64, resultant_force: f64) {
|
||||
let devkit_state = app.state::<DevKitState>();
|
||||
@@ -411,11 +430,11 @@ fn infer_matrix_shape(len: usize) -> (u32, u32) {
|
||||
|
||||
fn raw_to_g1(raw: u32) -> f64 {
|
||||
const X: [u32; 12] = [
|
||||
0, 84402, 117218, 140176, 159126, 175812, 191484, 208758, 224703, 252448, 302361, 352703,
|
||||
0, 21382, 108507, 123183, 147405, 171105, 192395, 250443, 231423, 350560, 396616, 429444
|
||||
];
|
||||
|
||||
const Y: [f64; 12] = [
|
||||
0.0, 160.0, 260.0, 360.0, 460.0, 560.0, 660.0, 760.0, 860.0, 1060.0, 1560.0, 2060.0,
|
||||
0.0, 57.0, 257.0, 357.0, 457.0, 557.0, 657.0, 857.0, 1057.0, 1557.0, 2057.0, 2557.0
|
||||
];
|
||||
|
||||
let n = X.len();
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"createUpdaterArtifacts": false,
|
||||
"createUpdaterArtifacts": true,
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
@@ -41,9 +41,7 @@
|
||||
"template": "nsis/installer.nsi"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
"resources/je-skin-devkit-server.exe"
|
||||
]
|
||||
"resources": []
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
|
||||
@@ -1,747 +0,0 @@
|
||||
from typing import Dict, List, Tuple
|
||||
import numpy as np
|
||||
from indicator.base import Indicator
|
||||
import math
|
||||
|
||||
|
||||
class EMA:
|
||||
"""指数移动平均"""
|
||||
|
||||
@staticmethod
|
||||
def calc(data: np.ndarray, period: int) -> np.ndarray:
|
||||
alpha = 2.0 / (period + 1)
|
||||
out = np.empty_like(data)
|
||||
out[0] = data[0]
|
||||
for i in range(1, len(data)):
|
||||
out[i] = alpha * data[i] + (1 - alpha) * out[i - 1]
|
||||
return out
|
||||
|
||||
|
||||
class SMA:
|
||||
"""简单移动平均"""
|
||||
|
||||
@staticmethod
|
||||
def calc(data: np.ndarray, period: int) -> np.ndarray:
|
||||
out = np.full_like(data, np.nan)
|
||||
if len(data) < period:
|
||||
return out
|
||||
cumsum = np.cumsum(data)
|
||||
out[period - 1:] = (cumsum[period - 1:] - np.concatenate([[0], cumsum[:-period]])) / period
|
||||
return out
|
||||
|
||||
|
||||
def true_range(high: np.ndarray, low: np.ndarray, close: np.ndarray) -> np.ndarray:
|
||||
"""True Range"""
|
||||
tr = np.empty(len(high))
|
||||
tr[0] = high[0] - low[0]
|
||||
for i in range(1, len(high)):
|
||||
tr[i] = max(high[i] - low[i], abs(high[i] - close[i - 1]), abs(low[i] - close[i - 1]))
|
||||
return tr
|
||||
|
||||
|
||||
class SignalTracker:
|
||||
"""逐帧更新的信号追踪器(无回看依赖,纯实时)"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ob_threshold: float = 80,
|
||||
os_threshold: float = 20,
|
||||
dist_extreme: float = 5,
|
||||
dist_far: float = 15,
|
||||
dist_mid: float = 30,
|
||||
macd_th: float = 0.3,
|
||||
rsi_ob: float = 70,
|
||||
rsi_os: float = 30,
|
||||
di_gap_bull: float = 15,
|
||||
di_gap_bear: float = -15,
|
||||
aroon_ob: float = 80,
|
||||
aroon_os: float = 20,
|
||||
williams_ob: float = -20,
|
||||
williams_os: float = -80,
|
||||
bias_ob: float = 5,
|
||||
bias_os: float = -5,
|
||||
):
|
||||
self.ob_threshold = ob_threshold
|
||||
self.os_threshold = os_threshold
|
||||
self.dist_extreme = dist_extreme
|
||||
self.dist_far = dist_far
|
||||
self.dist_mid = dist_mid
|
||||
self.macd_th = macd_th
|
||||
self.rsi_ob = rsi_ob
|
||||
self.rsi_os = rsi_os
|
||||
self.di_gap_bull = di_gap_bull
|
||||
self.di_gap_bear = di_gap_bear
|
||||
self.aroon_ob = aroon_ob
|
||||
self.aroon_os = aroon_os
|
||||
self.williams_ob = williams_ob
|
||||
self.williams_os = williams_os
|
||||
self.bias_ob = bias_ob
|
||||
self.bias_os = bias_os
|
||||
|
||||
# 状态缓存
|
||||
self._intp_buy_sma: float = 0.0
|
||||
self._intp_sell_sma: float = 0.0
|
||||
self._intp_sma_count: int = 0
|
||||
self._max_qtb_score: float = 0.0
|
||||
self._bull_count: int = 0
|
||||
self._bear_count: int = 0
|
||||
self._last_bull_idx: int = -100
|
||||
self._last_bear_idx: int = -100
|
||||
self._frame_idx: int = 0
|
||||
|
||||
# 历史缓冲区(保留最近200帧用于EMA/SMA/DI计算)
|
||||
self._buf_size: int = 200
|
||||
self._bbi_buf: List[float] = []
|
||||
self._amplitude_buf: List[float] = []
|
||||
self._close_buf: List[float] = []
|
||||
self._high_buf: List[float] = []
|
||||
self._low_buf: List[float] = []
|
||||
self._wr_buf: List[float] = []
|
||||
|
||||
def reset(self):
|
||||
"""重置所有状态"""
|
||||
self._intp_buy_sma = 0.0
|
||||
self._intp_sell_sma = 0.0
|
||||
self._intp_sma_count = 0
|
||||
self._max_qtb_score = 0.0
|
||||
self._bull_count = 0
|
||||
self._bear_count = 0
|
||||
self._last_bull_idx = -100
|
||||
self._last_bear_idx = -100
|
||||
self._frame_idx = 0
|
||||
self._bbi_buf.clear()
|
||||
self._amplitude_buf.clear()
|
||||
self._close_buf.clear()
|
||||
self._high_buf.clear()
|
||||
self._low_buf.clear()
|
||||
self._wr_buf.clear()
|
||||
|
||||
def _append_buf(self, buf: List[float], val: float):
|
||||
buf.append(val)
|
||||
if len(buf) > self._buf_size:
|
||||
buf.pop(0)
|
||||
|
||||
def _ema_val(self, buf: List[float], period: int) -> float:
|
||||
"""返回缓冲区中最后一个EMA值"""
|
||||
if len(buf) < period:
|
||||
return buf[-1] if buf else 0.0
|
||||
arr = np.array(buf, dtype=float)
|
||||
ema = EMA.calc(arr, period)
|
||||
return float(ema[-1])
|
||||
|
||||
def _sma_val(self, buf: List[float], period: int) -> float:
|
||||
if len(buf) < period:
|
||||
return np.nan
|
||||
arr = np.array(buf, dtype=float)
|
||||
sma = SMA.calc(arr, period)
|
||||
return float(sma[-1])
|
||||
|
||||
def get_signals(
|
||||
self,
|
||||
amplitude: float,
|
||||
bbi: float,
|
||||
cci: float,
|
||||
close: float,
|
||||
dmk: float,
|
||||
high: float,
|
||||
k: float,
|
||||
low: float,
|
||||
qtb_score: float,
|
||||
result: float,
|
||||
wr: float,
|
||||
percent: float,
|
||||
v0: float,
|
||||
boll_upper: float,
|
||||
boll_lower: float,
|
||||
macd_val: float,
|
||||
pmacd: float,
|
||||
bias: float,
|
||||
) -> Tuple[str, str, str, str]:
|
||||
"""
|
||||
逐帧更新,返回 (market_status, signal_type, intensity, detail)
|
||||
|
||||
不依赖历史数组,所有指标值由外部实时计算后传入。
|
||||
"""
|
||||
|
||||
# ─── 更新缓冲区 ───
|
||||
self._append_buf(self._bbi_buf, bbi)
|
||||
self._append_buf(self._amplitude_buf, amplitude)
|
||||
self._append_buf(self._close_buf, close)
|
||||
self._append_buf(self._high_buf, high)
|
||||
self._append_buf(self._low_buf, low)
|
||||
self._append_buf(self._wr_buf, wr)
|
||||
|
||||
# ─── 指标因子计算(使用传入值 + 缓冲区衍生值)───
|
||||
k_macd = self._k_macd_factor(k, macd_val, pmacd)
|
||||
k_di = self._k_di_factor(dmk, amplitude, close)
|
||||
k_aroon = self._k_aroon_factor(high, low, close)
|
||||
k_williams = self._k_williams_factor(wr)
|
||||
k_result = self._k_result_factor(result)
|
||||
k_bias = self._k_bias_factor(bias)
|
||||
k_trix = self._k_trix_factor(close)
|
||||
k_ema = self._k_ema_factor(close)
|
||||
k_amplitude = self._k_amplitude_factor(amplitude)
|
||||
|
||||
# ─── 累计 ───
|
||||
k_bull = k_macd + k_di + k_aroon + k_williams + k_result + k_bias + k_trix + k_ema + k_amplitude
|
||||
k_bear = k_macd + k_di + k_aroon + k_williams + k_result + k_bias + k_trix + k_ema + k_amplitude
|
||||
|
||||
if result > self.ob_threshold and k_bull > 0 and result >= 90:
|
||||
k_bull *= 1.5
|
||||
|
||||
# ─── 指数平滑 ───
|
||||
if self._intp_sma_count == 0:
|
||||
self._intp_buy_sma = k_bull
|
||||
self._intp_sell_sma = k_bear
|
||||
self._intp_sma_count = 1
|
||||
else:
|
||||
self._intp_sma_count += 1
|
||||
period = max(3, min(5, self._intp_sma_count))
|
||||
alpha = 2.0 / (period + 1)
|
||||
self._intp_buy_sma = alpha * k_bull + (1 - alpha) * self._intp_buy_sma
|
||||
self._intp_sell_sma = alpha * k_bear + (1 - alpha) * self._intp_sell_sma
|
||||
|
||||
# ─── 趋势确认与背离检测 ───
|
||||
is_bull_div, is_bear_div = self._detect_divergence(close, result)
|
||||
if is_bull_div:
|
||||
self._intp_buy_sma *= 1.3
|
||||
k_bull *= 1.3
|
||||
if is_bear_div:
|
||||
self._intp_sell_sma *= 1.3
|
||||
k_bear *= 1.3
|
||||
|
||||
is_bull, is_bear = self._detect_trend(close)
|
||||
if is_bull:
|
||||
self._intp_buy_sma *= 1.15
|
||||
k_bull *= 1.15
|
||||
if is_bear:
|
||||
self._intp_sell_sma *= 1.15
|
||||
k_bear *= 1.15
|
||||
|
||||
wmacd = self._wmacd()
|
||||
if wmacd > 0:
|
||||
k_bull += wmacd * 0.5
|
||||
elif wmacd < 0:
|
||||
k_bear += abs(wmacd) * 0.5
|
||||
|
||||
if qtb_score > self._max_qtb_score * 0.7:
|
||||
pass
|
||||
elif qtb_score < 3 and qtb_score > 1:
|
||||
k_bull *= 0.85
|
||||
k_bear *= 0.85
|
||||
|
||||
# ─── 信号分类 ───
|
||||
max_k = max(abs(k_bull), abs(k_bear))
|
||||
if max_k > 0:
|
||||
buy_score = ((k_bull + max_k) / (2 * max_k)) * 100
|
||||
sell_score = ((k_bear + max_k) / (2 * max_k)) * 100
|
||||
else:
|
||||
buy_score = sell_score = 50
|
||||
|
||||
bull_t = buy_score > 70 and result < 90
|
||||
bear_t = sell_score > 70 and result > 10
|
||||
k_cci = cci / 300
|
||||
boll_mid = self._boll_mid()
|
||||
|
||||
is_bull_t = (
|
||||
bull_t
|
||||
and k_bull > k_cci
|
||||
and close < boll_mid
|
||||
and (
|
||||
(result > 65 and result < 85 and k_bull > 0 and k_bear > 0 and (k_bear - k_bull < 1.5 or result > 75))
|
||||
or (result < 35 and result > 15 and k_bear > 0 and k_bull > 0 and (k_bull - k_bear < 1.5 or result < 25))
|
||||
)
|
||||
)
|
||||
is_bear_t = (
|
||||
bear_t
|
||||
and k_bear > k_cci
|
||||
and close > boll_mid
|
||||
and (
|
||||
(result > 65 and result < 85 and k_bull > 0 and k_bear > 0 and (k_bear - k_bull < 1.5 or result > 75))
|
||||
or (result < 35 and result > 15 and k_bear > 0 and k_bull > 0 and (k_bull - k_bear < 1.5 or result < 25))
|
||||
)
|
||||
)
|
||||
|
||||
if is_bull_t:
|
||||
self._bull_count += 1
|
||||
self._bear_count = 0
|
||||
self._last_bull_idx = self._frame_idx
|
||||
elif is_bear_t:
|
||||
self._bear_count += 1
|
||||
self._bull_count = 0
|
||||
self._last_bear_idx = self._frame_idx
|
||||
else:
|
||||
self._bull_count = max(self._bull_count - 1, 0)
|
||||
self._bear_count = max(self._bear_count - 1, 0)
|
||||
|
||||
# ─── 信号输出 ───
|
||||
sig_strength = 1.0 + max(0, (self._bull_count - 3) * 0.1) + max(0, (self._bear_count - 3) * 0.1)
|
||||
|
||||
strength = (
|
||||
"极强" if sig_strength >= 1.7
|
||||
else "强" if sig_strength >= 1.4
|
||||
else "中" if sig_strength >= 1.1
|
||||
else "弱"
|
||||
)
|
||||
|
||||
if buy_score >= sell_score:
|
||||
signal = f"买{strength}"
|
||||
else:
|
||||
signal = f"卖{strength}"
|
||||
|
||||
# ─── 市场状态 ───
|
||||
if abs(buy_score - sell_score) < 10:
|
||||
status = "中性震荡"
|
||||
elif buy_score > sell_score:
|
||||
if self._bull_count >= 3:
|
||||
status = "强势上涨"
|
||||
elif result > self.ob_threshold:
|
||||
status = "高位企稳"
|
||||
else:
|
||||
status = "温和上涨"
|
||||
else:
|
||||
if self._bear_count >= 3:
|
||||
status = "强势下跌"
|
||||
elif result < self.os_threshold:
|
||||
status = "低位企稳"
|
||||
else:
|
||||
status = "温和下跌"
|
||||
|
||||
# ─── 距离 ───
|
||||
boll_len = boll_upper - boll_lower
|
||||
if boll_len > 0:
|
||||
dist_ratio = (close - boll_lower) / boll_len * 100
|
||||
else:
|
||||
dist_ratio = 50
|
||||
|
||||
if dist_ratio < 50 - self.dist_extreme:
|
||||
dist = "极端超卖"
|
||||
elif dist_ratio < 50 - self.dist_far:
|
||||
dist = "远离"
|
||||
elif dist_ratio < 50 - self.dist_mid:
|
||||
dist = "偏离"
|
||||
elif dist_ratio < 50 + self.dist_mid:
|
||||
dist = "接近"
|
||||
elif dist_ratio < 50 + self.dist_far:
|
||||
dist = "靠近"
|
||||
elif dist_ratio < 50 + self.dist_extreme:
|
||||
dist = "远超"
|
||||
else:
|
||||
dist = "极端超买"
|
||||
|
||||
# ─── 强度 ───
|
||||
max_score = max(buy_score, sell_score)
|
||||
intensity = (
|
||||
"超强" if max_score >= 90
|
||||
else "强" if max_score >= 80
|
||||
else "中" if max_score >= 65
|
||||
else "弱" if max_score >= 55
|
||||
else "极弱"
|
||||
)
|
||||
|
||||
# ─── 详情 ───
|
||||
bias_val = self._ema_bias()
|
||||
bbp_val = self._boll_pct_b(close, boll_upper, boll_lower)
|
||||
detail = (
|
||||
f"前量:{percent:.1f} 数量:{int(amplitude):03d} 百分比:{bbp_val:.1f} "
|
||||
f"正:{k_bull:.1f} 负:{k_bear:.1f} 连买:{self._bull_count} 连卖:{self._bear_count} "
|
||||
f"误差:{bias_val:.1f}"
|
||||
)
|
||||
|
||||
self._frame_idx += 1
|
||||
return status, signal, intensity, detail
|
||||
|
||||
# ═══════════════════════════════════════════════════
|
||||
# 指标因子(全部基于实时数据,无回看窗口)
|
||||
# ═══════════════════════════════════════════════════
|
||||
|
||||
def _k_macd_factor(self, k: float, macd_val: float, pmacd: float) -> float:
|
||||
is_ob = k > self.ob_threshold
|
||||
is_os = k < self.os_threshold
|
||||
k_macd = 0.0
|
||||
if is_ob or is_os:
|
||||
if macd_val < 0:
|
||||
if is_os and k < 30 and pmacd > 0 and pmacd <= 10 and macd_val > 1.5:
|
||||
k_macd = 3.5
|
||||
elif is_ob and k > 80 and macd_val > 3 and abs(pmacd) < 5:
|
||||
k_macd = -3.5
|
||||
elif pmacd < 0 and macd_val < 0:
|
||||
if abs(pmacd) > 15 and macd_val >= -0.5:
|
||||
k_macd = 3.5
|
||||
elif 5 < abs(pmacd) < 15 and macd_val > 0.7:
|
||||
k_macd = 3.5
|
||||
elif pmacd > 0 and macd_val > 0:
|
||||
if pmacd >= 15 and macd_val < 0.5:
|
||||
k_macd = -3.5
|
||||
elif 5 < pmacd < 15 and macd_val < -0.7:
|
||||
k_macd = -3.5
|
||||
elif macd_val >= 3:
|
||||
k_macd = macd_val * 1.5
|
||||
elif macd_val <= -3:
|
||||
k_macd = macd_val * 1.5
|
||||
return k_macd
|
||||
|
||||
def _k_di_factor(self, dmk: float, amplitude: float, close: float) -> float:
|
||||
if len(self._close_buf) < 20:
|
||||
return 0.0
|
||||
k_close = np.array(self._close_buf, dtype=float)
|
||||
period = 14
|
||||
if len(k_close) < period + 1:
|
||||
return 0.0
|
||||
dx_list = []
|
||||
for i in range(1, min(period + 1, len(k_close))):
|
||||
diff = k_close[-i] - k_close[-i - 1]
|
||||
dx_list.append(diff)
|
||||
if not dx_list:
|
||||
return 0.0
|
||||
last_diff = dx_list[0]
|
||||
adx_val = abs(dmk) * 0.5
|
||||
|
||||
k_di = 0.0
|
||||
if adx_val < 20:
|
||||
return 0.0
|
||||
if dmk > 0 and len(dx_list) > 5:
|
||||
gains = [d for d in dx_list[:5] if d > 0]
|
||||
if len(gains) >= 3 and last_diff > 0:
|
||||
k_di = min(adx_val / 5, 6.0)
|
||||
elif dmk < 0 and len(dx_list) > 5:
|
||||
losses = [d for d in dx_list[:5] if d < 0]
|
||||
if len(losses) >= 3 and last_diff < 0:
|
||||
k_di = -min(adx_val / 5, 6.0)
|
||||
return k_di
|
||||
|
||||
def _k_aroon_factor(self, high: float, low: float, close: float) -> float:
|
||||
period = 14
|
||||
if len(self._high_buf) < period:
|
||||
return 0.0
|
||||
highs = self._high_buf[-period:]
|
||||
lows = self._low_buf[-period:]
|
||||
highest = max(highs)
|
||||
lowest = min(lows)
|
||||
rng = highest - lowest
|
||||
if rng == 0:
|
||||
return 0.0
|
||||
k_aroon = 0.0
|
||||
pct = (close - lowest) / rng * 100
|
||||
if pct >= self.aroon_ob:
|
||||
if close >= highest * 0.995:
|
||||
k_aroon = -2.0
|
||||
else:
|
||||
k_aroon = 2.0
|
||||
elif pct <= self.aroon_os:
|
||||
if close <= lowest * 1.005:
|
||||
k_aroon = 2.0
|
||||
else:
|
||||
k_aroon = -2.0
|
||||
else:
|
||||
k_aroon = (pct - 50) / 50 * 1.2
|
||||
return k_aroon
|
||||
|
||||
def _k_williams_factor(self, wr: float) -> float:
|
||||
k_wr = 0.0
|
||||
if wr > self.williams_ob:
|
||||
if wr > -10:
|
||||
k_wr = -1.2
|
||||
else:
|
||||
k_wr = -0.8
|
||||
elif wr < self.williams_os:
|
||||
if wr < -90:
|
||||
k_wr = 1.2
|
||||
else:
|
||||
k_wr = 0.8
|
||||
return k_wr
|
||||
|
||||
def _k_result_factor(self, result: float) -> float:
|
||||
k_result = 0.0
|
||||
is_ob = result > self.ob_threshold
|
||||
is_os = result < self.os_threshold
|
||||
if is_os and result < 10:
|
||||
k_result = 1.5
|
||||
elif is_ob and result > 90:
|
||||
k_result = -1.5
|
||||
return k_result
|
||||
|
||||
def _k_bias_factor(self, bias: float) -> float:
|
||||
k_bias = 0.0
|
||||
if bias < self.bias_os and bias < -3:
|
||||
k_bias = min(abs(bias) / 5, 2.5)
|
||||
elif bias > self.bias_ob and bias > 3:
|
||||
k_bias = -min(abs(bias) / 5, 2.5)
|
||||
return k_bias
|
||||
|
||||
def _k_trix_factor(self, close: float) -> float:
|
||||
ema3 = self._ema_val(self._close_buf, 3)
|
||||
ema9 = self._ema_val(self._close_buf, 9)
|
||||
if ema9 == 0:
|
||||
return 0.0
|
||||
trix = (ema3 - ema9) / ema9 * 100
|
||||
k_trix = 0.0
|
||||
if trix > 0:
|
||||
k_trix = min(trix / 2, 3.0)
|
||||
elif trix < 0:
|
||||
k_trix = max(trix / 2, -3.0)
|
||||
return k_trix
|
||||
|
||||
def _k_ema_factor(self, close: float) -> float:
|
||||
ema9 = self._ema_val(self._close_buf, 9)
|
||||
if ema9 == 0:
|
||||
return 0.0
|
||||
bias = (close - ema9) / ema9 * 100
|
||||
k_ema = 0.0
|
||||
if bias < -2:
|
||||
k_ema = min(abs(bias) * 0.3, 2.0)
|
||||
elif bias > 2:
|
||||
k_ema = -min(abs(bias) * 0.3, 2.0)
|
||||
return k_ema
|
||||
|
||||
def _k_amplitude_factor(self, amplitude: float) -> float:
|
||||
if len(self._amplitude_buf) < 10:
|
||||
return 0.0
|
||||
buf = self._amplitude_buf[-10:]
|
||||
mean = sum(buf) / len(buf)
|
||||
if mean == 0:
|
||||
return 0.0
|
||||
ratio = (amplitude - mean) / mean
|
||||
k_amp = 0.0
|
||||
if ratio > 0.5:
|
||||
k_amp = min(ratio * 1.5, 3.0)
|
||||
elif ratio < -0.3:
|
||||
k_amp = max(ratio * 1.5, -3.0)
|
||||
return k_amp
|
||||
|
||||
def _detect_divergence(self, close: float, result: float) -> Tuple[bool, bool]:
|
||||
"""简化背离检测(基于累计计数)"""
|
||||
is_bull_div = False
|
||||
is_bear_div = False
|
||||
if len(self._close_buf) > 30:
|
||||
c30 = self._close_buf[-30]
|
||||
if close > c30 * 1.05 and result < 50:
|
||||
is_bull_div = True
|
||||
elif close < c30 * 0.95 and result > 50:
|
||||
is_bear_div = True
|
||||
return is_bull_div, is_bear_div
|
||||
|
||||
def _detect_trend(self, close: float) -> Tuple[bool, bool]:
|
||||
"""简化趋势确认"""
|
||||
is_bull = False
|
||||
is_bear = False
|
||||
if len(self._close_buf) > 20:
|
||||
c20 = self._close_buf[-20]
|
||||
if close > c20 * 1.05:
|
||||
is_bull = True
|
||||
elif close < c20 * 0.95:
|
||||
is_bear = True
|
||||
return is_bull, is_bear
|
||||
|
||||
def _wmacd(self) -> float:
|
||||
"""简化加权MACD(基于缓冲区)"""
|
||||
if len(self._close_buf) < 12:
|
||||
return 0.0
|
||||
ema12 = self._ema_val(self._close_buf, 12)
|
||||
ema26 = self._ema_val(self._close_buf, 26)
|
||||
return ema12 - ema26
|
||||
|
||||
def _boll_mid(self) -> float:
|
||||
if len(self._close_buf) < 20:
|
||||
return self._close_buf[-1] if self._close_buf else 0.0
|
||||
return sum(self._close_buf[-20:]) / 20
|
||||
|
||||
def _ema_bias(self) -> float:
|
||||
if not self._close_buf:
|
||||
return 0.0
|
||||
ema5 = self._ema_val(self._close_buf, 5)
|
||||
ema10 = self._ema_val(self._close_buf, 10)
|
||||
if ema10 == 0:
|
||||
return 0.0
|
||||
return (ema5 - ema10) / ema10 * 1000
|
||||
|
||||
def _boll_pct_b(self, close: float, upper: float, lower: float) -> float:
|
||||
boll_len = upper - lower
|
||||
if boll_len == 0:
|
||||
return 50.0
|
||||
return (close - lower) / boll_len * 100
|
||||
|
||||
|
||||
def get_signals(indicator: Indicator) -> Tuple[str, str, str, str]:
|
||||
"""
|
||||
基于技术指标生成交易信号(逐帧调用版)
|
||||
|
||||
参数:
|
||||
indicator: Indicator 对象,包含所有实时指标值
|
||||
|
||||
返回:
|
||||
tuple: (market_status, signal_type, intensity, detail)
|
||||
"""
|
||||
# ─── 指标提取 ───
|
||||
bbi = indicator.BBI
|
||||
amplitude = indicator.AMPLITUDE
|
||||
cci = indicator.CCI
|
||||
close = indicator.CLOSE
|
||||
dmk = indicator.DMK
|
||||
high = indicator.HIGH
|
||||
k = indicator.K
|
||||
low = indicator.LOW
|
||||
macd_val = indicator.MACD
|
||||
ob = indicator.OB
|
||||
os_ = indicator.OS
|
||||
ovs = indicator.OVS
|
||||
ovc = indicator.OVC
|
||||
result = indicator.RESULT
|
||||
wr = indicator.WR
|
||||
percent = indicator.PERCENT
|
||||
v0 = indicator.V0
|
||||
boll_upper = indicator.BOLL_UP
|
||||
boll_lower = indicator.BOLL_LO
|
||||
bias = indicator.BIAS
|
||||
|
||||
# ─── 指标阈值 ───
|
||||
ob_threshold = ob if ob > 0 else 80
|
||||
os_threshold = os_ if os_ > 0 else 20
|
||||
dist_extreme = ovc if ovc > 0 else 5
|
||||
dist_far = ovs if ovs > 0 else 15
|
||||
dist_mid = 30
|
||||
macd_th = 0.3
|
||||
rsi_ob = ovc if ovc > 0 else 70
|
||||
rsi_os = ovs if ovs > 0 else 30
|
||||
di_gap_bull = ovc if ovc > 0 else 15
|
||||
di_gap_bear = -di_gap_bull
|
||||
aroon_ob = ob_threshold
|
||||
aroon_os = os_threshold
|
||||
williams_ob = -20
|
||||
williams_os = -80
|
||||
bias_ob = 5
|
||||
bias_os = -5
|
||||
|
||||
# ─── 指标因子 ───
|
||||
pmacd = getattr(indicator, 'PMACD', macd_val)
|
||||
|
||||
k_macd = _k_macd_factor(ob_threshold, os_threshold, k, macd_val, pmacd)
|
||||
k_di = _k_di_factor(dmk, amplitude, close)
|
||||
k_aroon = _k_aroon_factor(aroon_ob, aroon_os, high, low, close)
|
||||
k_williams = _k_williams_factor(williams_ob, williams_os, wr)
|
||||
k_result = _k_result_factor(ob_threshold, os_threshold, result)
|
||||
k_bias = _k_bias_factor(bias_ob, bias_os, bias)
|
||||
|
||||
# ─── K值累计 ───
|
||||
k_bull = k_macd + k_di + k_aroon + k_williams + k_result + k_bias
|
||||
k_bear = k_macd + k_di + k_aroon + k_williams + k_result + k_bias
|
||||
|
||||
if result > ob_threshold and k_bull > 0 and result >= 90:
|
||||
k_bull *= 1.5
|
||||
|
||||
# ─── 强度计算 ───
|
||||
max_k = max(abs(k_bull), abs(k_bear))
|
||||
if max_k > 0:
|
||||
buy_score = ((k_bull + max_k) / (2 * max_k)) * 100
|
||||
sell_score = ((k_bear + max_k) / (2 * max_k)) * 100
|
||||
else:
|
||||
buy_score = sell_score = 50
|
||||
|
||||
# ─── 信号判断 ───
|
||||
bull_t = buy_score > 70 and result < 90
|
||||
bear_t = sell_score > 70 and result > 10
|
||||
|
||||
k_cci = cci / 300
|
||||
boll_mid = indicator.BOLL_MID if hasattr(indicator, 'BOLL_MID') else (boll_upper + boll_lower) / 2
|
||||
boll_len = boll_upper - boll_lower
|
||||
|
||||
is_bull_t = (
|
||||
bull_t
|
||||
and k_bull > k_cci
|
||||
and close < boll_mid
|
||||
and (
|
||||
(result > 65 and result < 85 and k_bull > 0 and k_bear > 0 and (k_bear - k_bull < 1.5 or result > 75))
|
||||
or (result < 35 and result > 15 and k_bear > 0 and k_bull > 0 and (k_bull - k_bear < 1.5 or result < 25))
|
||||
)
|
||||
)
|
||||
is_bear_t = (
|
||||
bear_t
|
||||
and k_bear > k_cci
|
||||
and close > boll_mid
|
||||
and (
|
||||
(result > 65 and result < 85 and k_bull > 0 and k_bear > 0 and (k_bear - k_bull < 1.5 or result > 75))
|
||||
or (result < 35 and result > 15 and k_bear > 0 and k_bull > 0 and (k_bull - k_bear < 1.5 or result < 25))
|
||||
)
|
||||
)
|
||||
|
||||
# ─── 信号强度 ───
|
||||
sig_strength = 1.0
|
||||
if is_bull_t:
|
||||
sig_strength += 0.3 + max(0, (result - 70) / 30)
|
||||
elif is_bear_t:
|
||||
sig_strength += 0.3 + max(0, (30 - result) / 30)
|
||||
|
||||
strength = (
|
||||
"极强" if sig_strength >= 1.7
|
||||
else "强" if sig_strength >= 1.4
|
||||
else "中" if sig_strength >= 1.1
|
||||
else "弱"
|
||||
)
|
||||
|
||||
# ─── 信号类型 ───
|
||||
if is_bull_t:
|
||||
signal = f"买{strength}"
|
||||
elif is_bear_t:
|
||||
signal = f"卖{strength}"
|
||||
else:
|
||||
signal = "观望"
|
||||
|
||||
# ─── 市场状态 ───
|
||||
if abs(buy_score - sell_score) < 10:
|
||||
status = "中性震荡"
|
||||
elif buy_score > sell_score:
|
||||
if result > ob_threshold:
|
||||
status = "高位企稳"
|
||||
else:
|
||||
status = "温和上涨"
|
||||
else:
|
||||
if result < os_threshold:
|
||||
status = "低位企稳"
|
||||
else:
|
||||
status = "温和下跌"
|
||||
|
||||
# ─── 距离 ───
|
||||
if boll_len > 0:
|
||||
dist_ratio = (close - boll_lower) / boll_len * 100
|
||||
else:
|
||||
dist_ratio = 50
|
||||
|
||||
if dist_ratio < 50 - dist_extreme:
|
||||
dist = "极端超卖"
|
||||
elif dist_ratio < 50 - dist_far:
|
||||
dist = "远离"
|
||||
elif dist_ratio < 50 - dist_mid:
|
||||
dist = "偏离"
|
||||
elif dist_ratio < 50 + dist_mid:
|
||||
dist = "接近"
|
||||
elif dist_ratio < 50 + dist_far:
|
||||
dist = "靠近"
|
||||
elif dist_ratio < 50 + dist_extreme:
|
||||
dist = "远超"
|
||||
else:
|
||||
dist = "极端超买"
|
||||
|
||||
# ─── 强度标签 ───
|
||||
max_score = max(buy_score, sell_score)
|
||||
intensity = (
|
||||
"超强" if max_score >= 90
|
||||
else "强" if max_score >= 80
|
||||
else "中" if max_score >= 65
|
||||
else "弱" if max_score >= 55
|
||||
else "极弱"
|
||||
)
|
||||
|
||||
# ─── 详情 ───
|
||||
ema5 = indicator.EMA5 if hasattr(indicator, 'EMA5') else close
|
||||
ema10 = indicator.EMA10 if hasattr(indicator, 'EMA10') else close
|
||||
ema20 = indicator.EMA20 if hasattr(indicator, 'EMA20') else close
|
||||
bias_val = (ema5 - ema10) / ema10 * 1000 if ema10 != 0 else 0
|
||||
|
||||
bbp_val = (close - boll_lower) / boll_len * 100 if boll_len > 0 else 50
|
||||
|
||||
detail = (
|
||||
f"前量:{percent:.1f} 数量:{int(amplitude):03d} 百分比:{bbp_val:.1f} "
|
||||
f"正:{k_bull:.1f} 负:{k_bear:.1f}"
|
||||
)
|
||||
|
||||
return status, signal, intensity, detail
|
||||
@@ -6,17 +6,21 @@
|
||||
import { fly } from "svelte/transition";
|
||||
import { DEFAULT_PRESSURE_RANGE_MAX, DEFAULT_PRESSURE_RANGE_MIN } from "$lib/config/pressure-range";
|
||||
import ConfigPanel from "$lib/components/ConfigPanel.svelte";
|
||||
import ModelStage from "$lib/components/ModelStage.svelte";
|
||||
import NeonBreakoutArena from "$lib/components/NeonBreakoutArena.svelte";
|
||||
import PressureMatrixViewer from "$lib/components/PressureMatrixViewer.svelte";
|
||||
import SignalChart from "$lib/components/SignalChart.svelte";
|
||||
import SpatialForcePanel from "$lib/components/SpatialForcePanel.svelte";
|
||||
import SummaryCurve from "$lib/components/SummaryCurve.svelte";
|
||||
import type {
|
||||
HudColorMapOption,
|
||||
HudSignalPanel,
|
||||
HudSpatialForce,
|
||||
HudSummary,
|
||||
LocaleCode,
|
||||
MatrixDisplayMode,
|
||||
PressureColorMapPreset
|
||||
PressureColorMapPreset,
|
||||
StageViewMode
|
||||
} from "$lib/types/hud";
|
||||
|
||||
export let locale: LocaleCode = "zh-CN";
|
||||
@@ -24,6 +28,7 @@
|
||||
export let rightPanels: HudSignalPanel[] = [];
|
||||
export let summary: HudSummary;
|
||||
export let pressureMatrix: number[] | null = null;
|
||||
export let spatialForce: HudSpatialForce | null = null;
|
||||
export let showConfigPanel = false;
|
||||
export let configPanelTitle = "";
|
||||
export let configPanelHint = "";
|
||||
@@ -41,6 +46,8 @@
|
||||
export let rangeMax = DEFAULT_PRESSURE_RANGE_MAX;
|
||||
export let colorMapPreset: PressureColorMapPreset = "emerald";
|
||||
export let matrixDisplayMode: MatrixDisplayMode = "dots";
|
||||
export let stageViewMode: StageViewMode = "webgl";
|
||||
export let modelUrl = "/models/je-skin-model.glb";
|
||||
export let replaySectionLabel = "";
|
||||
export let replayPlayLabel = "";
|
||||
export let replayPauseLabel = "";
|
||||
@@ -53,8 +60,9 @@
|
||||
export let replayProgress = 0;
|
||||
export let replayFileName = "";
|
||||
export let replayFrameInfo = "";
|
||||
export let showPrecisionTestPanel = false;
|
||||
export let sessionStartedAt: number = Date.now();
|
||||
export let summaryReleasePending = false;
|
||||
export let spatialForcePanelVisible = false;
|
||||
|
||||
let stagePlaneEl: HTMLDivElement | undefined;
|
||||
let panelZoneEl: HTMLDivElement | undefined;
|
||||
@@ -81,9 +89,10 @@
|
||||
$: replaySide = summarySide === "left" ? "right" : "left";
|
||||
$: replayToggleButtonText = replayIsPlaying ? replayPauseLabel : replayPlayLabel;
|
||||
$: replayProgressPercent = Math.round(Math.min(1, Math.max(0, replayProgress)) * 100);
|
||||
$: summaryCurveVisible = summary.points.length > 0 && summary.points.some((value) => Number.isFinite(value) && Math.abs(value) >= 0.0001);
|
||||
$: splitMatrixTitle = locale === "zh-CN" ? "数字矩阵" : "Matrix";
|
||||
$: splitMatrixHint = locale === "zh-CN" ? "实时压力数据 / 数字矩阵" : "Live pressure matrix";
|
||||
$: summaryCurveVisible =
|
||||
summary.points.length > 0 &&
|
||||
(summaryReleasePending || summary.points.some((value) => Number.isFinite(value) && Math.abs(value) >= 0.0001));
|
||||
$: isModelStage = stageViewMode === "model3d";
|
||||
|
||||
function toPxNumber(rawValue: string): number {
|
||||
const value = Number.parseFloat(rawValue);
|
||||
@@ -108,7 +117,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
panelZoneTopPx = showPrecisionTestPanel ? 24 : 16;
|
||||
panelZoneTopPx = 16;
|
||||
|
||||
const panelZoneBottomPx = panelZoneEl ? toPxNumber(getComputedStyle(panelZoneEl).bottom) : 0;
|
||||
const zoneHeight = Math.max(0, stagePlaneEl.clientHeight - panelZoneTopPx - panelZoneBottomPx);
|
||||
@@ -176,42 +185,11 @@
|
||||
bind:this={stagePlaneEl}
|
||||
style="--panel-zone-top-dyn: {panelZoneTopPx}px; --rail-scale-left: {leftRailScale}; --rail-scale-right: {rightRailScale};"
|
||||
>
|
||||
{#if showPrecisionTestPanel}
|
||||
<div class="split-game-wrap">
|
||||
<section class="split-panel split-matrix-panel">
|
||||
<header class="split-panel-head">
|
||||
<p>{splitMatrixTitle}</p>
|
||||
<span>{splitMatrixHint}</span>
|
||||
</header>
|
||||
<div class="split-panel-body">
|
||||
{#key `${matrixRows}x${matrixCols}:${colorMapPreset}:split`}
|
||||
<PressureMatrixViewer
|
||||
{summary}
|
||||
{pressureMatrix}
|
||||
{matrixRows}
|
||||
{matrixCols}
|
||||
{rangeMin}
|
||||
{rangeMax}
|
||||
{colorMapPreset}
|
||||
{matrixDisplayMode}
|
||||
{locale}
|
||||
showStatsPanel={true}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="split-panel split-breakout-panel">
|
||||
<NeonBreakoutArena
|
||||
{locale}
|
||||
{pressureMatrix}
|
||||
{matrixRows}
|
||||
{matrixCols}
|
||||
{rangeMin}
|
||||
{rangeMax}
|
||||
{colorMapPreset}
|
||||
/>
|
||||
</section>
|
||||
{#if isModelStage}
|
||||
<div class="canvas-wrap">
|
||||
{#key modelUrl}
|
||||
<ModelStage {locale} {modelUrl} />
|
||||
{/key}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="canvas-wrap">
|
||||
@@ -232,7 +210,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showConfigPanel && !showPrecisionTestPanel}
|
||||
{#if showConfigPanel && !isModelStage}
|
||||
<div class="config-panel-wrap">
|
||||
<ConfigPanel
|
||||
bind:matrixRows
|
||||
@@ -254,7 +232,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !showPrecisionTestPanel}
|
||||
{#if !isModelStage}
|
||||
<div class="panel-zone" bind:this={panelZoneEl}>
|
||||
<aside class="side-rail left-rail">
|
||||
<div class="rail-stack" bind:this={leftStackEl}>
|
||||
@@ -302,6 +280,24 @@
|
||||
<SignalChart {panel} panelIndex={index} {locale} />
|
||||
</div>
|
||||
{/each}
|
||||
{#if spatialForcePanelVisible}
|
||||
<div
|
||||
class="panel-motion-shell"
|
||||
in:fly={{ x: 180, duration: 340, opacity: 0.08, easing: cubicOut }}
|
||||
out:fly={{ x: 180, duration: 280, opacity: 0.06, easing: cubicIn }}
|
||||
>
|
||||
<SpatialForcePanel
|
||||
{spatialForce}
|
||||
{locale}
|
||||
side="right"
|
||||
panelIndex={rightPanels.length}
|
||||
panelCode="3D"
|
||||
panelTitle={locale === "zh-CN" ? "三维力" : "3D Force"}
|
||||
badgeLabel=""
|
||||
badgeTone="lime"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if summaryCurveVisible && summarySide === "right"}
|
||||
<div
|
||||
@@ -317,7 +313,7 @@
|
||||
{sessionStartedAt}
|
||||
isRealtime={!replayHasData}
|
||||
side="right"
|
||||
panelIndex={rightPanels.length}
|
||||
panelIndex={rightPanels.length + (spatialForcePanelVisible ? 1 : 0)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -326,7 +322,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if replayHasData && !showPrecisionTestPanel}
|
||||
{#if replayHasData && !isModelStage}
|
||||
<aside class="replay-floating-panel" class:is-left={replaySide === "left"} class:is-right={replaySide === "right"}>
|
||||
<div class="replay-panel-head">
|
||||
<div class="replay-panel-title-group">
|
||||
@@ -364,7 +360,7 @@
|
||||
</aside>
|
||||
{/if}
|
||||
|
||||
{#if !showPrecisionTestPanel}
|
||||
{#if !isModelStage}
|
||||
<div class="stage-bottom-overlay">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type {
|
||||
ConnectionState,
|
||||
HudConfigLink,
|
||||
HudNoticeTone,
|
||||
LocaleCode,
|
||||
MatrixDisplayMode,
|
||||
StageViewMode,
|
||||
WindowControlAction
|
||||
} from "$lib/types/hud";
|
||||
|
||||
@@ -28,12 +27,9 @@
|
||||
export let serialPortValue = "";
|
||||
export let serialPortOptions: string[] = [];
|
||||
export let refreshPortsLabel = "";
|
||||
export let configLinksLabel = "";
|
||||
export let configLinks: HudConfigLink[] = [];
|
||||
export let matrixViewLabel = "";
|
||||
export let matrixViewNumericLabel = "";
|
||||
export let matrixViewDotsLabel = "";
|
||||
export let matrixDisplayMode: MatrixDisplayMode = "dots";
|
||||
export let stageViewMode: StageViewMode = "webgl";
|
||||
export let connectActionLabel = "";
|
||||
export let disconnectActionLabel = "";
|
||||
export let exportActionLabel = "";
|
||||
@@ -54,8 +50,8 @@
|
||||
const dispatch = createEventDispatcher<{
|
||||
windowcontrol: WindowControlAction;
|
||||
localechange: LocaleCode;
|
||||
configlink: string;
|
||||
matrixdisplaytoggle: boolean;
|
||||
stagemodechange: StageViewMode;
|
||||
portchange: string;
|
||||
serialrefresh: void;
|
||||
serialconnect: string;
|
||||
@@ -97,12 +93,8 @@
|
||||
dispatch("localechange", nextLocale);
|
||||
}
|
||||
|
||||
function emitConfigLink(linkId: string): void {
|
||||
dispatch("configlink", linkId);
|
||||
}
|
||||
|
||||
function emitMatrixDisplayToggle(): void {
|
||||
dispatch("matrixdisplaytoggle", matrixDisplayMode !== "dots");
|
||||
function emitStageModeChange(nextMode: StageViewMode): void {
|
||||
dispatch("stagemodechange", nextMode);
|
||||
}
|
||||
|
||||
function emitPortChange(event: Event): void {
|
||||
@@ -185,38 +177,6 @@
|
||||
|
||||
<div class="control-bar">
|
||||
<div class="control-main-row">
|
||||
<section class="config-links" aria-label={configLinksLabel}>
|
||||
{#each configLinks as link (link.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="config-link tone-{link.tone ?? 'neutral'}"
|
||||
class:is-active={Boolean(link.active)}
|
||||
on:click={() => emitConfigLink(link.id)}
|
||||
>
|
||||
<span class="config-indicator" aria-hidden="true"></span>
|
||||
<span class="config-label">{link.label}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</section>
|
||||
|
||||
<section class="matrix-switch-wrap" aria-label={matrixViewLabel}>
|
||||
<span class="matrix-switch-label">{matrixViewLabel}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="matrix-switch-btn"
|
||||
class:is-active={matrixDisplayMode === "dots"}
|
||||
role="switch"
|
||||
aria-checked={matrixDisplayMode === "dots"}
|
||||
aria-label={matrixViewDotsLabel}
|
||||
on:click={emitMatrixDisplayToggle}
|
||||
>
|
||||
<span class="matrix-switch-track" aria-hidden="true">
|
||||
<span class="matrix-switch-thumb"></span>
|
||||
</span>
|
||||
<span class="matrix-switch-copy">{matrixDisplayMode === "dots" ? matrixViewDotsLabel : matrixViewNumericLabel}</span>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section class="state-card" aria-label={connectionLabel}>
|
||||
<span class="state-dot" class:ok={connectionTone === "ok"} class:warn={connectionTone === "warn"}></span>
|
||||
<span class="state-label">{connectionLabel}</span>
|
||||
@@ -485,108 +445,6 @@
|
||||
background: var(--panel-surface);
|
||||
}
|
||||
|
||||
.matrix-switch-wrap {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
min-block-size: 2rem;
|
||||
border: 1px solid var(--panel-line);
|
||||
border-radius: 999px;
|
||||
padding: 0.16rem 0.22rem 0.16rem 0.56rem;
|
||||
background: var(--panel-surface);
|
||||
}
|
||||
|
||||
.matrix-switch-label {
|
||||
color: var(--panel-text-dim);
|
||||
font-size: 0.66rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.matrix-switch-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.42rem;
|
||||
min-block-size: 1.62rem;
|
||||
border: 1px solid rgb(var(--hud-border-rgb) / 0.26);
|
||||
border-radius: 999px;
|
||||
padding: 0.18rem 0.28rem 0.18rem 0.22rem;
|
||||
background: rgb(var(--hud-surface-deep-rgb) / 0.84);
|
||||
color: rgb(var(--hud-text-main-rgb) / 0.92);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 180ms ease,
|
||||
box-shadow 180ms ease,
|
||||
background-color 180ms ease,
|
||||
color 180ms ease;
|
||||
}
|
||||
|
||||
.matrix-switch-btn:hover {
|
||||
border-color: rgb(var(--hud-cyan-rgb) / 0.4);
|
||||
}
|
||||
|
||||
.matrix-switch-btn.is-active {
|
||||
border-color: rgb(var(--hud-cyan-rgb) / 0.5);
|
||||
background:
|
||||
linear-gradient(180deg, rgb(var(--hud-surface-alt-rgb) / 0.94), rgb(var(--hud-surface-rgb) / 0.9)),
|
||||
radial-gradient(circle at 50% 0, rgb(var(--hud-cyan-rgb) / 0.12), transparent 60%);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgb(var(--hud-text-main-rgb) / 0.05),
|
||||
0 0 12px rgb(var(--hud-cyan-rgb) / 0.12);
|
||||
}
|
||||
|
||||
.matrix-switch-track {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
inline-size: 2.2rem;
|
||||
block-size: 1.2rem;
|
||||
border-radius: 999px;
|
||||
padding: 0.14rem;
|
||||
background: rgb(var(--hud-surface-rgb) / 0.9);
|
||||
box-shadow: inset 0 0 0 1px rgb(var(--hud-border-rgb) / 0.24);
|
||||
transition:
|
||||
background-color 180ms ease,
|
||||
box-shadow 180ms ease;
|
||||
}
|
||||
|
||||
.matrix-switch-btn.is-active .matrix-switch-track {
|
||||
background: rgb(var(--hud-cyan-rgb) / 0.18);
|
||||
box-shadow: inset 0 0 0 1px rgb(var(--hud-cyan-rgb) / 0.18);
|
||||
}
|
||||
|
||||
.matrix-switch-thumb {
|
||||
inline-size: 0.92rem;
|
||||
block-size: 0.92rem;
|
||||
border-radius: 50%;
|
||||
background: rgb(var(--hud-text-main-rgb) / 0.96);
|
||||
box-shadow:
|
||||
0 1px 4px rgb(0 0 0 / 0.26),
|
||||
0 0 10px rgb(var(--hud-text-main-rgb) / 0.12);
|
||||
transform: translateX(0);
|
||||
transition:
|
||||
transform 180ms ease,
|
||||
background-color 180ms ease,
|
||||
box-shadow 180ms ease;
|
||||
}
|
||||
|
||||
.matrix-switch-btn.is-active .matrix-switch-thumb {
|
||||
transform: translateX(0.96rem);
|
||||
background: rgb(var(--hud-cyan-rgb) / 0.96);
|
||||
box-shadow:
|
||||
0 1px 4px rgb(0 0 0 / 0.26),
|
||||
0 0 12px rgb(var(--hud-cyan-rgb) / 0.22);
|
||||
}
|
||||
|
||||
.matrix-switch-copy {
|
||||
font-size: 0.74rem;
|
||||
letter-spacing: 0.04em;
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.state-dot {
|
||||
inline-size: 0.55rem;
|
||||
block-size: 0.55rem;
|
||||
@@ -1050,93 +908,6 @@
|
||||
background: rgb(10 16 20 / 0.7);
|
||||
}
|
||||
|
||||
.config-links {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
min-block-size: 2rem;
|
||||
border: 1px solid rgb(95 132 158 / 0.36);
|
||||
border-radius: 999px;
|
||||
padding: 0.17rem 0.2rem;
|
||||
background: linear-gradient(180deg, rgb(9 15 19 / 0.9), rgb(4 8 12 / 0.86));
|
||||
box-shadow: inset 0 0 0 1px rgb(140 184 210 / 0.06);
|
||||
}
|
||||
|
||||
.config-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.34rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 999px;
|
||||
padding: 0.26rem 0.64rem;
|
||||
background: transparent;
|
||||
color: rgb(164 188 208 / 0.9);
|
||||
font-size: 0.81rem;
|
||||
letter-spacing: 0.05em;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
color 180ms ease,
|
||||
border-color 180ms ease,
|
||||
background-color 180ms ease,
|
||||
box-shadow 220ms ease;
|
||||
}
|
||||
|
||||
.config-indicator {
|
||||
inline-size: 0.34rem;
|
||||
block-size: 0.34rem;
|
||||
border-radius: 999px;
|
||||
background: rgb(136 157 174 / 0.88);
|
||||
box-shadow: 0 0 0 2px rgb(136 157 174 / 0.16);
|
||||
transition:
|
||||
background-color 180ms ease,
|
||||
box-shadow 200ms ease;
|
||||
}
|
||||
|
||||
.config-label {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.config-link:hover {
|
||||
color: #d7edfb;
|
||||
border-color: rgb(62 232 255 / 0.26);
|
||||
}
|
||||
|
||||
.config-link.is-active {
|
||||
color: #f1fdff;
|
||||
border-color: rgb(106 150 180 / 0.56);
|
||||
background: rgb(18 27 35 / 0.9);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgb(167 218 252 / 0.08),
|
||||
0 0 10px rgb(62 232 255 / 0.12);
|
||||
}
|
||||
|
||||
.config-link.tone-cyan.is-active {
|
||||
border-color: rgb(62 232 255 / 0.48);
|
||||
}
|
||||
|
||||
.config-link.tone-lime.is-active {
|
||||
border-color: rgb(133 255 68 / 0.52);
|
||||
}
|
||||
|
||||
.config-link.tone-orange.is-active {
|
||||
border-color: rgb(255 91 63 / 0.52);
|
||||
}
|
||||
|
||||
.config-link.tone-cyan.is-active .config-indicator {
|
||||
background: var(--hud-cyan);
|
||||
box-shadow: 0 0 0 2px rgb(62 232 255 / 0.17);
|
||||
}
|
||||
|
||||
.config-link.tone-lime.is-active .config-indicator {
|
||||
background: var(--hud-lime);
|
||||
box-shadow: 0 0 0 2px rgb(133 255 68 / 0.17);
|
||||
}
|
||||
|
||||
.config-link.tone-orange.is-active .config-indicator {
|
||||
background: var(--hud-orange);
|
||||
box-shadow: 0 0 0 2px rgb(255 91 63 / 0.18);
|
||||
}
|
||||
|
||||
.locale-btn {
|
||||
border: 1px solid transparent;
|
||||
border-radius: 999px;
|
||||
@@ -1163,21 +934,11 @@
|
||||
background: rgb(24 31 25 / 0.9);
|
||||
}
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
.config-links {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.control-main-row {
|
||||
gap: 0.44rem;
|
||||
}
|
||||
|
||||
.config-links {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.serial-select {
|
||||
padding-inline-start: 0.45rem;
|
||||
}
|
||||
@@ -1216,4 +977,4 @@
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
469
src/lib/components/ModelStage.svelte
Normal file
469
src/lib/components/ModelStage.svelte
Normal file
@@ -0,0 +1,469 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import * as THREE from "three";
|
||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
||||
import type { GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
|
||||
import type { LocaleCode } from "$lib/types/hud";
|
||||
|
||||
type ModelLoadState = "loading" | "ready" | "missing" | "error";
|
||||
|
||||
export let locale: LocaleCode = "zh-CN";
|
||||
export let modelUrl = "/models/je-skin-model.gltf";
|
||||
|
||||
let rootEl: HTMLDivElement | undefined;
|
||||
let canvasEl: HTMLCanvasElement | undefined;
|
||||
let loadState: ModelLoadState = "loading";
|
||||
let loadProgress = 0;
|
||||
let loadError = "";
|
||||
|
||||
const FLOOR_Y = -1.15;
|
||||
const MODEL_FLOOR_CLEARANCE = 0.035;
|
||||
const MODEL_TARGET_HEIGHT = 8.4;
|
||||
const MODEL_MIN_SCALE = 0.02;
|
||||
const MODEL_MAX_SCALE = 80;
|
||||
const CAMERA_DISTANCE_FACTOR = 1.35;
|
||||
const CAMERA_DISTANCE_MIN = 7.5;
|
||||
const CAMERA_DISTANCE_MAX = 24;
|
||||
|
||||
$: copy =
|
||||
locale === "zh-CN"
|
||||
? {
|
||||
title: "3D 模型舱",
|
||||
subtitle: "Dark Grid / Future Lab",
|
||||
loading: "正在加载模型",
|
||||
ready: "模型已载入",
|
||||
missing: "等待模型文件",
|
||||
error: "模型加载失败",
|
||||
modelPath: "模型路径",
|
||||
hint: "请使用 glTF 2.0 的 .glb/.gltf;旧版 glTF 1.0 需要先转换"
|
||||
}
|
||||
: {
|
||||
title: "3D Model Bay",
|
||||
subtitle: "Dark Grid / Future Lab",
|
||||
loading: "Loading model",
|
||||
ready: "Model loaded",
|
||||
missing: "Waiting for model file",
|
||||
error: "Model load failed",
|
||||
modelPath: "Model path",
|
||||
hint: "Use glTF 2.0 .glb/.gltf assets; older glTF 1.0 files need conversion first"
|
||||
};
|
||||
$: statusText =
|
||||
loadState === "ready"
|
||||
? copy.ready
|
||||
: loadState === "missing"
|
||||
? copy.missing
|
||||
: loadState === "error"
|
||||
? copy.error
|
||||
: `${copy.loading} ${Math.round(loadProgress)}%`;
|
||||
|
||||
function disposeObject3D(object: THREE.Object3D): void {
|
||||
object.traverse((child) => {
|
||||
const mesh = child as THREE.Mesh;
|
||||
if (mesh.geometry) {
|
||||
mesh.geometry.dispose();
|
||||
}
|
||||
|
||||
const material = mesh.material;
|
||||
if (Array.isArray(material)) {
|
||||
for (const item of material) {
|
||||
item.dispose();
|
||||
}
|
||||
} else if (material) {
|
||||
material.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildPlaceholderModel(): THREE.Group {
|
||||
const group = new THREE.Group();
|
||||
const cyan = new THREE.Color(0x5ee7ff);
|
||||
const lime = new THREE.Color(0xa6ff7a);
|
||||
|
||||
const platform = new THREE.Mesh(
|
||||
new THREE.CylinderGeometry(5.8, 6.7, 0.36, 96),
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: 0x0c1824,
|
||||
emissive: 0x07131f,
|
||||
metalness: 0.62,
|
||||
roughness: 0.34
|
||||
})
|
||||
);
|
||||
platform.position.y = 0.18;
|
||||
group.add(platform);
|
||||
|
||||
const ringGeometry = new THREE.TorusGeometry(4.35, 0.035, 10, 128);
|
||||
const ringMaterial = new THREE.MeshBasicMaterial({ color: cyan, transparent: true, opacity: 0.78 });
|
||||
for (let index = 0; index < 3; index += 1) {
|
||||
const ring = new THREE.Mesh(ringGeometry, ringMaterial);
|
||||
ring.position.y = 0.52 + index * 0.52;
|
||||
ring.rotation.x = Math.PI / 2;
|
||||
group.add(ring);
|
||||
}
|
||||
|
||||
const coreMaterial = new THREE.MeshStandardMaterial({
|
||||
color: 0x1b2a38,
|
||||
emissive: 0x0a2632,
|
||||
metalness: 0.48,
|
||||
roughness: 0.42,
|
||||
transparent: true,
|
||||
opacity: 0.72
|
||||
});
|
||||
const core = new THREE.Mesh(new THREE.BoxGeometry(2.2, 3.4, 1.1), coreMaterial);
|
||||
core.position.y = 2.4;
|
||||
core.rotation.y = -0.36;
|
||||
group.add(core);
|
||||
|
||||
const sensorMaterial = new THREE.MeshBasicMaterial({ color: lime, transparent: true, opacity: 0.88 });
|
||||
for (let index = 0; index < 7; index += 1) {
|
||||
const bead = new THREE.Mesh(new THREE.SphereGeometry(0.13, 18, 18), sensorMaterial);
|
||||
bead.position.set(-0.72 + index * 0.24, 3.18 + Math.sin(index * 0.72) * 0.18, 0.6);
|
||||
group.add(bead);
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
function clamp(value: number, min: number, max: number): number {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
}
|
||||
|
||||
function normalizeObjectToStage(object: THREE.Object3D): THREE.Box3 {
|
||||
object.updateMatrixWorld(true);
|
||||
let bounds = new THREE.Box3().setFromObject(object);
|
||||
const size = bounds.getSize(new THREE.Vector3());
|
||||
const currentHeight = Math.max(size.y, 0.001);
|
||||
const scale = clamp(MODEL_TARGET_HEIGHT / currentHeight, MODEL_MIN_SCALE, MODEL_MAX_SCALE);
|
||||
|
||||
object.scale.multiplyScalar(scale);
|
||||
object.updateMatrixWorld(true);
|
||||
|
||||
bounds = new THREE.Box3().setFromObject(object);
|
||||
const center = bounds.getCenter(new THREE.Vector3());
|
||||
object.position.x -= center.x;
|
||||
object.position.z -= center.z;
|
||||
object.position.y += FLOOR_Y + MODEL_FLOOR_CLEARANCE - bounds.min.y;
|
||||
object.updateMatrixWorld(true);
|
||||
|
||||
return new THREE.Box3().setFromObject(object);
|
||||
}
|
||||
|
||||
function frameObject(object: THREE.Object3D, camera: THREE.PerspectiveCamera, controls: OrbitControls): void {
|
||||
const bounds = normalizeObjectToStage(object);
|
||||
const size = bounds.getSize(new THREE.Vector3());
|
||||
const maxAxis = Math.max(size.x, size.y, size.z, 1);
|
||||
const distance = clamp(maxAxis * CAMERA_DISTANCE_FACTOR, CAMERA_DISTANCE_MIN, CAMERA_DISTANCE_MAX);
|
||||
const targetY = FLOOR_Y + Math.max(size.y * 0.46, 1.4);
|
||||
|
||||
camera.position.set(distance * 0.48, targetY + distance * 0.24, distance * 0.68);
|
||||
camera.near = Math.max(distance / 80, 0.01);
|
||||
camera.far = distance * 24;
|
||||
camera.updateProjectionMatrix();
|
||||
controls.target.set(0, targetY, 0);
|
||||
controls.minDistance = Math.max(distance * 0.32, 2);
|
||||
controls.maxDistance = Math.max(distance * 2.5, 12);
|
||||
controls.update();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!rootEl || !canvasEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
canvas: canvasEl,
|
||||
antialias: true,
|
||||
alpha: true,
|
||||
powerPreference: "high-performance"
|
||||
});
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
||||
renderer.setClearColor(0x03070d, 1);
|
||||
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||
renderer.toneMappingExposure = 1.08;
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
scene.fog = new THREE.FogExp2(0x03070d, 0.028);
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(38, 1, 0.05, 600);
|
||||
camera.position.set(8, 6, 9);
|
||||
|
||||
const controls = new OrbitControls(camera, canvasEl);
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.08;
|
||||
controls.minDistance = 2.4;
|
||||
controls.maxDistance = 32;
|
||||
controls.target.set(0, FLOOR_Y + 3.2, 0);
|
||||
|
||||
const labGroup = new THREE.Group();
|
||||
scene.add(labGroup);
|
||||
|
||||
const grid = new THREE.GridHelper(42, 42, 0x63e6ff, 0x123047);
|
||||
grid.position.y = FLOOR_Y;
|
||||
const gridMaterial = grid.material;
|
||||
if (Array.isArray(gridMaterial)) {
|
||||
for (const material of gridMaterial) {
|
||||
material.transparent = true;
|
||||
material.opacity = 0.28;
|
||||
}
|
||||
} else {
|
||||
gridMaterial.transparent = true;
|
||||
gridMaterial.opacity = 0.28;
|
||||
}
|
||||
labGroup.add(grid);
|
||||
|
||||
const backGrid = new THREE.GridHelper(42, 42, 0x5ee7ff, 0x0c2436);
|
||||
backGrid.position.set(0, 9.5, -17);
|
||||
backGrid.rotation.x = Math.PI / 2;
|
||||
const backGridMaterial = backGrid.material;
|
||||
if (Array.isArray(backGridMaterial)) {
|
||||
for (const material of backGridMaterial) {
|
||||
material.transparent = true;
|
||||
material.opacity = 0.12;
|
||||
}
|
||||
} else {
|
||||
backGridMaterial.transparent = true;
|
||||
backGridMaterial.opacity = 0.12;
|
||||
}
|
||||
labGroup.add(backGrid);
|
||||
|
||||
const floor = new THREE.Mesh(
|
||||
new THREE.PlaneGeometry(42, 42),
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: 0x050c14,
|
||||
metalness: 0.28,
|
||||
roughness: 0.64,
|
||||
transparent: true,
|
||||
opacity: 0.72
|
||||
})
|
||||
);
|
||||
floor.rotation.x = -Math.PI / 2;
|
||||
floor.position.y = FLOOR_Y - 0.018;
|
||||
labGroup.add(floor);
|
||||
|
||||
const ambient = new THREE.AmbientLight(0x9fb8d0, 0.22);
|
||||
const keyLight = new THREE.DirectionalLight(0x7be7ff, 1.5);
|
||||
keyLight.position.set(8, 12, 8);
|
||||
const rimLight = new THREE.PointLight(0xa6ff7a, 26, 24, 2.1);
|
||||
rimLight.position.set(-4.5, 4.8, -3.6);
|
||||
const sideLight = new THREE.PointLight(0x5c8cff, 15, 28, 1.7);
|
||||
sideLight.position.set(5.8, 3.2, -5.4);
|
||||
scene.add(ambient, keyLight, rimLight, sideLight);
|
||||
|
||||
let activeModel: THREE.Object3D = buildPlaceholderModel();
|
||||
scene.add(activeModel);
|
||||
frameObject(activeModel, camera, controls);
|
||||
|
||||
const loader = new GLTFLoader();
|
||||
loader.load(
|
||||
modelUrl,
|
||||
(gltf: GLTF) => {
|
||||
scene.remove(activeModel);
|
||||
disposeObject3D(activeModel);
|
||||
activeModel = gltf.scene;
|
||||
activeModel.traverse((child) => {
|
||||
const mesh = child as THREE.Mesh;
|
||||
if (mesh.isMesh) {
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
}
|
||||
});
|
||||
scene.add(activeModel);
|
||||
frameObject(activeModel, camera, controls);
|
||||
loadState = "ready";
|
||||
loadProgress = 100;
|
||||
},
|
||||
(event) => {
|
||||
if (event.total > 0) {
|
||||
loadProgress = (event.loaded / event.total) * 100;
|
||||
} else {
|
||||
loadProgress = 12;
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
loadError = message || "Unknown model loader error";
|
||||
loadState = message.toLowerCase().includes("404") ? "missing" : "error";
|
||||
loadProgress = 0;
|
||||
}
|
||||
);
|
||||
|
||||
const resize = () => {
|
||||
if (!rootEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const width = rootEl.clientWidth;
|
||||
const height = rootEl.clientHeight;
|
||||
if (width <= 0 || height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderer.setSize(width, height, false);
|
||||
camera.aspect = width / height;
|
||||
camera.updateProjectionMatrix();
|
||||
};
|
||||
|
||||
resize();
|
||||
const resizeObserver = new ResizeObserver(resize);
|
||||
resizeObserver.observe(rootEl);
|
||||
|
||||
renderer.setAnimationLoop((timestamp) => {
|
||||
const seconds = timestamp / 1000;
|
||||
labGroup.position.y = Math.sin(seconds * 0.75) * 0.015;
|
||||
if (loadState !== "ready") {
|
||||
activeModel.rotation.y = seconds * 0.32;
|
||||
}
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
renderer.setAnimationLoop(null);
|
||||
controls.dispose();
|
||||
disposeObject3D(activeModel);
|
||||
disposeObject3D(labGroup);
|
||||
renderer.dispose();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="model-stage" bind:this={rootEl}>
|
||||
<canvas class="model-canvas" bind:this={canvasEl} aria-label={copy.title}></canvas>
|
||||
<div class="model-vignette" aria-hidden="true"></div>
|
||||
<div class="model-scanlines" aria-hidden="true"></div>
|
||||
|
||||
<section class="model-hud" aria-label={copy.title}>
|
||||
<p class="model-kicker">{copy.subtitle}</p>
|
||||
<h2>{copy.title}</h2>
|
||||
<div class="model-status-row">
|
||||
<span class="status-light" class:is-ready={loadState === "ready"}></span>
|
||||
<span>{statusText}</span>
|
||||
</div>
|
||||
<p class="model-path">{copy.modelPath}: {modelUrl}</p>
|
||||
<p class="model-hint">{loadError || copy.hint}</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.model-stage {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(circle at 52% 62%, rgb(94 231 255 / 0.12), transparent 26%),
|
||||
radial-gradient(circle at 24% 18%, rgb(166 255 122 / 0.07), transparent 24%),
|
||||
linear-gradient(180deg, #03070d 0%, #07111b 48%, #02050a 100%);
|
||||
}
|
||||
|
||||
.model-canvas,
|
||||
.model-vignette,
|
||||
.model-scanlines {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
}
|
||||
|
||||
.model-canvas {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.model-vignette,
|
||||
.model-scanlines {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.model-vignette {
|
||||
background:
|
||||
linear-gradient(90deg, rgb(0 0 0 / 0.36), transparent 22%, transparent 78%, rgb(0 0 0 / 0.34)),
|
||||
radial-gradient(circle at center, transparent 48%, rgb(0 0 0 / 0.58) 100%);
|
||||
}
|
||||
|
||||
.model-scanlines {
|
||||
opacity: 0.32;
|
||||
background:
|
||||
repeating-linear-gradient(180deg, rgb(94 231 255 / 0.045) 0, rgb(94 231 255 / 0.045) 1px, transparent 1px, transparent 4px);
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
|
||||
.model-hud {
|
||||
position: absolute;
|
||||
top: clamp(1.2rem, 2.8vw, 2.2rem);
|
||||
left: clamp(1.2rem, 2.8vw, 2.4rem);
|
||||
z-index: 2;
|
||||
display: grid;
|
||||
gap: 0.42rem;
|
||||
max-inline-size: min(22rem, 42vw);
|
||||
padding: 0.9rem 1rem 1rem;
|
||||
border: 1px solid rgb(94 231 255 / 0.24);
|
||||
border-radius: 0.7rem;
|
||||
background:
|
||||
linear-gradient(180deg, rgb(8 18 28 / 0.82), rgb(3 9 15 / 0.72)),
|
||||
radial-gradient(circle at 0 0, rgb(94 231 255 / 0.1), transparent 44%);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgb(255 255 255 / 0.06),
|
||||
0 0 28px rgb(94 231 255 / 0.08);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.model-kicker,
|
||||
.model-path,
|
||||
.model-hint {
|
||||
margin: 0;
|
||||
color: rgb(198 226 239 / 0.72);
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: rgb(241 251 255 / 0.96);
|
||||
font-size: clamp(1.15rem, 1.1vw + 0.88rem, 1.72rem);
|
||||
line-height: 1.05;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.model-status-row {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.44rem;
|
||||
color: rgb(229 249 255 / 0.94);
|
||||
font-size: 0.78rem;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.status-light {
|
||||
inline-size: 0.58rem;
|
||||
block-size: 0.58rem;
|
||||
border-radius: 50%;
|
||||
background: rgb(255 188 92 / 0.95);
|
||||
box-shadow: 0 0 0 2px rgb(255 188 92 / 0.16), 0 0 12px rgb(255 188 92 / 0.18);
|
||||
}
|
||||
|
||||
.status-light.is-ready {
|
||||
background: rgb(166 255 122 / 0.95);
|
||||
box-shadow: 0 0 0 2px rgb(166 255 122 / 0.16), 0 0 14px rgb(166 255 122 / 0.22);
|
||||
}
|
||||
|
||||
.model-path {
|
||||
color: rgb(94 231 255 / 0.78);
|
||||
text-transform: none;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.model-hint {
|
||||
color: rgb(198 226 239 / 0.66);
|
||||
text-transform: none;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.model-hud {
|
||||
max-inline-size: min(20rem, calc(100% - 2.4rem));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -67,6 +67,7 @@
|
||||
const CAMERA_TARGET_Z = MATRIX_OFFSET_Z - 0.4;
|
||||
const MATRIX_ROTATION_Y = 0;
|
||||
const rangeStopPositions = [0, 0.25, 0.5, 0.75, 0.875, 1] as const;
|
||||
const maxDisplayForce = 25.6;
|
||||
|
||||
const labelVector = new THREE.Vector3();
|
||||
$: resolvedColorPalette = pressureColorPalettes[colorMapPreset] ?? pressureColorPalettes.emerald;
|
||||
@@ -145,6 +146,10 @@
|
||||
return "--";
|
||||
}
|
||||
|
||||
if (value >= maxDisplayForce) {
|
||||
return `${maxDisplayForce.toFixed(1)}+`;
|
||||
}
|
||||
|
||||
return value.toFixed(1);
|
||||
}
|
||||
|
||||
@@ -672,14 +677,6 @@
|
||||
<span class="stats-key">{viewerI18n.current}</span>
|
||||
<strong class="stats-value">{formatForceStat(stats.current)}</strong>
|
||||
</article>
|
||||
<article class="stats-card">
|
||||
<span class="stats-key">{viewerI18n.max}</span>
|
||||
<strong class="stats-value">{formatForceStat(stats.max)}</strong>
|
||||
</article>
|
||||
<article class="stats-card">
|
||||
<span class="stats-key">{viewerI18n.min}</span>
|
||||
<strong class="stats-value">{formatForceStat(stats.min)}</strong>
|
||||
</article>
|
||||
</div>
|
||||
<p class="stats-note">{statsNote}</p>
|
||||
</section>
|
||||
@@ -768,7 +765,7 @@
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 0.46rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
const viewportWidth = 100;
|
||||
const viewportHeight = 36;
|
||||
const maxDisplayForce = 25.6;
|
||||
|
||||
const toneColorMap: Record<SignalTone, string> = {
|
||||
cyan: "62 232 255",
|
||||
@@ -32,7 +33,15 @@
|
||||
}
|
||||
|
||||
function formatMetric(value: number | null): string {
|
||||
return value === null ? "--" : value.toFixed(1);
|
||||
if (value === null) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
if (value >= maxDisplayForce) {
|
||||
return `${maxDisplayForce.toFixed(1)}+`;
|
||||
}
|
||||
|
||||
return value.toFixed(1);
|
||||
}
|
||||
|
||||
function resolveBounds(seriesCollection: HudSignalPanel["series"]): { min: number; max: number } {
|
||||
|
||||
514
src/lib/components/SpatialForcePanel.svelte
Normal file
514
src/lib/components/SpatialForcePanel.svelte
Normal file
@@ -0,0 +1,514 @@
|
||||
<script lang="ts">
|
||||
import type { HudSpatialForce } from "$lib/types/hud";
|
||||
|
||||
export let spatialForce: HudSpatialForce | null = null;
|
||||
export let side: "left" | "right" = "right";
|
||||
export let panelIndex = 0;
|
||||
export let locale: "zh-CN" | "en-US" = "zh-CN";
|
||||
export let panelCode = "TAN";
|
||||
export let panelTitle = "";
|
||||
export let badgeLabel = "";
|
||||
export let badgeTone: "cyan" | "lime" | "orange" = "cyan";
|
||||
export let requireMagnitude = true;
|
||||
|
||||
function normalizeAngle(value: number): number {
|
||||
return ((value % 360) + 360) % 360;
|
||||
}
|
||||
|
||||
function shortestAngleDelta(from: number, to: number): number {
|
||||
const delta = ((to - from + 540) % 360) - 180;
|
||||
return delta === -180 ? 180 : delta;
|
||||
}
|
||||
|
||||
const displayAngleOffsetDeg = 90;
|
||||
const jumpAngleThresholdDeg = 72;
|
||||
|
||||
let visualAngleDeg = 0;
|
||||
let previousRawAngleDeg: number | null = null;
|
||||
let snapVector = false;
|
||||
let snapResetFrame: number | null = null;
|
||||
|
||||
function setSnapVector(): void {
|
||||
snapVector = true;
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (snapResetFrame !== null) {
|
||||
window.cancelAnimationFrame(snapResetFrame);
|
||||
}
|
||||
|
||||
snapResetFrame = window.requestAnimationFrame(() => {
|
||||
snapVector = false;
|
||||
snapResetFrame = null;
|
||||
});
|
||||
}
|
||||
|
||||
function updateVisualAngle(rawAngleDeg: number, active: boolean): void {
|
||||
if (!active) {
|
||||
previousRawAngleDeg = null;
|
||||
visualAngleDeg = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousRawAngleDeg === null) {
|
||||
previousRawAngleDeg = rawAngleDeg;
|
||||
visualAngleDeg = rawAngleDeg;
|
||||
return;
|
||||
}
|
||||
|
||||
const delta = shortestAngleDelta(previousRawAngleDeg, rawAngleDeg);
|
||||
if (Math.abs(delta) < 0.001) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Math.abs(delta) >= jumpAngleThresholdDeg) {
|
||||
setSnapVector();
|
||||
}
|
||||
|
||||
visualAngleDeg += delta;
|
||||
previousRawAngleDeg = rawAngleDeg;
|
||||
}
|
||||
|
||||
$: i18n =
|
||||
locale === "zh-CN"
|
||||
? {
|
||||
title: "三维力",
|
||||
waiting: "等待数据",
|
||||
angle: "ANGLE",
|
||||
heading: "方向角",
|
||||
strength: "强度",
|
||||
confidence: "置信度"
|
||||
}
|
||||
: {
|
||||
title: "3D Force",
|
||||
waiting: "Waiting",
|
||||
angle: "ANGLE",
|
||||
heading: "Heading",
|
||||
strength: "Strength",
|
||||
confidence: "Confidence"
|
||||
};
|
||||
$: resolvedTitle = panelTitle || i18n.title;
|
||||
$: resolvedBadgeLabel = badgeLabel || i18n.angle;
|
||||
|
||||
$: hasData =
|
||||
spatialForce !== null &&
|
||||
Number.isFinite(spatialForce.angleDeg) &&
|
||||
(!requireMagnitude || (Number.isFinite(spatialForce.magnitude) && Math.abs(spatialForce.magnitude) >= 0.0001));
|
||||
$: angleDeg = hasData ? normalizeAngle((spatialForce?.angleDeg ?? 0) + displayAngleOffsetDeg) : 0;
|
||||
$: updateVisualAngle(angleDeg, hasData);
|
||||
</script>
|
||||
|
||||
<article
|
||||
class="signal-panel spatial-panel side-{side}"
|
||||
class:is-empty={!hasData}
|
||||
aria-label={resolvedTitle}
|
||||
style="--panel-index: {panelIndex};"
|
||||
>
|
||||
<header class="panel-head">
|
||||
<div class="head-text">
|
||||
<p class="panel-code">{panelCode}</p>
|
||||
<p class="panel-title">{resolvedTitle}</p>
|
||||
</div>
|
||||
|
||||
{#if resolvedBadgeLabel}
|
||||
<div class="icon-layer" aria-hidden="true">
|
||||
<span class={`icon-chip tone-${badgeTone}`}>{resolvedBadgeLabel}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
<div class="panel-body">
|
||||
<div class="compass-stage">
|
||||
<div class="compass-core">
|
||||
<div class="compass-ring compass-ring-outer"></div>
|
||||
<div class="compass-ring compass-ring-inner"></div>
|
||||
<div class="compass-axis axis-horizontal"></div>
|
||||
<div class="compass-axis axis-vertical"></div>
|
||||
{#if hasData}
|
||||
<div
|
||||
class="compass-vector"
|
||||
class:is-snap={snapVector}
|
||||
style="transform: translateY(-50%) rotate({-visualAngleDeg}deg);"
|
||||
>
|
||||
<span class="vector-shaft"></span>
|
||||
<span class="vector-head"></span>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="compass-center"></div>
|
||||
<span class="compass-label label-top">0</span>
|
||||
<span class="compass-label label-right">270</span>
|
||||
<span class="compass-label label-bottom">180</span>
|
||||
<span class="compass-label label-left">90</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</article>
|
||||
<style>
|
||||
.signal-panel {
|
||||
--offset-x: 12%;
|
||||
--enter-ms: 1800ms;
|
||||
--fade-ms: 1000ms;
|
||||
overflow: hidden;
|
||||
inline-size: var(--rail-width, min(100%, clamp(34rem, 44vw, 44rem)));
|
||||
max-inline-size: 100%;
|
||||
box-sizing: border-box;
|
||||
flex: 0 0 var(--rail-width, auto);
|
||||
justify-self: start;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
gap: 0.68rem;
|
||||
padding: 0.88rem 0.96rem 1rem;
|
||||
border: 1px solid rgb(var(--hud-border-strong-rgb) / 0.42);
|
||||
border-radius: 0.92rem;
|
||||
background:
|
||||
linear-gradient(160deg, rgb(var(--hud-surface-alt-rgb) / 0.76) 0%, rgb(var(--hud-surface-rgb) / 0.62) 48%, rgb(var(--hud-surface-deep-rgb) / 0.76) 100%),
|
||||
radial-gradient(circle at 12% 0, rgb(var(--hud-glow-rgb) / 0.1), transparent 40%);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgb(var(--hud-border-strong-rgb) / 0.08),
|
||||
inset 0 -24px 32px rgb(0 0 0 / 0.48),
|
||||
0 0 14px rgb(var(--hud-glow-rgb) / 0.14);
|
||||
opacity: 1;
|
||||
transform: translateX(0) scale(1) rotate(0);
|
||||
transition:
|
||||
opacity var(--fade-ms) cubic-bezier(0.18, 0.88, 0.3, 1),
|
||||
transform var(--enter-ms) cubic-bezier(0.2, 0.9, 0.28, 1),
|
||||
border-color 460ms ease,
|
||||
filter 760ms ease;
|
||||
transition-delay: calc(var(--panel-index) * 140ms);
|
||||
}
|
||||
|
||||
.signal-panel.side-left {
|
||||
--offset-x: -132%;
|
||||
}
|
||||
|
||||
.signal-panel.side-right {
|
||||
--offset-x: 132%;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.spatial-panel.is-empty {
|
||||
opacity: 0.82;
|
||||
}
|
||||
|
||||
.spatial-panel::after {
|
||||
content: "";
|
||||
display: block;
|
||||
block-size: 1rem;
|
||||
}
|
||||
|
||||
.spatial-panel {
|
||||
margin-block-end: clamp(0.8rem, 1.8vh, 1.4rem);
|
||||
}
|
||||
|
||||
.panel-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 0.4rem;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
.head-text {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.panel-code {
|
||||
margin: 0;
|
||||
font-size: 0.63rem;
|
||||
color: rgb(var(--hud-text-dim-rgb) / 0.88);
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
margin: 0.12rem 0 0;
|
||||
font-size: 1.08rem;
|
||||
color: rgb(var(--hud-text-main-rgb) / 0.96);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.icon-layer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.26rem;
|
||||
}
|
||||
|
||||
.icon-chip {
|
||||
border: 1px solid rgb(var(--hud-border-strong-rgb) / 0.44);
|
||||
border-radius: 999px;
|
||||
padding: 0.08rem 0.36rem;
|
||||
font-size: 0.58rem;
|
||||
letter-spacing: 0.08em;
|
||||
color: rgb(var(--hud-text-main-rgb) / 0.94);
|
||||
background: rgb(var(--hud-surface-rgb) / 0.66);
|
||||
}
|
||||
|
||||
.icon-chip.tone-cyan {
|
||||
border-color: rgb(var(--hud-cyan-rgb) / 0.54);
|
||||
}
|
||||
|
||||
.icon-chip.tone-lime {
|
||||
border-color: rgb(var(--hud-lime-rgb) / 0.54);
|
||||
}
|
||||
|
||||
.icon-chip.tone-orange {
|
||||
border-color: rgb(var(--hud-orange-rgb) / 0.54);
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 0.72rem;
|
||||
block-size: clamp(14rem, 17vw, 16.5rem);
|
||||
min-block-size: 5rem;
|
||||
}
|
||||
|
||||
.compass-stage {
|
||||
position: relative;
|
||||
min-block-size: 0;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
padding: 1.35rem 1.7rem;
|
||||
border: 1px solid rgb(var(--hud-border-strong-rgb) / 0.32);
|
||||
border-radius: 0.62rem;
|
||||
background:
|
||||
linear-gradient(180deg, rgb(var(--hud-surface-alt-rgb) / 0.68), rgb(var(--hud-surface-deep-rgb) / 0.78)),
|
||||
radial-gradient(circle at 50% 0, rgb(var(--hud-glow-rgb) / 0.09), transparent 45%);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.compass-core {
|
||||
position: relative;
|
||||
inline-size: min(72%, 12.2rem, calc(100% - 3.4rem));
|
||||
min-inline-size: 6.2rem;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.compass-ring,
|
||||
.compass-axis,
|
||||
.compass-center,
|
||||
.compass-vector {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.compass-ring {
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.compass-ring-outer {
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
border: 1px solid rgb(var(--hud-cyan-rgb) / 0.28);
|
||||
box-shadow: 0 0 18px rgb(var(--hud-glow-rgb) / 0.08);
|
||||
}
|
||||
|
||||
.compass-ring-inner {
|
||||
inline-size: 62%;
|
||||
block-size: 62%;
|
||||
border: 1px dashed rgb(var(--hud-border-strong-rgb) / 0.3);
|
||||
}
|
||||
|
||||
.compass-axis {
|
||||
background: rgb(var(--hud-border-strong-rgb) / 0.18);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.axis-horizontal {
|
||||
inline-size: 86%;
|
||||
block-size: 1px;
|
||||
}
|
||||
|
||||
.axis-vertical {
|
||||
inline-size: 1px;
|
||||
block-size: 86%;
|
||||
}
|
||||
|
||||
.compass-vector {
|
||||
inline-size: 42%;
|
||||
block-size: 0.9rem;
|
||||
transform-origin: 0 50%;
|
||||
transition: transform 220ms ease;
|
||||
}
|
||||
|
||||
.compass-vector.is-snap {
|
||||
transition-duration: 0ms;
|
||||
}
|
||||
|
||||
.vector-shaft {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0.7rem;
|
||||
block-size: 2px;
|
||||
transform: translateY(-50%);
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, rgb(var(--hud-cyan-rgb) / 0.18), rgb(var(--hud-cyan-rgb) / 0.96));
|
||||
box-shadow: 0 0 14px rgb(var(--hud-cyan-rgb) / 0.2);
|
||||
}
|
||||
|
||||
.vector-head {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
inline-size: 0;
|
||||
block-size: 0;
|
||||
transform: translateY(-50%);
|
||||
border-top: 0.36rem solid transparent;
|
||||
border-bottom: 0.36rem solid transparent;
|
||||
border-left: 0.7rem solid rgb(var(--hud-lime-rgb) / 0.96);
|
||||
filter: drop-shadow(0 0 8px rgb(var(--hud-lime-rgb) / 0.24));
|
||||
}
|
||||
|
||||
.compass-center {
|
||||
inline-size: 0.56rem;
|
||||
block-size: 0.56rem;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
background: rgb(var(--hud-text-main-rgb) / 0.92);
|
||||
box-shadow: 0 0 10px rgb(var(--hud-text-main-rgb) / 0.12);
|
||||
}
|
||||
|
||||
.compass-label {
|
||||
position: absolute;
|
||||
font-size: 0.58rem;
|
||||
color: rgb(var(--hud-text-dim-rgb) / 0.8);
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.label-top {
|
||||
top: 0.35rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.label-right {
|
||||
top: 50%;
|
||||
right: 0.42rem;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.label-bottom {
|
||||
bottom: 0.35rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.label-left {
|
||||
top: 50%;
|
||||
left: 0.42rem;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgb(var(--hud-text-dim-rgb) / 0.76);
|
||||
font-size: 0.66rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
background: linear-gradient(180deg, rgb(var(--hud-surface-deep-rgb) / 0.06), rgb(var(--hud-surface-deep-rgb) / 0.18));
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.signal-panel {
|
||||
inline-size: var(--rail-width, min(100%, clamp(28rem, 40vw, 38rem)));
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
block-size: clamp(12rem, 14.5vw, 14rem);
|
||||
}
|
||||
|
||||
.compass-core {
|
||||
inline-size: min(64%, 10.4rem, calc(100% - 3.4rem));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 900px) {
|
||||
.signal-panel {
|
||||
inline-size: var(--rail-width, min(100%, clamp(28rem, 38vw, 36rem)));
|
||||
padding: 0.7rem 0.76rem 0.8rem;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
block-size: clamp(11.2rem, 13.5vw, 13.2rem);
|
||||
}
|
||||
|
||||
.compass-stage {
|
||||
padding-block: 1.2rem;
|
||||
}
|
||||
|
||||
.compass-core {
|
||||
inline-size: min(62%, 10rem, calc(100% - 3.4rem));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 760px) {
|
||||
.signal-panel {
|
||||
inline-size: var(--rail-width, min(100%, clamp(24rem, 34vw, 30rem)));
|
||||
padding: 0.62rem 0.68rem 0.72rem;
|
||||
gap: 0.48rem;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
block-size: clamp(9.4rem, 11vw, 10.8rem);
|
||||
}
|
||||
|
||||
.compass-stage {
|
||||
padding: 1rem 1.45rem;
|
||||
}
|
||||
|
||||
.compass-core {
|
||||
inline-size: min(58%, 8.2rem, calc(100% - 2.9rem));
|
||||
min-inline-size: 5.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 680px) {
|
||||
.signal-panel {
|
||||
inline-size: var(--rail-width, min(100%, clamp(20rem, 28vw, 26rem)));
|
||||
padding: 0.52rem 0.58rem 0.6rem;
|
||||
gap: 0.36rem;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
block-size: clamp(7.6rem, 9vw, 8.8rem);
|
||||
}
|
||||
|
||||
.compass-stage {
|
||||
padding: 0.82rem 1.25rem;
|
||||
}
|
||||
|
||||
.compass-core {
|
||||
inline-size: min(54%, 6.4rem, calc(100% - 2.5rem));
|
||||
min-inline-size: 4.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.signal-panel {
|
||||
inline-size: 100%;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
grid-template-columns: 1fr;
|
||||
block-size: auto;
|
||||
min-block-size: auto;
|
||||
}
|
||||
|
||||
.compass-core {
|
||||
inline-size: min(58vw, 12rem);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -11,15 +11,26 @@
|
||||
export let sessionStartedAt: number = Date.now();
|
||||
export let isRealtime = false;
|
||||
|
||||
let canvasEl: HTMLCanvasElement | undefined;
|
||||
let currentTimeSeconds = 0;
|
||||
let timerId: ReturnType<typeof setInterval> | null = null;
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
onMount(() => {
|
||||
timerId = setInterval(() => {
|
||||
currentTimeSeconds = Math.round((Date.now() - sessionStartedAt) / 100) / 10;
|
||||
}, 200);
|
||||
|
||||
if (canvasEl) {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
drawCanvas(canvasEl, plotPoints, yAxisTicks, xAxisTicks, sampleCount);
|
||||
});
|
||||
resizeObserver.observe(canvasEl);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (timerId != null) clearInterval(timerId);
|
||||
resizeObserver?.disconnect();
|
||||
};
|
||||
});
|
||||
|
||||
@@ -33,7 +44,8 @@
|
||||
const plotInsetRight = 4;
|
||||
const plotInsetTop = 4;
|
||||
const plotInsetBottom = 9;
|
||||
const fixedYBounds = { min: 0, max: 25 };
|
||||
const maxDisplayForce = 25.6;
|
||||
const fixedYBounds = { min: 0, max: maxDisplayForce };
|
||||
|
||||
interface CurveSample {
|
||||
x: number;
|
||||
@@ -56,14 +68,6 @@
|
||||
return Math.min(max, Math.max(min, value));
|
||||
}
|
||||
|
||||
function formatValue(value: number | null): string {
|
||||
if (value === null) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
return value.toFixed(1);
|
||||
}
|
||||
|
||||
function formatAxisValue(value: number, axis: "x" | "y"): string {
|
||||
if (!Number.isFinite(value)) {
|
||||
return "--";
|
||||
@@ -225,24 +229,117 @@
|
||||
}));
|
||||
}
|
||||
|
||||
function createLinePath(points: PlotPoint[]): string {
|
||||
if (points.length === 0) {
|
||||
return "";
|
||||
function drawCanvas(
|
||||
canvas: HTMLCanvasElement | undefined,
|
||||
points: PlotPoint[],
|
||||
yTicks: AxisTick[],
|
||||
xTicks: AxisTick[],
|
||||
count: number
|
||||
): void {
|
||||
if (!canvas) {
|
||||
return;
|
||||
}
|
||||
|
||||
return points.map((point, index) => `${index === 0 ? "M" : "L"} ${point.x} ${point.y}`).join(" ");
|
||||
}
|
||||
|
||||
function createAreaPath(points: PlotPoint[]): string {
|
||||
if (points.length < 2) {
|
||||
return "";
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
const linePath = createLinePath(points);
|
||||
const firstPoint = points[0];
|
||||
const lastPoint = points[points.length - 1];
|
||||
const cssWidth = Math.max(1, canvas.clientWidth);
|
||||
const cssHeight = Math.max(1, canvas.clientHeight);
|
||||
const dpr = typeof window === "undefined" ? 1 : window.devicePixelRatio || 1;
|
||||
const targetWidth = Math.round(cssWidth * dpr);
|
||||
const targetHeight = Math.round(cssHeight * dpr);
|
||||
|
||||
return `${linePath} L ${lastPoint.x} ${viewportHeight - plotInsetBottom} L ${firstPoint.x} ${viewportHeight - plotInsetBottom} Z`;
|
||||
if (canvas.width !== targetWidth || canvas.height !== targetHeight) {
|
||||
canvas.width = targetWidth;
|
||||
canvas.height = targetHeight;
|
||||
}
|
||||
|
||||
context.setTransform(targetWidth / viewportWidth, 0, 0, targetHeight / viewportHeight, 0, 0);
|
||||
context.clearRect(0, 0, viewportWidth, viewportHeight);
|
||||
|
||||
context.save();
|
||||
context.lineWidth = 0.45;
|
||||
context.strokeStyle = "rgb(120 180 150 / 0.16)";
|
||||
for (const tick of yTicks) {
|
||||
context.beginPath();
|
||||
context.moveTo(plotInsetLeft, tick.plotY);
|
||||
context.lineTo(viewportWidth - plotInsetRight, tick.plotY);
|
||||
context.stroke();
|
||||
}
|
||||
context.restore();
|
||||
|
||||
if (points.length >= 2) {
|
||||
const baselineY = viewportHeight - plotInsetBottom;
|
||||
const fill = context.createLinearGradient(0, plotInsetTop, 0, baselineY);
|
||||
fill.addColorStop(0, "rgb(62 232 255 / 0.28)");
|
||||
fill.addColorStop(1, "rgb(62 232 255 / 0.02)");
|
||||
|
||||
context.save();
|
||||
context.beginPath();
|
||||
context.moveTo(points[0].x, points[0].y);
|
||||
for (let index = 1; index < points.length; index += 1) {
|
||||
context.lineTo(points[index].x, points[index].y);
|
||||
}
|
||||
context.lineTo(points[points.length - 1].x, baselineY);
|
||||
context.lineTo(points[0].x, baselineY);
|
||||
context.closePath();
|
||||
context.fillStyle = fill;
|
||||
context.fill();
|
||||
context.restore();
|
||||
}
|
||||
|
||||
if (points.length > 0) {
|
||||
context.save();
|
||||
context.beginPath();
|
||||
context.moveTo(points[0].x, points[0].y);
|
||||
for (let index = 1; index < points.length; index += 1) {
|
||||
context.lineTo(points[index].x, points[index].y);
|
||||
}
|
||||
context.lineWidth = 1.35;
|
||||
context.lineCap = "round";
|
||||
context.lineJoin = "round";
|
||||
context.strokeStyle = "rgb(130 232 255 / 0.96)";
|
||||
context.shadowColor = "rgb(62 232 255 / 0.22)";
|
||||
context.shadowBlur = 4;
|
||||
context.stroke();
|
||||
context.restore();
|
||||
|
||||
const lastPoint = points[points.length - 1];
|
||||
context.save();
|
||||
context.beginPath();
|
||||
context.arc(lastPoint.x, lastPoint.y, 1.7, 0, Math.PI * 2);
|
||||
context.fillStyle = "rgb(73 222 128 / 0.98)";
|
||||
context.shadowColor = "rgb(73 222 128 / 0.3)";
|
||||
context.shadowBlur = 6;
|
||||
context.fill();
|
||||
context.restore();
|
||||
}
|
||||
|
||||
context.save();
|
||||
context.font = "600 3.2px sans-serif";
|
||||
context.textBaseline = "middle";
|
||||
context.shadowColor = "rgb(0 0 0 / 0.3)";
|
||||
context.shadowBlur = 4;
|
||||
context.fillStyle = "rgb(145 185 165 / 0.84)";
|
||||
for (const tick of yTicks) {
|
||||
context.textAlign = "right";
|
||||
context.fillText(tick.label, tick.plotX, tick.plotY + 0.5);
|
||||
}
|
||||
|
||||
context.fillStyle = "rgb(180 220 200 / 0.9)";
|
||||
context.textBaseline = "alphabetic";
|
||||
for (let index = 0; index < xTicks.length; index += 1) {
|
||||
const tick = xTicks[index];
|
||||
context.textAlign = index === 0 ? "left" : index === xTicks.length - 1 ? "right" : "center";
|
||||
context.fillText(tick.label, tick.plotX, tick.plotY);
|
||||
}
|
||||
context.restore();
|
||||
|
||||
if (count === 0) {
|
||||
context.clearRect(0, 0, viewportWidth, viewportHeight);
|
||||
}
|
||||
}
|
||||
|
||||
$: sourceYValues = yValues && yValues.length ? yValues : summary.points;
|
||||
@@ -261,14 +358,9 @@
|
||||
$: xDataBounds = resolveDataBounds(samples.map((sample) => sample.x));
|
||||
$: yDataBounds = resolveDataBounds(samples.map((sample) => sample.y));
|
||||
$: plotPoints = convertPoints(samples, xScaleBounds, yScaleBounds);
|
||||
$: linePath = createLinePath(plotPoints);
|
||||
$: areaPath = createAreaPath(plotPoints);
|
||||
$: lastPoint = plotPoints.length > 0 ? plotPoints[plotPoints.length - 1] : null;
|
||||
$: yAxisTicks = sampleCount > 0 ? buildYAxisTicks(yScaleBounds, yDataBounds) : [];
|
||||
$: xAxisTicks = sampleCount > 0 ? buildXAxisTicks(xScaleBounds) : [];
|
||||
$: latestValue = formatValue(summary.latest);
|
||||
$: minValue = formatValue(summary.min);
|
||||
$: maxValue = formatValue(summary.max);
|
||||
$: drawCanvas(canvasEl, plotPoints, yAxisTicks, xAxisTicks, sampleCount);
|
||||
</script>
|
||||
|
||||
<article
|
||||
@@ -280,62 +372,12 @@
|
||||
<header class="panel-head">
|
||||
<div class="head-text">
|
||||
<p class="panel-code">RF</p>
|
||||
<p class="panel-title">{summary.label}</p>
|
||||
</div>
|
||||
|
||||
<div class="icon-layer" aria-hidden="true">
|
||||
<span class="icon-chip tone-cyan">NOW</span>
|
||||
<span class="icon-chip tone-lime">MIN</span>
|
||||
<span class="icon-chip tone-orange">MAX</span>
|
||||
<p class="panel-title">{locale === "zh-CN" ? "合力" : "Resultant Force"}</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="chart-stage">
|
||||
<svg viewBox="0 0 {viewportWidth} {viewportHeight}" preserveAspectRatio="none" role="img" aria-label={summary.label}>
|
||||
<defs>
|
||||
<linearGradient id="summary-fill" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="rgb(62 232 255 / 0.28)" />
|
||||
<stop offset="100%" stop-color="rgb(62 232 255 / 0.02)" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<g class="grid-lines" aria-hidden="true">
|
||||
{#each yAxisTicks as tick (`grid-${tick.value}`)}
|
||||
<line x1={plotInsetLeft} y1={tick.plotY} x2={viewportWidth - plotInsetRight} y2={tick.plotY}></line>
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
{#if areaPath}
|
||||
<path d={areaPath} class="summary-area"></path>
|
||||
{/if}
|
||||
|
||||
{#if linePath}
|
||||
<path d={linePath} class="summary-line"></path>
|
||||
{/if}
|
||||
|
||||
{#if lastPoint}
|
||||
<circle cx={lastPoint.x} cy={lastPoint.y} r="1.7" class="summary-dot"></circle>
|
||||
{/if}
|
||||
|
||||
<g class="axis-labels" aria-hidden="true">
|
||||
{#each yAxisTicks as tick, index (`y-${index}`)}
|
||||
<text class="axis-label y-axis-label" x={tick.plotX} y={tick.plotY + 1.1} text-anchor="end">
|
||||
{tick.label}
|
||||
</text>
|
||||
{/each}
|
||||
|
||||
{#each xAxisTicks as tick, index (`x-${index}`)}
|
||||
<text
|
||||
class="axis-label x-axis-label"
|
||||
x={tick.plotX}
|
||||
y={tick.plotY}
|
||||
text-anchor={index === 0 ? "start" : index === xAxisTicks.length - 1 ? "end" : "middle"}
|
||||
>
|
||||
{tick.label}
|
||||
</text>
|
||||
{/each}
|
||||
</g>
|
||||
</svg>
|
||||
<canvas bind:this={canvasEl} aria-label={summary.label}></canvas>
|
||||
|
||||
{#if sampleCount === 0}
|
||||
<div class="empty-state">
|
||||
@@ -343,24 +385,6 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<footer class="panel-foot">
|
||||
<p class="foot-item">
|
||||
<span class="dot tone-cyan"></span>
|
||||
<span class="metric-text">{i18n.now}</span>
|
||||
<span class="value">{latestValue}</span>
|
||||
</p>
|
||||
<p class="foot-item">
|
||||
<span class="dot tone-lime"></span>
|
||||
<span class="metric-text">{i18n.min}</span>
|
||||
<span class="value">{minValue}</span>
|
||||
</p>
|
||||
<p class="foot-item">
|
||||
<span class="dot tone-orange"></span>
|
||||
<span class="metric-text">{i18n.max}</span>
|
||||
<span class="value">{maxValue}</span>
|
||||
</p>
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
<style>
|
||||
@@ -369,10 +393,13 @@
|
||||
--enter-ms: 1800ms;
|
||||
--fade-ms: 1000ms;
|
||||
overflow: hidden;
|
||||
inline-size: min(100%, clamp(34rem, 44vw, 44rem));
|
||||
inline-size: var(--rail-width, min(100%, clamp(34rem, 44vw, 44rem)));
|
||||
max-inline-size: 100%;
|
||||
box-sizing: border-box;
|
||||
flex: 0 0 var(--rail-width, auto);
|
||||
justify-self: start;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
grid-template-rows: auto 1fr;
|
||||
gap: 0.68rem;
|
||||
padding: 0.88rem 0.96rem 1rem;
|
||||
border: 1px solid rgb(var(--hud-border-strong-rgb) / 0.42);
|
||||
@@ -438,36 +465,6 @@
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.icon-layer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.26rem;
|
||||
}
|
||||
|
||||
.icon-chip {
|
||||
border: 1px solid rgb(var(--hud-border-strong-rgb) / 0.44);
|
||||
border-radius: 999px;
|
||||
padding: 0.08rem 0.36rem;
|
||||
font-size: 0.58rem;
|
||||
letter-spacing: 0.08em;
|
||||
color: rgb(var(--hud-text-main-rgb) / 0.94);
|
||||
background: rgb(var(--hud-surface-rgb) / 0.66);
|
||||
}
|
||||
|
||||
.icon-chip.tone-cyan {
|
||||
border-color: rgb(var(--hud-cyan-rgb) / 0.54);
|
||||
}
|
||||
|
||||
.icon-chip.tone-lime {
|
||||
border-color: rgb(var(--hud-lime-rgb) / 0.56);
|
||||
}
|
||||
|
||||
.icon-chip.tone-orange {
|
||||
border-color: rgb(var(--hud-orange-rgb) / 0.58);
|
||||
}
|
||||
|
||||
.chart-stage {
|
||||
position: relative;
|
||||
block-size: clamp(12rem, 15.5vw, 15rem);
|
||||
@@ -480,53 +477,12 @@
|
||||
radial-gradient(circle at 50% 0, rgb(var(--hud-glow-rgb) / 0.09), transparent 45%);
|
||||
}
|
||||
|
||||
svg {
|
||||
canvas {
|
||||
display: block;
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
}
|
||||
|
||||
.grid-lines line {
|
||||
stroke: rgb(var(--hud-border-strong-rgb) / 0.16);
|
||||
stroke-width: 0.45;
|
||||
}
|
||||
|
||||
.summary-area {
|
||||
fill: url(#summary-fill);
|
||||
}
|
||||
|
||||
.summary-line {
|
||||
fill: none;
|
||||
stroke: rgb(var(--hud-cyan-rgb) / 0.96);
|
||||
stroke-width: 1.35;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
filter: drop-shadow(0 0 4px rgb(var(--hud-cyan-rgb) / 0.22));
|
||||
}
|
||||
|
||||
.summary-dot {
|
||||
fill: rgb(var(--hud-lime-rgb) / 0.98);
|
||||
filter: drop-shadow(0 0 6px rgb(var(--hud-lime-rgb) / 0.3));
|
||||
}
|
||||
|
||||
.axis-label {
|
||||
fill: rgb(var(--hud-text-main-rgb) / 0.88);
|
||||
font-size: 3.2px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
text-shadow:
|
||||
0 1px 0 rgb(0 0 0 / 0.46),
|
||||
0 0 4px rgb(0 0 0 / 0.3);
|
||||
}
|
||||
|
||||
.y-axis-label {
|
||||
fill: rgb(var(--hud-text-dim-rgb) / 0.84);
|
||||
}
|
||||
|
||||
.x-axis-label {
|
||||
fill: rgb(var(--hud-text-dim-rgb) / 0.9);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -540,58 +496,9 @@
|
||||
background: linear-gradient(180deg, rgb(var(--hud-surface-deep-rgb) / 0.06), rgb(var(--hud-surface-deep-rgb) / 0.18));
|
||||
}
|
||||
|
||||
.panel-foot {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.8rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.foot-item {
|
||||
margin: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.22rem;
|
||||
color: rgb(var(--hud-text-main-rgb) / 0.9);
|
||||
font-size: 0.68rem;
|
||||
letter-spacing: 0.03em;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.metric-text {
|
||||
color: rgb(var(--hud-text-dim-rgb) / 0.82);
|
||||
text-transform: uppercase;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dot {
|
||||
inline-size: 0.34rem;
|
||||
block-size: 0.34rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.dot.tone-cyan {
|
||||
background: rgb(var(--hud-cyan-rgb));
|
||||
}
|
||||
|
||||
.dot.tone-lime {
|
||||
background: rgb(var(--hud-lime-rgb));
|
||||
}
|
||||
|
||||
.dot.tone-orange {
|
||||
background: rgb(var(--hud-orange-rgb));
|
||||
}
|
||||
|
||||
.value {
|
||||
min-inline-size: 2.6rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.signal-panel {
|
||||
inline-size: min(100%, clamp(28rem, 40vw, 38rem));
|
||||
inline-size: var(--rail-width, min(100%, clamp(28rem, 40vw, 38rem)));
|
||||
}
|
||||
|
||||
.chart-stage {
|
||||
@@ -601,7 +508,7 @@
|
||||
|
||||
@media (max-height: 900px) {
|
||||
.signal-panel {
|
||||
inline-size: min(100%, clamp(28rem, 38vw, 36rem));
|
||||
inline-size: var(--rail-width, min(100%, clamp(28rem, 38vw, 36rem)));
|
||||
padding: 0.7rem 0.76rem 0.8rem;
|
||||
}
|
||||
|
||||
@@ -612,7 +519,7 @@
|
||||
|
||||
@media (max-height: 760px) {
|
||||
.signal-panel {
|
||||
inline-size: min(100%, clamp(24rem, 34vw, 30rem));
|
||||
inline-size: var(--rail-width, min(100%, clamp(24rem, 34vw, 30rem)));
|
||||
padding: 0.62rem 0.68rem 0.72rem;
|
||||
gap: 0.48rem;
|
||||
}
|
||||
@@ -624,7 +531,7 @@
|
||||
|
||||
@media (max-height: 680px) {
|
||||
.signal-panel {
|
||||
inline-size: min(100%, clamp(20rem, 28vw, 26rem));
|
||||
inline-size: var(--rail-width, min(100%, clamp(20rem, 28vw, 26rem)));
|
||||
padding: 0.52rem 0.58rem 0.6rem;
|
||||
gap: 0.36rem;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ export type LocaleCode = "zh-CN" | "en-US";
|
||||
export type WindowControlAction = "minimize" | "toggle-maximize" | "close";
|
||||
|
||||
export type ConnectionState = "online" | "connecting" | "offline";
|
||||
export type StageViewMode = "webgl" | "model3d";
|
||||
|
||||
export type StageStatusTone = "ok" | "warn" | "idle";
|
||||
export type HudNoticeTone = "ok" | "warn" | "info";
|
||||
@@ -13,8 +14,6 @@ export type MatrixDisplayMode = "numeric" | "dots";
|
||||
|
||||
export type SignalPanelSide = "left" | "right";
|
||||
|
||||
export type HudConfigTone = "neutral" | "cyan" | "lime" | "orange";
|
||||
|
||||
export interface HudSignalSeries {
|
||||
id: string;
|
||||
tone: SignalTone;
|
||||
@@ -40,11 +39,18 @@ export interface HudSignalPanel {
|
||||
max: number | null;
|
||||
}
|
||||
|
||||
export interface HudSpatialForce {
|
||||
angleDeg: number;
|
||||
magnitude: number;
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
export interface HudPacket {
|
||||
ts: number;
|
||||
panels: HudSignalPanel[];
|
||||
summary: HudSummary;
|
||||
pressureMatrix: number[] | null;
|
||||
spatialForce: HudSpatialForce | null;
|
||||
}
|
||||
|
||||
export interface HudSummary {
|
||||
@@ -56,13 +62,6 @@ export interface HudSummary {
|
||||
max: number | null;
|
||||
}
|
||||
|
||||
export interface HudConfigLink {
|
||||
id: string;
|
||||
label: string;
|
||||
tone?: HudConfigTone;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
export interface HudColorMapOption {
|
||||
id: PressureColorMapPreset;
|
||||
label: string;
|
||||
@@ -86,6 +85,9 @@ export interface HudCopy {
|
||||
matrixViewLabel: string;
|
||||
matrixViewNumericLabel: string;
|
||||
matrixViewDotsLabel: string;
|
||||
stageModeLabel: string;
|
||||
stageModeWebglLabel: string;
|
||||
stageModeModelLabel: string;
|
||||
resetConfigLabel: string;
|
||||
applyLiveHint: string;
|
||||
runtimeReady: string;
|
||||
@@ -96,7 +98,6 @@ export interface HudCopy {
|
||||
deviceLabel: string;
|
||||
sampleRateLabel: string;
|
||||
channelsLabel: string;
|
||||
configLinksLabel: string;
|
||||
refreshPortsLabel: string;
|
||||
connectActionLabel: string;
|
||||
disconnectActionLabel: string;
|
||||
@@ -158,6 +159,7 @@ export interface SerialRecordStateResult {
|
||||
export interface SerialImportFrameResult {
|
||||
data: number[];
|
||||
dtsMs: number;
|
||||
spatialForce: HudSpatialForce | null;
|
||||
}
|
||||
|
||||
export interface SerialImportResult {
|
||||
|
||||
@@ -19,20 +19,20 @@
|
||||
FileExplorerRoot,
|
||||
HudColorMapOption,
|
||||
HudCopy,
|
||||
HudConfigLink,
|
||||
HudNoticeTone,
|
||||
HudPacket,
|
||||
HudSpatialForce,
|
||||
PressureColorMapPreset,
|
||||
HudSignalPanel,
|
||||
HudSignalSeries,
|
||||
HudSummary,
|
||||
LocaleCode,
|
||||
MatrixDisplayMode,
|
||||
SerialConnectResult,
|
||||
SerialExportResult,
|
||||
SerialRecordStateResult,
|
||||
SerialImportResult,
|
||||
SignalTone,
|
||||
StageViewMode,
|
||||
WindowControlAction
|
||||
} from "$lib/types/hud";
|
||||
|
||||
@@ -42,6 +42,28 @@
|
||||
interface ReplayFrame {
|
||||
values: number[];
|
||||
dtsMs: number;
|
||||
spatialForce?: HudSpatialForce | null;
|
||||
}
|
||||
|
||||
interface DevKitPztAngleEvent {
|
||||
seq: number;
|
||||
timestampMs: number;
|
||||
dtsMs: number;
|
||||
angle: number;
|
||||
magnitude: number;
|
||||
state: number;
|
||||
copX: number;
|
||||
copY: number;
|
||||
baseX: number;
|
||||
baseY: number;
|
||||
totalPress: number;
|
||||
threshold: number;
|
||||
}
|
||||
|
||||
interface DevKitReplayFramePushResult {
|
||||
seq: number;
|
||||
timestampMs: number;
|
||||
dtsMs: number;
|
||||
}
|
||||
|
||||
const copyByLocale: Record<LocaleCode, HudCopy> = {
|
||||
@@ -60,8 +82,11 @@
|
||||
rangeMaxLabel: "最大值",
|
||||
colorMapLabel: "映射颜色",
|
||||
matrixViewLabel: "矩阵模式",
|
||||
matrixViewNumericLabel: "数字矩阵",
|
||||
matrixViewNumericLabel: "数字模式",
|
||||
matrixViewDotsLabel: "点矩阵",
|
||||
stageModeLabel: "渲染模式",
|
||||
stageModeWebglLabel: "WebGL",
|
||||
stageModeModelLabel: "3D 模型",
|
||||
resetConfigLabel: "恢复默认",
|
||||
applyLiveHint: "实时生效 / 矩阵尺寸变更将重建 viewer",
|
||||
runtimeReady: "WEBGL2 READY",
|
||||
@@ -72,7 +97,6 @@
|
||||
deviceLabel: "设备",
|
||||
sampleRateLabel: "采样率",
|
||||
channelsLabel: "通道",
|
||||
configLinksLabel: "配置链接",
|
||||
refreshPortsLabel: "刷新",
|
||||
connectActionLabel: "连接",
|
||||
disconnectActionLabel: "断开",
|
||||
@@ -121,6 +145,9 @@
|
||||
matrixViewLabel: "Matrix Mode",
|
||||
matrixViewNumericLabel: "Numeric",
|
||||
matrixViewDotsLabel: "Dots",
|
||||
stageModeLabel: "Render Mode",
|
||||
stageModeWebglLabel: "WebGL",
|
||||
stageModeModelLabel: "3D Model",
|
||||
resetConfigLabel: "Reset",
|
||||
applyLiveHint: "Live apply / size changes recreate the viewer",
|
||||
runtimeReady: "WEBGL2 READY",
|
||||
@@ -131,7 +158,6 @@
|
||||
deviceLabel: "Device",
|
||||
sampleRateLabel: "Sample Rate",
|
||||
channelsLabel: "Channels",
|
||||
configLinksLabel: "Config Links",
|
||||
refreshPortsLabel: "Refresh",
|
||||
connectActionLabel: "Connect",
|
||||
disconnectActionLabel: "Disconnect",
|
||||
@@ -168,6 +194,9 @@
|
||||
const pointsPerSeries = 28;
|
||||
const summaryPointsPerSeries = 42;
|
||||
const signalRenderTickMs = 1200;
|
||||
const summaryReleaseForceEpsilon = 0.1;
|
||||
const releaseClearDelayMs = 5000;
|
||||
const spatialForceMagnitudeEpsilon = 0.0001;
|
||||
const replayDefaultFrameMs = 40;
|
||||
const showSignalPanels = false;
|
||||
const mockToneCycle: SignalTone[] = ["cyan", "lime", "orange", "violet", "gold", "rose"];
|
||||
@@ -214,19 +243,18 @@
|
||||
let sampleRateValue = "100Hz";
|
||||
let channelsValue = "84";
|
||||
let isWindowMaximized = false;
|
||||
let activeConfigLinkId = "stream-on";
|
||||
let isConfigPanelOpen = false;
|
||||
let isPrecisionTestOpen = false;
|
||||
let hasSignalData = false;
|
||||
let signalPanels: HudSignalPanel[] = buildInactivePanels();
|
||||
let summary: HudSummary = buildEmptySummary();
|
||||
let pressureMatrix: number[] | null = null;
|
||||
let spatialForce: HudSpatialForce | null = null;
|
||||
let devkitSpatialForce: HudSpatialForce | null = null;
|
||||
let matrixRows = 12;
|
||||
let matrixCols = 7;
|
||||
let rangeMin = DEFAULT_PRESSURE_RANGE_MIN;
|
||||
let rangeMax = DEFAULT_PRESSURE_RANGE_MAX;
|
||||
let colorMapPreset: PressureColorMapPreset = "emerald";
|
||||
let matrixDisplayMode: MatrixDisplayMode = "dots";
|
||||
let stageViewMode: StageViewMode = "webgl";
|
||||
let replayFrames: ReplayFrame[] = [];
|
||||
let replayCurrentIndex = 0;
|
||||
let replayHasDisplayedFrame = false;
|
||||
@@ -235,6 +263,8 @@
|
||||
let replayProgress = 0;
|
||||
let replayFileName = "";
|
||||
let replayTimerId: number | null = null;
|
||||
let replayPendingDevkitSeq: number | null = null;
|
||||
let replayDevkitSeqCounter = 0;
|
||||
let fileExplorerOpen = false;
|
||||
let fileExplorerMode: FileExplorerMode = "open";
|
||||
let fileExplorerBusy = false;
|
||||
@@ -260,17 +290,15 @@
|
||||
rowsKept: number;
|
||||
} | null = null;
|
||||
let devkitStatusTimer: number | null = null;
|
||||
let devkitSpatialForceClearTimer: number | null = null;
|
||||
let summaryClearTimer: number | null = null;
|
||||
let spatialForceClearTimer: number | null = null;
|
||||
let summaryReleaseHidden = true;
|
||||
let spatialForceReleaseHidden = false;
|
||||
let spatialForcePanelVisible = false;
|
||||
let sessionStartedAt: number = Date.now();
|
||||
|
||||
$: uiCopy = copyByLocale[locale];
|
||||
$: configLinks = buildConfigLinks(
|
||||
locale,
|
||||
activeConfigLinkId,
|
||||
isConfigPanelOpen,
|
||||
isPrecisionTestOpen,
|
||||
devkitEnabled,
|
||||
isDevKitConfigOpen
|
||||
);
|
||||
$: leftSignalPanels = signalPanels.filter((panel) => panel.side === "left");
|
||||
$: rightSignalPanels = signalPanels.filter((panel) => panel.side === "right");
|
||||
$: rangeTicks = buildRangeTicks(rangeMin, rangeMax);
|
||||
@@ -278,6 +306,7 @@
|
||||
$: rangeScaleStyle = buildRangeScaleStyle(colorMapPreset);
|
||||
$: replayHasData = replayFrames.length > 0;
|
||||
$: replayFrameInfo = replayHasData ? `${replayHasDisplayedFrame ? replayCurrentIndex + 1 : 0}/${replayFrames.length}` : "";
|
||||
$: summaryReleasePending = summaryClearTimer != null;
|
||||
$: fileExplorerTitle =
|
||||
fileExplorerMode === "open" ? uiCopy.fileExplorerImportTitle : uiCopy.fileExplorerExportTitle;
|
||||
$: fileExplorerConfirmLabel =
|
||||
@@ -287,6 +316,83 @@
|
||||
return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
|
||||
}
|
||||
|
||||
function clearDevkitSpatialForce(): void {
|
||||
devkitSpatialForce = null;
|
||||
if (devkitSpatialForceClearTimer != null && typeof window !== "undefined") {
|
||||
window.clearTimeout(devkitSpatialForceClearTimer);
|
||||
devkitSpatialForceClearTimer = null;
|
||||
}
|
||||
hasSignalData =
|
||||
signalPanels.length > 0 ||
|
||||
summary.points.length > 0 ||
|
||||
spatialForce !== null ||
|
||||
spatialForcePanelVisible;
|
||||
}
|
||||
|
||||
function scheduleDevkitSpatialForceClear(): void {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (devkitSpatialForceClearTimer != null) {
|
||||
window.clearTimeout(devkitSpatialForceClearTimer);
|
||||
}
|
||||
|
||||
devkitSpatialForceClearTimer = window.setTimeout(() => {
|
||||
devkitSpatialForce = null;
|
||||
devkitSpatialForceClearTimer = null;
|
||||
hasSignalData = signalPanels.length > 0 || summary.points.length > 0 || spatialForce !== null;
|
||||
}, 420);
|
||||
}
|
||||
|
||||
function cancelSummaryClear(): void {
|
||||
if (summaryClearTimer != null && typeof window !== "undefined") {
|
||||
window.clearTimeout(summaryClearTimer);
|
||||
}
|
||||
summaryClearTimer = null;
|
||||
}
|
||||
|
||||
function scheduleSummaryClear(): void {
|
||||
if (typeof window === "undefined" || summary.points.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (summaryClearTimer != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
summaryClearTimer = window.setTimeout(() => {
|
||||
summary = buildEmptySummary();
|
||||
summaryClearTimer = null;
|
||||
summaryReleaseHidden = true;
|
||||
hasSignalData = signalPanels.length > 0 || spatialForce !== null || spatialForcePanelVisible;
|
||||
}, releaseClearDelayMs);
|
||||
}
|
||||
|
||||
function cancelSpatialForceClear(): void {
|
||||
if (spatialForceClearTimer != null && typeof window !== "undefined") {
|
||||
window.clearTimeout(spatialForceClearTimer);
|
||||
}
|
||||
spatialForceClearTimer = null;
|
||||
}
|
||||
|
||||
function scheduleSpatialForceClear(): void {
|
||||
if (typeof window === "undefined" || !spatialForcePanelVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (spatialForceClearTimer != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
spatialForceClearTimer = window.setTimeout(() => {
|
||||
spatialForceClearTimer = null;
|
||||
spatialForceReleaseHidden = true;
|
||||
spatialForcePanelVisible = false;
|
||||
hasSignalData = signalPanels.length > 0 || summary.points.length > 0 || spatialForce !== null;
|
||||
}, releaseClearDelayMs);
|
||||
}
|
||||
|
||||
function clamp(value: number, min: number, max: number): number {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
}
|
||||
@@ -639,7 +745,8 @@
|
||||
});
|
||||
frames = result.frames.map((frame) => ({
|
||||
values: frame.data,
|
||||
dtsMs: frame.dtsMs
|
||||
dtsMs: frame.dtsMs,
|
||||
spatialForce: frame.spatialForce
|
||||
}));
|
||||
importedFrameCount = result.frameCount;
|
||||
importedChannelCount = result.channelCount;
|
||||
@@ -669,7 +776,8 @@
|
||||
|
||||
const frames = result.frames.map((frame) => ({
|
||||
values: frame.data,
|
||||
dtsMs: frame.dtsMs
|
||||
dtsMs: frame.dtsMs,
|
||||
spatialForce: frame.spatialForce
|
||||
}));
|
||||
|
||||
applyImportedFrames(result.fileName, frames, result.frameCount, result.channelCount);
|
||||
@@ -690,6 +798,43 @@
|
||||
replayTimerId = null;
|
||||
}
|
||||
|
||||
function buildReplayDevkitSeq(frameIndex: number): number {
|
||||
replayDevkitSeqCounter = (replayDevkitSeqCounter + 1) % 1000;
|
||||
return Date.now() * 1000 + replayDevkitSeqCounter + frameIndex;
|
||||
}
|
||||
|
||||
function canPushReplayFrameToDevkit(frame: ReplayFrame): boolean {
|
||||
return (
|
||||
isTauriRuntime() &&
|
||||
devkitEnabled &&
|
||||
devkitRunning &&
|
||||
frame.values.length === 84
|
||||
);
|
||||
}
|
||||
|
||||
function pushReplayFrameToDevkit(frame: ReplayFrame, frameIndex: number): void {
|
||||
if (!canPushReplayFrameToDevkit(frame)) {
|
||||
replayPendingDevkitSeq = null;
|
||||
clearDevkitSpatialForce();
|
||||
return;
|
||||
}
|
||||
|
||||
const seq = buildReplayDevkitSeq(frameIndex);
|
||||
replayPendingDevkitSeq = seq;
|
||||
clearDevkitSpatialForce();
|
||||
|
||||
void invoke<DevKitReplayFramePushResult>("devkit_push_replay_frame", {
|
||||
values: frame.values.map((value) => Math.max(0, Math.round(Number(value) || 0))),
|
||||
dtsMs: clamp(Math.round(frame.dtsMs), 0, 4_294_967_295),
|
||||
seq
|
||||
}).catch((error) => {
|
||||
if (replayPendingDevkitSeq === seq) {
|
||||
replayPendingDevkitSeq = null;
|
||||
}
|
||||
console.error("Failed to push replay frame to DevKit:", error);
|
||||
});
|
||||
}
|
||||
|
||||
function frameValuesToMatrix(values: number[]): number[] {
|
||||
const totalCells = Math.max(matrixRows * matrixCols, 1);
|
||||
const matrix = new Array<number>(totalCells).fill(0);
|
||||
@@ -708,7 +853,14 @@
|
||||
}
|
||||
|
||||
function resetReplayVisualState(): void {
|
||||
cancelSummaryClear();
|
||||
cancelSpatialForceClear();
|
||||
spatialForceReleaseHidden = false;
|
||||
spatialForcePanelVisible = false;
|
||||
pressureMatrix = buildZeroMatrix();
|
||||
spatialForce = null;
|
||||
replayPendingDevkitSeq = null;
|
||||
clearDevkitSpatialForce();
|
||||
signalPanels = buildInactivePanels();
|
||||
summary = buildEmptySummary();
|
||||
hasSignalData = false;
|
||||
@@ -743,7 +895,14 @@
|
||||
replayCurrentIndex = safeIndex;
|
||||
replayHasDisplayedFrame = true;
|
||||
replayProgress = replayFrames.length > 1 ? safeIndex / (replayFrames.length - 1) : 1;
|
||||
pressureMatrix = frameValuesToMatrix(replayFrames[safeIndex].values);
|
||||
const frame = replayFrames[safeIndex];
|
||||
cancelSummaryClear();
|
||||
cancelSpatialForceClear();
|
||||
spatialForceReleaseHidden = false;
|
||||
pressureMatrix = frameValuesToMatrix(frame.values);
|
||||
spatialForce = frame.spatialForce ?? null;
|
||||
spatialForcePanelVisible = spatialForce !== null;
|
||||
pushReplayFrameToDevkit(frame, safeIndex);
|
||||
signalPanels = buildInactivePanels();
|
||||
summary = buildReplaySummaryAt(safeIndex);
|
||||
hasSignalData = true;
|
||||
@@ -912,13 +1071,99 @@
|
||||
}
|
||||
|
||||
function isZeroLikeValue(value: number): boolean {
|
||||
return !Number.isFinite(value) || Math.abs(value) < 0.0001;
|
||||
return !Number.isFinite(value) || Math.abs(value) <= summaryReleaseForceEpsilon;
|
||||
}
|
||||
|
||||
function shouldHideSummary(points: number[]): boolean {
|
||||
return points.length === 0 || points.every((value) => isZeroLikeValue(value));
|
||||
}
|
||||
|
||||
function latestSummaryValue(summaryValue: HudSummary): number | null {
|
||||
if (Number.isFinite(summaryValue.latest)) {
|
||||
return summaryValue.latest;
|
||||
}
|
||||
|
||||
const latestPoint = summaryValue.points[summaryValue.points.length - 1];
|
||||
return Number.isFinite(latestPoint) ? latestPoint : null;
|
||||
}
|
||||
|
||||
function hasActiveSummary(summaryValue: HudSummary): boolean {
|
||||
const latest = latestSummaryValue(summaryValue);
|
||||
return latest !== null && !isZeroLikeValue(latest);
|
||||
}
|
||||
|
||||
function hasActiveSpatialForce(spatialForceValue: HudSpatialForce | null): boolean {
|
||||
return (
|
||||
spatialForceValue !== null &&
|
||||
Number.isFinite(spatialForceValue.angleDeg) &&
|
||||
Number.isFinite(spatialForceValue.magnitude) &&
|
||||
Math.abs(spatialForceValue.magnitude) > spatialForceMagnitudeEpsilon
|
||||
);
|
||||
}
|
||||
|
||||
function buildPacketSummary(summaryValue: HudSummary): HudSummary {
|
||||
const nowSeconds = Math.round((Date.now() - sessionStartedAt) / 100) / 10;
|
||||
const pointCount = summaryValue.points.length;
|
||||
const spacing =
|
||||
pointCount > 1 ? Math.min(1.2, nowSeconds / Math.max(pointCount - 1, 1)) : 0;
|
||||
const startX = Math.max(0, nowSeconds - spacing * Math.max(pointCount - 1, 0));
|
||||
const xValues = summaryValue.points.map((_, index) => Math.round((startX + index * spacing) * 10) / 10);
|
||||
return { ...summaryValue, xValues };
|
||||
}
|
||||
|
||||
function estimateSummarySpacing(xValues: number[]): number {
|
||||
const diffs = xValues
|
||||
.slice(1)
|
||||
.map((value, index) => value - xValues[index])
|
||||
.filter((value) => Number.isFinite(value) && value > 0);
|
||||
|
||||
if (diffs.length === 0) {
|
||||
return 1.2;
|
||||
}
|
||||
|
||||
const average = diffs.reduce((sum, value) => sum + value, 0) / diffs.length;
|
||||
return Math.max(0.1, Math.min(1.2, Math.round(average * 10) / 10));
|
||||
}
|
||||
|
||||
function buildContinuousSummary(summaryValue: HudSummary): HudSummary {
|
||||
if (
|
||||
summary.points.length === 0 ||
|
||||
!summary.xValues ||
|
||||
summary.xValues.length !== summary.points.length
|
||||
) {
|
||||
return buildPacketSummary(summaryValue);
|
||||
}
|
||||
|
||||
const pointCount = summaryValue.points.length;
|
||||
const previousXValues = summary.xValues;
|
||||
const previousCount = previousXValues.length;
|
||||
|
||||
if (pointCount === 0) {
|
||||
return buildEmptySummary();
|
||||
}
|
||||
|
||||
if (pointCount < previousCount) {
|
||||
return buildPacketSummary(summaryValue);
|
||||
}
|
||||
|
||||
const spacing = estimateSummarySpacing(previousXValues);
|
||||
let xValues: number[];
|
||||
|
||||
if (pointCount > previousCount) {
|
||||
xValues = previousXValues.slice();
|
||||
while (xValues.length < pointCount) {
|
||||
const previousX = xValues[xValues.length - 1] ?? 0;
|
||||
xValues.push(Math.round((previousX + spacing) * 10) / 10);
|
||||
}
|
||||
} else {
|
||||
xValues = previousXValues.slice(1);
|
||||
const previousX = previousXValues[previousXValues.length - 1] ?? 0;
|
||||
xValues.push(Math.round((previousX + spacing) * 10) / 10);
|
||||
}
|
||||
|
||||
return { ...summaryValue, xValues };
|
||||
}
|
||||
|
||||
function normalizeSummary(summaryValue: HudSummary): HudSummary {
|
||||
return shouldHideSummary(summaryValue.points) ? buildEmptySummary() : summaryValue;
|
||||
}
|
||||
@@ -986,37 +1231,65 @@
|
||||
return;
|
||||
}
|
||||
signalPanels = showSignalPanels ? packet.panels : buildInactivePanels();
|
||||
const packetHasActiveSummary = hasActiveSummary(packet.summary);
|
||||
if (packet.summary.points.length > 0) {
|
||||
const nowSeconds = Math.round((Date.now() - sessionStartedAt) / 100) / 10;
|
||||
const pointCount = packet.summary.points.length;
|
||||
const spacing =
|
||||
pointCount > 1 ? Math.min(1.2, nowSeconds / Math.max(pointCount - 1, 1)) : 0;
|
||||
const startX = Math.max(0, nowSeconds - spacing * Math.max(pointCount - 1, 0));
|
||||
const xValues = packet.summary.points.map((_, index) => Math.round((startX + index * spacing) * 10) / 10);
|
||||
summary = { ...packet.summary, xValues };
|
||||
} else {
|
||||
summary = packet.summary;
|
||||
if (packetHasActiveSummary) {
|
||||
summaryReleaseHidden = false;
|
||||
summary = buildContinuousSummary(packet.summary);
|
||||
cancelSummaryClear();
|
||||
} else if (summaryReleaseHidden) {
|
||||
summary = buildEmptySummary();
|
||||
} else {
|
||||
summary = buildContinuousSummary(packet.summary);
|
||||
scheduleSummaryClear();
|
||||
}
|
||||
} else if (!summaryReleaseHidden) {
|
||||
scheduleSummaryClear();
|
||||
}
|
||||
pressureMatrix = packet.pressureMatrix;
|
||||
hasSignalData = signalPanels.length > 0 || packet.summary.points.length > 0;
|
||||
const nextSpatialForce = packet.spatialForce ?? null;
|
||||
if (packetHasActiveSummary && hasActiveSpatialForce(nextSpatialForce)) {
|
||||
spatialForceReleaseHidden = false;
|
||||
spatialForcePanelVisible = true;
|
||||
spatialForce = nextSpatialForce;
|
||||
cancelSpatialForceClear();
|
||||
} else {
|
||||
spatialForce = null;
|
||||
if (spatialForceReleaseHidden || !spatialForcePanelVisible) {
|
||||
spatialForcePanelVisible = false;
|
||||
}
|
||||
scheduleSpatialForceClear();
|
||||
}
|
||||
hasSignalData =
|
||||
signalPanels.length > 0 ||
|
||||
summary.points.length > 0 ||
|
||||
spatialForce !== null ||
|
||||
spatialForcePanelVisible;
|
||||
}
|
||||
|
||||
function clearHudPanels(): void {
|
||||
cancelSummaryClear();
|
||||
cancelSpatialForceClear();
|
||||
summaryReleaseHidden = true;
|
||||
spatialForceReleaseHidden = false;
|
||||
spatialForcePanelVisible = false;
|
||||
hasSignalData = false;
|
||||
signalPanels = buildInactivePanels();
|
||||
summary = buildEmptySummary();
|
||||
pressureMatrix = null;
|
||||
spatialForce = null;
|
||||
clearDevkitSpatialForce();
|
||||
}
|
||||
|
||||
function startMockFeed(push: (packet: HudPacket) => void): () => void {
|
||||
let panels = buildInactivePanels();
|
||||
let summaryValue = buildSummary(createSummaryPoints(randomBetween(480, 1440)));
|
||||
push({ ts: Date.now(), panels, summary: summaryValue, pressureMatrix: null });
|
||||
push({ ts: Date.now(), panels, summary: summaryValue, pressureMatrix: null, spatialForce: null });
|
||||
|
||||
const timerId = window.setInterval(() => {
|
||||
summaryValue = evolveSummary(summaryValue);
|
||||
|
||||
push({ ts: Date.now(), panels, summary: summaryValue, pressureMatrix: null });
|
||||
push({ ts: Date.now(), panels, summary: summaryValue, pressureMatrix: null, spatialForce: null });
|
||||
}, signalRenderTickMs);
|
||||
|
||||
return () => {
|
||||
@@ -1030,69 +1303,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
function buildConfigLinks(
|
||||
currentLocale: LocaleCode,
|
||||
activeId: string,
|
||||
isSettingsOpen: boolean,
|
||||
isPrecisionOpen: boolean,
|
||||
isDevKitEnabled: boolean,
|
||||
isDevKitOpen: boolean
|
||||
): HudConfigLink[] {
|
||||
const labels =
|
||||
currentLocale === "zh-CN"
|
||||
? {
|
||||
streamOn: "打开",
|
||||
streamOff: "关闭",
|
||||
precisionTest: "游戏",
|
||||
settings: "参数"
|
||||
}
|
||||
: {
|
||||
streamOn: "Open",
|
||||
streamOff: "Close",
|
||||
precisionTest: "Game",
|
||||
settings: "Setup"
|
||||
};
|
||||
const devkitLabel = currentLocale === "zh-CN" ? "开发工具" : "DevKit";
|
||||
|
||||
const links: HudConfigLink[] = [
|
||||
{
|
||||
id: "stream-on",
|
||||
label: labels.streamOn,
|
||||
tone: "lime",
|
||||
active: activeId === "stream-on"
|
||||
},
|
||||
{
|
||||
id: "stream-off",
|
||||
label: labels.streamOff,
|
||||
tone: "orange",
|
||||
active: activeId === "stream-off"
|
||||
},
|
||||
{
|
||||
id: "precision-test",
|
||||
label: labels.precisionTest,
|
||||
tone: "lime",
|
||||
active: isPrecisionOpen
|
||||
},
|
||||
{
|
||||
id: "settings",
|
||||
label: labels.settings,
|
||||
tone: "neutral",
|
||||
active: isSettingsOpen
|
||||
}
|
||||
];
|
||||
|
||||
if (isDevKitEnabled) {
|
||||
links.push({
|
||||
id: "devkit",
|
||||
label: devkitLabel,
|
||||
tone: "cyan",
|
||||
active: isDevKitOpen
|
||||
});
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
async function ensureDefaultWindowSize(): Promise<void> {
|
||||
if (!isTauriRuntime()) {
|
||||
return;
|
||||
@@ -1642,35 +1852,6 @@
|
||||
resetReplayVisualState();
|
||||
}
|
||||
|
||||
function handleConfigLink(event: CustomEvent<string>): void {
|
||||
if (event.detail === "precision-test") {
|
||||
isPrecisionTestOpen = !isPrecisionTestOpen;
|
||||
isConfigPanelOpen = false;
|
||||
isDevKitConfigOpen = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.detail === "settings") {
|
||||
isPrecisionTestOpen = false;
|
||||
isConfigPanelOpen = !isConfigPanelOpen;
|
||||
isDevKitConfigOpen = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.detail === "devkit") {
|
||||
isPrecisionTestOpen = false;
|
||||
isConfigPanelOpen = false;
|
||||
isDevKitConfigOpen = !isDevKitConfigOpen;
|
||||
return;
|
||||
}
|
||||
|
||||
isPrecisionTestOpen = false;
|
||||
isConfigPanelOpen = false;
|
||||
isDevKitConfigOpen = false;
|
||||
activeConfigLinkId = event.detail;
|
||||
console.info("[hud] config link clicked:", event.detail);
|
||||
}
|
||||
|
||||
async function handleWindowControl(event: CustomEvent<WindowControlAction>): Promise<void> {
|
||||
if (!isTauriRuntime()) {
|
||||
return;
|
||||
@@ -1739,8 +1920,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleMatrixDisplayToggle(event: CustomEvent<boolean>): void {
|
||||
matrixDisplayMode = event.detail ? "dots" : "numeric";
|
||||
function handleStageModeChange(event: CustomEvent<StageViewMode>): void {
|
||||
stageViewMode = event.detail;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
@@ -1770,12 +1951,29 @@
|
||||
.catch((error) => {
|
||||
console.error("Failed to listen for hud_stream:", error);
|
||||
});
|
||||
void listen<{ seq: number; timestampMs: number; dtsMs: number; angle: number }>(
|
||||
"devkit_pzt_angle",
|
||||
(event) => {
|
||||
console.log("[devkit_pzt_angle]", event.payload);
|
||||
void listen<DevKitPztAngleEvent>("devkit_pzt_angle", (event) => {
|
||||
if (replayHasData) {
|
||||
if (replayPendingDevkitSeq == null || event.payload.seq !== replayPendingDevkitSeq) {
|
||||
return;
|
||||
}
|
||||
replayPendingDevkitSeq = null;
|
||||
}
|
||||
)
|
||||
|
||||
const angleDeg = Number(event.payload.angle);
|
||||
const magnitude = Number(event.payload.magnitude);
|
||||
const isReportable = event.payload.state > 0 && Number.isFinite(magnitude) && magnitude > 0;
|
||||
if (!Number.isFinite(angleDeg) || !isReportable) {
|
||||
clearDevkitSpatialForce();
|
||||
return;
|
||||
}
|
||||
|
||||
devkitSpatialForce = {
|
||||
angleDeg,
|
||||
magnitude,
|
||||
confidence: 0
|
||||
};
|
||||
scheduleDevkitSpatialForceClear();
|
||||
})
|
||||
.then((unlisten) => {
|
||||
if (disposed) {
|
||||
unlisten();
|
||||
@@ -1794,6 +1992,9 @@
|
||||
return () => {
|
||||
disposed = true;
|
||||
pauseReplayPlayback();
|
||||
cancelSummaryClear();
|
||||
cancelSpatialForceClear();
|
||||
clearDevkitSpatialForce();
|
||||
stopMockFeed?.();
|
||||
unlistenHudStream?.();
|
||||
unlistenDevkitPztAngle?.();
|
||||
@@ -1832,12 +2033,10 @@
|
||||
sampleRateValue={sampleRateValue}
|
||||
channelsLabel={uiCopy.channelsLabel}
|
||||
channelsValue={channelsValue}
|
||||
configLinksLabel={uiCopy.configLinksLabel}
|
||||
refreshPortsLabel={uiCopy.refreshPortsLabel}
|
||||
matrixViewLabel={uiCopy.matrixViewLabel}
|
||||
matrixViewNumericLabel={uiCopy.matrixViewNumericLabel}
|
||||
matrixViewDotsLabel={uiCopy.matrixViewDotsLabel}
|
||||
{matrixDisplayMode}
|
||||
{stageViewMode}
|
||||
connectActionLabel={uiCopy.connectActionLabel}
|
||||
disconnectActionLabel={uiCopy.disconnectActionLabel}
|
||||
exportActionLabel={uiCopy.exportActionLabel}
|
||||
@@ -1849,7 +2048,6 @@
|
||||
noticeCancelLabel={locale === "zh-CN" ? "取消" : "Cancel"}
|
||||
noticeShowActions={updateNoticeVisible}
|
||||
noticeActionBusy={updateInstallBusy}
|
||||
{configLinks}
|
||||
{isRefreshingPorts}
|
||||
{isExporting}
|
||||
isConnectDisabled={!serialPortValue || connectionState === "connecting"}
|
||||
@@ -1858,8 +2056,7 @@
|
||||
on:windowcontrol={handleWindowControl}
|
||||
on:localechange={handleLocaleChange}
|
||||
on:portchange={handlePortChange}
|
||||
on:configlink={handleConfigLink}
|
||||
on:matrixdisplaytoggle={handleMatrixDisplayToggle}
|
||||
on:stagemodechange={handleStageModeChange}
|
||||
on:serialrefresh={handleSerialRefresh}
|
||||
on:serialconnect={handleSerialConnect}
|
||||
on:serialexport={handleSerialExportRequest}
|
||||
@@ -1879,7 +2076,7 @@
|
||||
bind:rangeMin
|
||||
bind:rangeMax
|
||||
bind:colorMapPreset
|
||||
bind:matrixDisplayMode
|
||||
{stageViewMode}
|
||||
configPanelTitle={uiCopy.configPanelTitle}
|
||||
configPanelHint={uiCopy.configPanelHint}
|
||||
matrixSizeLabel={uiCopy.matrixSizeLabel}
|
||||
@@ -1901,22 +2098,23 @@
|
||||
{replayFileName}
|
||||
{replayFrameInfo}
|
||||
{sessionStartedAt}
|
||||
{summaryReleasePending}
|
||||
{spatialForcePanelVisible}
|
||||
resetConfigLabel={uiCopy.resetConfigLabel}
|
||||
applyLiveHint={uiCopy.applyLiveHint}
|
||||
leftPanels={leftSignalPanels}
|
||||
rightPanels={rightSignalPanels}
|
||||
{pressureMatrix}
|
||||
showConfigPanel={isConfigPanelOpen}
|
||||
showPrecisionTestPanel={isPrecisionTestOpen}
|
||||
{spatialForce}
|
||||
showConfigPanel={false}
|
||||
{summary}
|
||||
on:replaytoggle={handleReplayToggle}
|
||||
on:replaystop={handleReplayStop}
|
||||
on:replayseek={handleReplaySeek}
|
||||
on:replayspeed={handleReplaySpeed}
|
||||
on:replayclose={handleReplayClose}
|
||||
on:configclose={() => (isConfigPanelOpen = false)}
|
||||
>
|
||||
{#if !isPrecisionTestOpen}
|
||||
{#if stageViewMode === "webgl"}
|
||||
<section class="range-scale" aria-label="Signal Range">
|
||||
<p class="range-label">{locale === "zh-CN" ? "范围" : "Range"}</p>
|
||||
<div class="range-track">
|
||||
|
||||
14
static/models/README.md
Normal file
14
static/models/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# 3D model assets
|
||||
|
||||
Put the first pipeline model here:
|
||||
|
||||
- Preferred: `static/models/je-skin-model.glb`
|
||||
- Format: glTF 2.0 `.glb`
|
||||
- Also supported after changing `modelUrl`: glTF 2.0 `.gltf` with its `.bin` and texture files in the same folder
|
||||
- Not supported directly: older glTF 1.0 assets. Convert them to glTF 2.0 first.
|
||||
|
||||
Runtime URL used by the app:
|
||||
|
||||
```text
|
||||
/models/je-skin-model.glb
|
||||
```
|
||||
BIN
static/models/je-skin-model.bin
Normal file
BIN
static/models/je-skin-model.bin
Normal file
Binary file not shown.
1
static/models/je-skin-model.gltf
Normal file
1
static/models/je-skin-model.gltf
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user