Compare commits
82 Commits
main
..
098af30fa7
| Author | SHA1 | Date | |
|---|---|---|---|
| 098af30fa7 | |||
| 280b000496 | |||
| 4bdc5af620 | |||
| ef260c8fb4 | |||
| 1c0d861051 | |||
| 832420f8f9 | |||
| 53518d1114 | |||
| 654fc722f9 | |||
| cf8d81348d | |||
| db9201fd05 | |||
| c9cceb4c01 | |||
| debb38a29b | |||
| b0a4c2b1e6 | |||
| 08f74575c2 | |||
| 9387ade537 | |||
| 6b4ab4365e | |||
| a7fe81e658 | |||
| fb8c15dfd3 | |||
| 0a15102f8c | |||
| d44180db67 | |||
| 4125f71792 | |||
| 656b1bf62b | |||
| a7c9cdc16e | |||
| 11ba2bf8b9 | |||
| 36806e4dc1 | |||
| 33aac68f16 | |||
| 1d959e2c51 | |||
| 76a87fc20d | |||
| 978b782606 | |||
| 54ef5836b1 | |||
| 104c083995 | |||
| 189ca80343 | |||
| 5aef38a99d | |||
| bfb3cbaf9b | |||
| 01017f03fe | |||
| cb1106f319 | |||
| b051ac75cf | |||
| 342fcaeb34 | |||
| fdffd4617a | |||
| e1d7b7d532 | |||
| f29b44da07 | |||
| 99f0451412 | |||
| 56db72a054 | |||
| e07b4fe359 | |||
| 744ce37d6d | |||
| 54d3eb7014 | |||
| c010d0c913 | |||
| f6be625a8f | |||
| a88f0920cf | |||
| c46c8df475 | |||
| 755546713c | |||
| 2ab7e279eb | |||
| 9cbf4ac7e9 | |||
| c7de438d57 | |||
| 8cc0e46422 | |||
| 951d07cf31 | |||
| 97d6a0219f | |||
| d560ce6f82 | |||
| 4b181e5ad3 | |||
| 9a504013c6 | |||
| f38252d21e | |||
| 94300fc7ef | |||
| f323fe5706 | |||
| 0b26613f6e | |||
| 72180e0ea1 | |||
| a1bc61417e | |||
| 326fe50370 | |||
| 3b8cd9e0eb | |||
| 6f5a824fcb | |||
| f0df24ecaa | |||
| 9a1eec48e9 | |||
| 1c2d5f24ef | |||
| 94649ad564 | |||
| 836f9667cb | |||
| 429fceda43 | |||
| a50c7c6945 | |||
| 17a9f6c4ef | |||
| c36df07204 | |||
| d05ca7582c | |||
| 87554e27a1 | |||
| 1b3df4e85c | |||
| 895b86d204 |
@@ -0,0 +1,16 @@
|
|||||||
|
VITE_API_BASE_URL=http://localhost:4400/api
|
||||||
|
VITE_LAUNCHER_API_BASE_URL=http://localhost:4200/api
|
||||||
|
|
||||||
|
# Electron main process runtime overrides
|
||||||
|
LOCAL_AUTOWARE_BRIDGE_IMAGE=autoware-ros-bridge
|
||||||
|
LOCAL_NAV2_BRIDGE_IMAGE=nav2-ros-bridge
|
||||||
|
LOCAL_NAV2_RUNTIME_IMAGE=future-nav2-gazebo:humble
|
||||||
|
LOCAL_SLAM_BRIDGE_IMAGE=slam-ros-bridge
|
||||||
|
LOCAL_SLAM_RUNTIME_IMAGE=future-nav2-gazebo:humble
|
||||||
|
|
||||||
|
# Remote Jetson runtime overrides
|
||||||
|
JETSON_AGENT_URL=http://100.64.0.2:8787
|
||||||
|
JETSON_AGENT_TOKEN=
|
||||||
|
JETSON_AUTOWARE_BRIDGE_IMAGE=reg.autonomystudio.net/fd-module/autoware-ros-bridge:jetson
|
||||||
|
JETSON_NAV2_BRIDGE_IMAGE=reg.autonomystudio.net/fd-module/nav2-ros-bridge:jetson
|
||||||
|
JETSON_NAV2_RUNTIME_IMAGE=reg.autonomystudio.net/fd-module/future-nav2:jetson
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
out
|
||||||
|
dist
|
||||||
|
.env
|
||||||
|
*.log
|
||||||
|
*.tsbuildinfo
|
||||||
|
.DS_Store
|
||||||
@@ -1,3 +1,128 @@
|
|||||||
# lms-fe
|
# Autonomy Studio LMS
|
||||||
|
|
||||||
lms 백엔드
|
Autonomy Studio LMS의 학생용 데스크톱 애플리케이션입니다. Electron 기반으로 동작하며, 학습 워크스페이스에서 ROS 2/NAV2 모듈 설치와 실행, 코드 편집, Git 연동, 터미널, 맵 및 시각화를 제공합니다.
|
||||||
|
|
||||||
|
로컬 PC 시뮬레이션과 원격 플랫폼 워크스페이스를 모두 지원합니다.
|
||||||
|
|
||||||
|
## 주요 기능
|
||||||
|
|
||||||
|
- 강좌, 학습 단계, 모듈 기반 워크스페이스 관리
|
||||||
|
- 로컬 및 원격 플랫폼의 모듈 설치, 빌드, 실행, 종료
|
||||||
|
- 코드 편집기, Git 소스 제어, 내장 터미널
|
||||||
|
- ROS 토픽 연결 상태와 모듈 간 발행/구독 관계 확인
|
||||||
|
- 2D/3D SLAM 맵 저장 및 주행 시각화
|
||||||
|
- Docker 기반 ROS 런타임과 SLAM ROS 브리지 관리
|
||||||
|
- 원격 플랫폼 에이전트와 연동한 워크스페이스 및 교보재 제어
|
||||||
|
|
||||||
|
## 프로젝트 구조
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/
|
||||||
|
main/ Electron main process, Docker/워크스페이스/원격 제어
|
||||||
|
preload/ renderer에 노출하는 IPC API
|
||||||
|
renderer/src/ React 사용자 인터페이스
|
||||||
|
resources/
|
||||||
|
slam-ros-bridge/ SLAM 및 시각화용 ROS 브리지
|
||||||
|
bin/ 패키징에 포함되는 실행 파일
|
||||||
|
licenses/ 배포 라이선스
|
||||||
|
```
|
||||||
|
|
||||||
|
## 요구 사항
|
||||||
|
|
||||||
|
- Node.js와 npm
|
||||||
|
- Docker Engine
|
||||||
|
- Git
|
||||||
|
- Linux AppImage 패키징 시 필요한 시스템 의존성
|
||||||
|
|
||||||
|
GPU 가속을 사용할 경우에는 NVIDIA 드라이버 외에 Docker용 NVIDIA Container Toolkit이 필요합니다. GPU 가속은 Docker 컨테이너에 `--gpus all` 옵션을 전달하므로, Docker 런타임이 GPU를 인식하지 못하면 모듈 실행이 실패합니다.
|
||||||
|
|
||||||
|
## 개발 환경 실행
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
npm ci
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
renderer 개발 서버는 기본적으로 `5176` 포트를 사용합니다.
|
||||||
|
|
||||||
|
## 환경 변수
|
||||||
|
|
||||||
|
`.env.example`을 복사해 로컬 환경에 맞게 설정합니다.
|
||||||
|
|
||||||
|
| 변수 | 설명 |
|
||||||
|
| --- | --- |
|
||||||
|
| `VITE_API_BASE_URL` | LMS 백엔드 API 주소 |
|
||||||
|
| `VITE_LAUNCHER_API_BASE_URL` | 런처/플랫폼 API 주소 |
|
||||||
|
| `LOCAL_AUTOWARE_BRIDGE_IMAGE` | 로컬 Autoware ROS 브리지 이미지 |
|
||||||
|
| `LOCAL_NAV2_BRIDGE_IMAGE` | 로컬 NAV2 ROS 브리지 이미지 |
|
||||||
|
| `LOCAL_NAV2_RUNTIME_IMAGE` | 로컬 NAV2 런타임 이미지 |
|
||||||
|
| `LOCAL_SLAM_BRIDGE_IMAGE` | 로컬 SLAM ROS 브리지 이미지 |
|
||||||
|
| `LOCAL_SLAM_RUNTIME_IMAGE` | 로컬 SLAM 런타임 이미지 |
|
||||||
|
| `JETSON_AGENT_URL` | 원격 Jetson 에이전트 주소 |
|
||||||
|
| `JETSON_AGENT_TOKEN` | 원격 Jetson 에이전트 인증 토큰 |
|
||||||
|
| `JETSON_AUTOWARE_BRIDGE_IMAGE` | 원격 Autoware ROS 브리지 이미지 |
|
||||||
|
| `JETSON_NAV2_BRIDGE_IMAGE` | 원격 NAV2 ROS 브리지 이미지 |
|
||||||
|
| `JETSON_NAV2_RUNTIME_IMAGE` | 원격 NAV2 런타임 이미지 |
|
||||||
|
|
||||||
|
`VITE_` 접두사 변수는 renderer 번들에 포함됩니다. 운영 AppImage를 만들 때는 운영 API 주소로 빌드해야 하며, 토큰이나 비밀번호 같은 민감한 값은 `VITE_` 변수에 넣으면 안 됩니다.
|
||||||
|
|
||||||
|
예시:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
VITE_API_BASE_URL=https://workspace.autonomystudio.net/lms-api/api \
|
||||||
|
VITE_LAUNCHER_API_BASE_URL=https://portal-api.autonomystudio.net/api \
|
||||||
|
npm run build:linux
|
||||||
|
```
|
||||||
|
|
||||||
|
## 빌드
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Electron main, preload, renderer 빌드
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Linux AppImage 생성
|
||||||
|
npm run build:linux
|
||||||
|
```
|
||||||
|
|
||||||
|
패키징 결과물은 `dist/`에 생성됩니다. AppImage에는 아이콘, SLAM ROS 브리지, 실행 도구, 라이선스 리소스가 함께 포함됩니다.
|
||||||
|
|
||||||
|
운영 배포용 AppImage는 관리자 웹의 빌드 변수와 동일한 API 주소, 동일한 소스 커밋을 기준으로 빌드해야 합니다. Kubernetes의 LMS 웹 배포 환경 변수는 Electron AppImage에 런타임으로 주입되지 않습니다.
|
||||||
|
|
||||||
|
## 워크스페이스와 맵
|
||||||
|
|
||||||
|
모듈, 빌드 결과물, 저장 맵은 선택한 사용자 워크스페이스 루트 아래에 관리됩니다. SLAM 맵은 기본적으로 다음 경로에 저장됩니다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
<workspace>/maps
|
||||||
|
```
|
||||||
|
|
||||||
|
앱은 해당 워크스페이스를 Docker 컨테이너의 `/workspace`로 마운트하고, SLAM 브리지에는 `SAVED_MAPS_PATH=/workspace/maps`를 전달합니다.
|
||||||
|
|
||||||
|
## GPU 가속
|
||||||
|
|
||||||
|
GPU 가속을 켠 상태에서 다음과 비슷한 오류가 발생하면 Docker GPU 런타임이 설정되지 않은 상태입니다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
failed to discover GPU vendor from CDI
|
||||||
|
```
|
||||||
|
|
||||||
|
NVIDIA Container Toolkit을 설치하고 Docker를 재시작한 뒤 아래 명령이 정상 동작하는지 확인합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi
|
||||||
|
```
|
||||||
|
|
||||||
|
GPU 설정이 준비되지 않은 PC에서는 앱 설정에서 GPU 가속을 끈 상태로 사용합니다.
|
||||||
|
|
||||||
|
## 로그와 문제 확인
|
||||||
|
|
||||||
|
- 앱 renderer 로그는 Electron의 사용자 로그 경로에 `renderer.log`로 기록됩니다.
|
||||||
|
- 모듈 실행 실패는 워크스페이스 화면의 모듈 로그와 Docker 컨테이너 로그를 함께 확인합니다.
|
||||||
|
- 원격 플랫폼 문제는 에이전트 상태, Tailscale/Headscale 연결, ROS Domain ID, 원격 Docker 이미지를 순서대로 점검합니다.
|
||||||
|
|
||||||
|
## 보안 유의 사항
|
||||||
|
|
||||||
|
- `.env`는 저장소에 커밋하지 않습니다.
|
||||||
|
- 에이전트 토큰, API 키, 인증 정보는 빌드 변수 또는 별도 비밀 저장소로 관리합니다.
|
||||||
|
- 운영 배포 전에는 API 주소와 원격 런타임 이미지 태그가 대상 환경과 일치하는지 확인합니다.
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Korean and English UI
|
||||||
|
|
||||||
|
Use the language selector at the top of the studio settings screen (local or
|
||||||
|
remote workspace). Select a language and press Save to apply it. Unsaved choices
|
||||||
|
do not change the UI or stored preference. Korean is the default. The choice is
|
||||||
|
stored under `autonomy-studio.lms.language` in local storage and applies on the
|
||||||
|
next launch. Switching language updates React subscribers without remounting the
|
||||||
|
workspace, terminal, editor, or visualization iframe.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- App navigation, curriculum UI, studio controls, settings, dialogs and help.
|
||||||
|
- Known application status/error messages and NAV2/SLAM viewer controls.
|
||||||
|
- Course titles, learning content, materials, module names, source code, terminal
|
||||||
|
output and server-authored notifications remain in their original language.
|
||||||
|
- Unrecognized backend/SSH/Docker errors are preserved verbatim for diagnosis.
|
||||||
|
- Embedded text in guide images and external pages is not translated.
|
||||||
|
- This does not change ROS, Docker, SSH, the OS locale, or module parameters.
|
||||||
|
|
||||||
|
## Adding Text
|
||||||
|
|
||||||
|
Use `t('Korean source text')` from `src/renderer/src/i18n` at the display boundary
|
||||||
|
and add the English value to `en.json`. Call `useLocaleRender()` in components
|
||||||
|
which render translations so an open screen reacts to language changes.
|
||||||
|
|
||||||
|
Keep identifiers and state values unchanged. Do not translate stored form values,
|
||||||
|
parameter strings, module identifiers, or values used in conditions. Use whole
|
||||||
|
sentences with named interpolation placeholders instead of joining translated
|
||||||
|
fragments. Korean falls back to the source key; unknown messages retain the source.
|
||||||
|
|
||||||
|
The standalone SLAM viewer uses `public/slam-viewer/locale.js` and explicitly
|
||||||
|
marked text/attributes. It follows the same saved preference through storage
|
||||||
|
events, without reconnecting the bridge or resetting the scene.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run test:i18n
|
||||||
|
npx tsc --noEmit -p tsconfig.web.json
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
The tests cover language persistence, storage failure, cross-window updates,
|
||||||
|
interpolation, source fallback and literal translation-key coverage. Browser
|
||||||
|
checks should additionally cover both languages, narrow studio panels, preserving
|
||||||
|
unsaved edits, and language changes with the viewer open. `npm run build` does not
|
||||||
|
package an AppImage.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
declare const _default: import("electron-vite").ElectronViteConfig;
|
||||||
|
export default _default;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { resolve } from 'path';
|
||||||
|
import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
|
export default defineConfig({
|
||||||
|
main: {
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
input: { index: resolve(__dirname, 'src/main/bootstrap.ts') },
|
||||||
|
output: { inlineDynamicImports: false, chunkFileNames: '[name]-[hash].js' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [externalizeDepsPlugin()]
|
||||||
|
},
|
||||||
|
preload: {
|
||||||
|
plugins: [externalizeDepsPlugin()]
|
||||||
|
},
|
||||||
|
renderer: {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@renderer': resolve('src/renderer/src')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [react(), tailwindcss()]
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { resolve } from 'path'
|
||||||
|
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
main: {
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
input: { index: resolve(__dirname, 'src/main/bootstrap.ts') },
|
||||||
|
output: { inlineDynamicImports: false, chunkFileNames: '[name]-[hash].js' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [externalizeDepsPlugin()]
|
||||||
|
},
|
||||||
|
preload: {
|
||||||
|
plugins: [externalizeDepsPlugin()]
|
||||||
|
},
|
||||||
|
renderer: {
|
||||||
|
server: {
|
||||||
|
port: 5176,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@renderer': resolve('src/renderer/src')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [react(), tailwindcss()]
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"name": "lms",
|
||||||
|
"version": "1.2.15",
|
||||||
|
"description": "Autonomy Studio LMS Electron app",
|
||||||
|
"main": "./out/main/index.js",
|
||||||
|
"author": {
|
||||||
|
"name": "Autonomy Studio",
|
||||||
|
"email": "hooladhoon@gmail.com"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test:i18n": "node --test scripts/test-i18n.cjs",
|
||||||
|
"test:theme": "node --test scripts/test-studio-theme.cjs",
|
||||||
|
"dev": "electron-vite dev",
|
||||||
|
"build": "electron-vite build",
|
||||||
|
"build:linux": "npm run build && electron-builder --linux"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "com.autonomystudio.lms",
|
||||||
|
"productName": "Autonomy Studio LMS",
|
||||||
|
"directories": {
|
||||||
|
"output": "dist"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"out/**/*",
|
||||||
|
"package.json"
|
||||||
|
],
|
||||||
|
"extraResources": [
|
||||||
|
{
|
||||||
|
"from": "resources/icon.png",
|
||||||
|
"to": "icon.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": "resources/slam-ros-bridge",
|
||||||
|
"to": "slam-ros-bridge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": "resources/bin",
|
||||||
|
"to": "bin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": "resources/licenses",
|
||||||
|
"to": "licenses"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"linux": {
|
||||||
|
"target": "AppImage",
|
||||||
|
"icon": "resources/icon.png",
|
||||||
|
"category": "Education"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@electron-toolkit/preload": "^3.0.1",
|
||||||
|
"@electron-toolkit/utils": "^4.0.0",
|
||||||
|
"@mkkellogg/gaussian-splats-3d": "^0.4.7",
|
||||||
|
"@monaco-editor/react": "^4.7.0",
|
||||||
|
"@types/three": "^0.181.0",
|
||||||
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
|
"@xterm/xterm": "^6.0.0",
|
||||||
|
"driver.js": "^1.4.0",
|
||||||
|
"i18next": "^26.4.2",
|
||||||
|
"lucide-react": "^1.14.0",
|
||||||
|
"monaco-editor": "^0.55.1",
|
||||||
|
"node-pty": "^1.1.0",
|
||||||
|
"react-i18next": "^17.0.15",
|
||||||
|
"react-markdown": "^10.1.0",
|
||||||
|
"remark-gfm": "^4.0.1",
|
||||||
|
"socket.io-client": "^4.8.3",
|
||||||
|
"three": "^0.181.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||||
|
"@tailwindcss/vite": "^4.2.4",
|
||||||
|
"@types/react": "^19.1.2",
|
||||||
|
"@types/react-dom": "^19.1.2",
|
||||||
|
"@vitejs/plugin-react": "^4.4.1",
|
||||||
|
"electron": "35.7.5",
|
||||||
|
"electron-builder": "^25.1.8",
|
||||||
|
"electron-vite": "^3.1.0",
|
||||||
|
"react": "^19.1.0",
|
||||||
|
"react-dom": "^19.1.0",
|
||||||
|
"tailwindcss": "^4.2.4",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
FROM ghcr.io/autowarefoundation/autoware-universe:humble-20240301-cuda
|
||||||
|
|
||||||
|
WORKDIR /ros_bridge
|
||||||
|
|
||||||
|
RUN curl -fsSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
|
||||||
|
-o /usr/share/keyrings/ros-archive-keyring.gpg \
|
||||||
|
&& printf "deb [arch=%s signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu %s main\n" \
|
||||||
|
"$(dpkg --print-architecture)" "$(. /etc/os-release && echo "$UBUNTU_CODENAME")" \
|
||||||
|
> /etc/apt/sources.list.d/ros2.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
ros-humble-autoware-adapi-v1-msgs \
|
||||||
|
ros-humble-autoware-planning-msgs \
|
||||||
|
ros-humble-autoware-vehicle-msgs \
|
||||||
|
ros-humble-nav2-msgs \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements.txt /ros_bridge/requirements.txt
|
||||||
|
RUN pip3 install --no-cache-dir -r /ros_bridge/requirements.txt
|
||||||
|
|
||||||
|
COPY bridge.py /ros_bridge/bridge.py
|
||||||
|
|
||||||
|
CMD bash -c "source /opt/ros/humble/setup.bash && if [ -f /autoware_ws/install/setup.bash ]; then source /autoware_ws/install/setup.bash; fi && python3 /ros_bridge/bridge.py"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fastapi>=0.110.0
|
||||||
|
uvicorn[standard]>=0.29.0
|
||||||
|
pydantic>=2.0.0
|
||||||
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,30 @@
|
|||||||
|
BSD License
|
||||||
|
|
||||||
|
For Zstandard software
|
||||||
|
|
||||||
|
Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name Facebook, nor Meta, nor the names of its contributors may
|
||||||
|
be used to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
FROM ros:humble-ros-base
|
||||||
|
|
||||||
|
WORKDIR /ros_bridge
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
python3-pip \
|
||||||
|
ros-humble-nav2-msgs \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements.txt /ros_bridge/requirements.txt
|
||||||
|
RUN pip3 install --no-cache-dir -r /ros_bridge/requirements.txt
|
||||||
|
|
||||||
|
COPY bridge.py /ros_bridge/bridge.py
|
||||||
|
|
||||||
|
CMD bash -c "source /opt/ros/humble/setup.bash && python3 /ros_bridge/bridge.py"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fastapi>=0.110.0
|
||||||
|
uvicorn[standard]>=0.29.0
|
||||||
|
pydantic>=2.0.0
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
FROM future-nav2-gazebo:humble
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt /app/requirements.txt
|
||||||
|
COPY bridge.py /app/bridge.py
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends python3-pip && \
|
||||||
|
pip3 install --no-cache-dir --retries 5 --timeout 180 -r /app/requirements.txt && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
CMD bash -c "source /opt/ros/humble/setup.bash && python3 /app/bridge.py"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fastapi>=0.110.0
|
||||||
|
uvicorn[standard]>=0.29.0
|
||||||
|
pydantic>=2.0.0
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import ast
|
||||||
|
import math
|
||||||
|
from pathlib import Path
|
||||||
|
import threading
|
||||||
|
from types import SimpleNamespace
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class Twist:
|
||||||
|
def __init__(self):
|
||||||
|
self.linear = SimpleNamespace(x=0.0)
|
||||||
|
self.angular = SimpleNamespace(z=0.0)
|
||||||
|
|
||||||
|
|
||||||
|
class TeleopTest(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.now = 10.0
|
||||||
|
self.messages = []
|
||||||
|
source = ast.parse(Path(__file__).with_name('bridge.py').read_text())
|
||||||
|
node = next(item for item in source.body if isinstance(item, ast.ClassDef) and item.name == 'SlamNode')
|
||||||
|
methods = [item for item in node.body if isinstance(item, ast.FunctionDef)
|
||||||
|
and item.name in ('publish_cmd_vel', '_check_teleop_timeout')]
|
||||||
|
scope = {'math': math, 'Twist': Twist, 'time': SimpleNamespace(monotonic=lambda: self.now)}
|
||||||
|
exec(compile(ast.Module(body=methods, type_ignores=[]), 'bridge-teleop', 'exec'), scope)
|
||||||
|
cls = type('Teleop', (), {name: scope[name] for name in ('publish_cmd_vel', '_check_teleop_timeout')})
|
||||||
|
self.node = cls()
|
||||||
|
self.node.read_only = False
|
||||||
|
self.node._teleop_lock = threading.Lock()
|
||||||
|
self.node._teleop_deadline = None
|
||||||
|
self.node._cmd_vel_pub = SimpleNamespace(publish=lambda msg: self.messages.append((msg.linear.x, msg.angular.z)))
|
||||||
|
|
||||||
|
def test_low_speed_and_expiry(self):
|
||||||
|
self.node.publish_cmd_vel(5.0, -5.0, True)
|
||||||
|
self.assertEqual(self.messages[-1], (0.1, -0.5))
|
||||||
|
self.now += 0.6
|
||||||
|
self.node._check_teleop_timeout()
|
||||||
|
self.assertEqual(self.messages[-1], (0.0, 0.0))
|
||||||
|
count = len(self.messages)
|
||||||
|
self.node._check_teleop_timeout()
|
||||||
|
self.assertEqual(len(self.messages), count)
|
||||||
|
|
||||||
|
def test_heartbeat_and_release(self):
|
||||||
|
self.node.publish_cmd_vel(0.1, 0, True)
|
||||||
|
self.now += 0.4
|
||||||
|
self.node.publish_cmd_vel(0.1, 0, True)
|
||||||
|
self.now += 0.2
|
||||||
|
self.node._check_teleop_timeout()
|
||||||
|
self.assertEqual(len(self.messages), 2)
|
||||||
|
self.node.publish_cmd_vel(0, 0, True)
|
||||||
|
self.assertIsNone(self.node._teleop_deadline)
|
||||||
|
|
||||||
|
def test_simulation_and_idle_navigation_unchanged(self):
|
||||||
|
self.node._check_teleop_timeout()
|
||||||
|
self.assertEqual(self.messages, [])
|
||||||
|
self.node.publish_cmd_vel(0.22, 2.84)
|
||||||
|
self.now += 2
|
||||||
|
self.node._check_teleop_timeout()
|
||||||
|
self.assertEqual(self.messages, [(0.22, 2.84)])
|
||||||
|
|
||||||
|
def test_invalid_and_readonly_rejected(self):
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
self.node.publish_cmd_vel(float('nan'), 0, True)
|
||||||
|
self.node.read_only = True
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
self.node.publish_cmd_vel(0.1, 0, True)
|
||||||
|
self.assertEqual(self.messages, [])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
const { test } = require('node:test')
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const vm = require('node:vm')
|
||||||
|
const ts = require('typescript')
|
||||||
|
|
||||||
|
const output = {}
|
||||||
|
vm.runInNewContext(ts.transpileModule(fs.readFileSync(require.resolve('../src/renderer/src/features/workspace/agentContext.ts'), 'utf8'), {
|
||||||
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }
|
||||||
|
}).outputText, { exports: output, DOMException })
|
||||||
|
const { resolveAgentContext } = output
|
||||||
|
const plain = value => JSON.parse(JSON.stringify(value))
|
||||||
|
const moduleInfo = (name, files = []) => ({ name, state: 'stopped', files })
|
||||||
|
const response = (requestedModules = [], requestedLogs = []) => ({
|
||||||
|
provider: 'QWEN', model: 'test', usage: { totalTokens: 10 },
|
||||||
|
plan: { summary: 'result', explanation: 'analysis', requestedModules, requestedLogs, actionPlan: [], commands: [], changes: [], createModule: null }
|
||||||
|
})
|
||||||
|
const options = overrides => ({
|
||||||
|
initialContext: { modules: [moduleInfo('MKMini'), moduleInfo('Control')], runtimeLogs: [] },
|
||||||
|
signal: new AbortController().signal,
|
||||||
|
...overrides
|
||||||
|
})
|
||||||
|
|
||||||
|
test('continues MKMini -> Control -> final analysis and retains earlier source files', async () => {
|
||||||
|
const inputs = []
|
||||||
|
const loads = []
|
||||||
|
const result = await resolveAgentContext(options({
|
||||||
|
requestPlan: async context => {
|
||||||
|
inputs.push(plain(context))
|
||||||
|
return [response(['MKMini']), response(['Control']), response()][inputs.length - 1]
|
||||||
|
},
|
||||||
|
loadRequestedContext: async request => {
|
||||||
|
loads.push(plain(request))
|
||||||
|
return { modules: ['MKMini', 'Control'].map(name => moduleInfo(name, request.modules.includes(name) ? [{ path: `${name}.py`, content: `${name} source` }] : [])), runtimeLogs: [] }
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
assert.equal(inputs.length, 3)
|
||||||
|
assert.deepEqual(loads, [{ modules: ['MKMini'], logs: [] }, { modules: ['Control'], logs: [] }])
|
||||||
|
assert.equal(inputs[2].modules[0].files[0].content, 'MKMini source')
|
||||||
|
assert.equal(inputs[2].modules[1].files[0].content, 'Control source')
|
||||||
|
assert.deepEqual(plain(result.plan.requestedModules), [])
|
||||||
|
assert.equal(result.usage.totalTokens, 30)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('keeps logs while loading a later source module', async () => {
|
||||||
|
let calls = 0
|
||||||
|
await resolveAgentContext(options({
|
||||||
|
requestPlan: async context => {
|
||||||
|
calls += 1
|
||||||
|
if (calls === 3) assert.equal(context.runtimeLogs[0].content, 'error log')
|
||||||
|
return [response([], ['Control']), response(['Control']), response()][calls - 1]
|
||||||
|
},
|
||||||
|
loadRequestedContext: async request => ({
|
||||||
|
modules: [moduleInfo('Control', request.modules.length ? [{ path: 'node.py', content: 'code' }] : [])],
|
||||||
|
runtimeLogs: request.logs.length ? [{ moduleName: 'Control', state: 'running', content: 'error log' }] : []
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
assert.equal(calls, 3)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects repeated requests instead of returning a stalled plan', async () => {
|
||||||
|
let loads = 0
|
||||||
|
await assert.rejects(resolveAgentContext(options({
|
||||||
|
requestPlan: async () => response(['Control']),
|
||||||
|
loadRequestedContext: async () => { loads += 1; return { modules: [moduleInfo('Control')], runtimeLogs: [] } }
|
||||||
|
})), /반복 요청/)
|
||||||
|
assert.equal(loads, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('requests only new resources when the model also repeats an earlier module', async () => {
|
||||||
|
let count = 0
|
||||||
|
const loads = []
|
||||||
|
await resolveAgentContext(options({
|
||||||
|
requestPlan: async () => [response(['MKMini']), response(['MKMini', 'Control']), response()][count++],
|
||||||
|
loadRequestedContext: async request => { loads.push(plain(request)); return { modules: request.modules.map(name => moduleInfo(name)), runtimeLogs: [] } }
|
||||||
|
}))
|
||||||
|
assert.deepEqual(loads[1], { modules: ['Control'], logs: [] })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not start another API call after cancellation during context loading', async () => {
|
||||||
|
const controller = new AbortController()
|
||||||
|
let calls = 0
|
||||||
|
await assert.rejects(resolveAgentContext(options({
|
||||||
|
signal: controller.signal,
|
||||||
|
requestPlan: async () => { calls += 1; return response(['Control']) },
|
||||||
|
loadRequestedContext: async () => { controller.abort(); return { modules: [], runtimeLogs: [] } }
|
||||||
|
})), { name: 'AbortError' })
|
||||||
|
assert.equal(calls, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('bounds the number of distinct automatic context requests', async () => {
|
||||||
|
let calls = 0
|
||||||
|
await assert.rejects(resolveAgentContext(options({
|
||||||
|
requestPlan: async () => response([`Module${calls++}`]),
|
||||||
|
loadRequestedContext: async request => ({ modules: request.modules.map(name => moduleInfo(name)), runtimeLogs: [] })
|
||||||
|
})), /5회/)
|
||||||
|
assert.equal(calls, 6)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('passes file-read failures to the model for a concrete final explanation', async () => {
|
||||||
|
let count = 0
|
||||||
|
await resolveAgentContext(options({
|
||||||
|
requestPlan: async context => {
|
||||||
|
if (count++ === 0) return response(['Control'])
|
||||||
|
assert.equal(context.modules.find(module => module.name === 'Control').fileContext.status, 'error')
|
||||||
|
return response()
|
||||||
|
},
|
||||||
|
loadRequestedContext: async () => ({ modules: [{ ...moduleInfo('Control'), fileContext: { status: 'error', message: 'permission denied' } }], runtimeLogs: [] })
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not silently truncate accumulated source context beyond API limits', async () => {
|
||||||
|
await assert.rejects(resolveAgentContext(options({
|
||||||
|
requestPlan: async () => response(['Control']),
|
||||||
|
loadRequestedContext: async () => ({ modules: [moduleInfo('Control', [{ path: 'large.py', content: 'x'.repeat(160_001) }])], runtimeLogs: [] })
|
||||||
|
})), /조회 한도/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not display a context-only response when no loader is available', async () => {
|
||||||
|
await assert.rejects(resolveAgentContext(options({ requestPlan: async () => response(['Control']) })), /읽을 수 없는/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('keeps a just-started module running when a stale collector supplies source files', async () => {
|
||||||
|
let calls = 0
|
||||||
|
await resolveAgentContext(options({
|
||||||
|
initialContext: { modules: [{ ...moduleInfo('Control'), state: 'running' }], runtimeLogs: [] },
|
||||||
|
requestPlan: async context => {
|
||||||
|
if (calls++ === 0) return response(['Control'])
|
||||||
|
assert.equal(context.modules[0].state, 'running')
|
||||||
|
assert.equal(context.modules[0].files[0].content, 'code')
|
||||||
|
return response()
|
||||||
|
},
|
||||||
|
loadRequestedContext: async () => ({ modules: [moduleInfo('Control', [{ path: 'node.py', content: 'code' }])], runtimeLogs: [] })
|
||||||
|
}))
|
||||||
|
})
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
const { test } = require('node:test')
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const vm = require('node:vm')
|
||||||
|
const ts = require('typescript')
|
||||||
|
|
||||||
|
const source = ts.createSourceFile('useAiAgent.ts', fs.readFileSync(require.resolve('../src/renderer/src/features/workspace/useAiAgent.ts'), 'utf8'), ts.ScriptTarget.Latest, true)
|
||||||
|
let payloadExpression
|
||||||
|
function visit(node) {
|
||||||
|
if (ts.isCallExpression(node) && node.expression.getText(source) === 'JSON.stringify' && node.arguments[0]?.getText(source).includes('options.attachments')) {
|
||||||
|
payloadExpression = node.getText(source)
|
||||||
|
}
|
||||||
|
ts.forEachChild(node, visit)
|
||||||
|
}
|
||||||
|
visit(source)
|
||||||
|
assert.ok(payloadExpression, 'Agent request JSON serialization must exist')
|
||||||
|
|
||||||
|
function serialize(attachments) {
|
||||||
|
return JSON.parse(vm.runInNewContext(payloadExpression, {
|
||||||
|
courseId: 'course-1', weekNumber: 3, request: '설정 위치 알려줘', options: { attachments },
|
||||||
|
context: { modules: [{ name: 'Control', files: [] }], runtimeLogs: [] }
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attachments of [undefined, []]) {
|
||||||
|
test(`omits attachments when ${attachments ? 'empty' : 'undefined'}`, () => {
|
||||||
|
const body = serialize(attachments)
|
||||||
|
assert.equal(Object.hasOwn(body, 'attachments'), false)
|
||||||
|
assert.equal(body.request, '설정 위치 알려줘')
|
||||||
|
assert.equal(body.modules[0].name, 'Control')
|
||||||
|
assert.equal(body.week, 3)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test('preserves actual attachment contents rather than silently dropping them', () => {
|
||||||
|
const attachments = [{ name: 'config.yaml', content: 'steering: ackermann' }]
|
||||||
|
assert.deepEqual(serialize(attachments).attachments, attachments)
|
||||||
|
})
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
const { test } = require('node:test')
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const vm = require('node:vm')
|
||||||
|
const ts = require('typescript')
|
||||||
|
|
||||||
|
const exportsObject = {}
|
||||||
|
vm.runInNewContext(ts.transpileModule(fs.readFileSync(require.resolve('../src/renderer/src/features/workspace/agentSessionSync.ts'), 'utf8'), {
|
||||||
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }
|
||||||
|
}).outputText, { exports: exportsObject, console: { warn() {} } })
|
||||||
|
const { createAgentSessionSync } = exportsObject
|
||||||
|
const message = (id, content = id) => ({ id, role: 'assistant', content })
|
||||||
|
const plain = value => JSON.parse(JSON.stringify(value))
|
||||||
|
const deferred = () => {
|
||||||
|
let resolve
|
||||||
|
const promise = new Promise(done => { resolve = done })
|
||||||
|
return { promise, resolve }
|
||||||
|
}
|
||||||
|
const storage = () => {
|
||||||
|
const data = new Map()
|
||||||
|
return {
|
||||||
|
getItem: key => data.get(key) ?? null,
|
||||||
|
setItem: (key, value) => data.set(key, value),
|
||||||
|
removeItem: key => data.delete(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('saves replies in order and preserves a newer result arriving during an active save', async () => {
|
||||||
|
const gate = deferred()
|
||||||
|
const calls = []
|
||||||
|
const sync = createAgentSessionSync({ storageKey: 'test', save: async batch => {
|
||||||
|
calls.push(plain(batch))
|
||||||
|
if (calls.length === 1) await gate.promise
|
||||||
|
} })
|
||||||
|
sync.enqueue([message('command', 'running')])
|
||||||
|
await Promise.resolve()
|
||||||
|
sync.enqueue([message('command', 'completed'), message('answer')])
|
||||||
|
assert.equal(calls.length, 1)
|
||||||
|
gate.resolve()
|
||||||
|
await sync.flush()
|
||||||
|
assert.equal(calls.length, 2)
|
||||||
|
assert.deepEqual(calls[1], [message('command', 'completed'), message('answer')])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('keeps a reply cached before network completion and after failed persistence', async () => {
|
||||||
|
const disk = storage()
|
||||||
|
const sync = createAgentSessionSync({ storage: disk, storageKey: 'student-a', save: async () => { throw Error('offline') } })
|
||||||
|
sync.enqueue([message('answer')])
|
||||||
|
assert.deepEqual(JSON.parse(disk.getItem('student-a')), [message('answer')])
|
||||||
|
await sync.flush()
|
||||||
|
assert.ok(sync.getError())
|
||||||
|
const batches = []
|
||||||
|
const reopened = createAgentSessionSync({ storage: disk, storageKey: 'student-a', save: async batch => { batches.push(plain(batch)) } })
|
||||||
|
assert.deepEqual(plain(reopened.restore([{ id: 'question', role: 'user', content: '?' }])), [
|
||||||
|
{ id: 'question', role: 'user', content: '?' }, message('answer')
|
||||||
|
])
|
||||||
|
await reopened.flush()
|
||||||
|
assert.deepEqual(batches, [[message('answer')]])
|
||||||
|
assert.equal(disk.getItem('student-a'), null)
|
||||||
|
assert.equal(reopened.getError(), null)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not overwrite a new reply with an older history response', async () => {
|
||||||
|
const sync = createAgentSessionSync({ storageKey: 'test', save: async () => {} })
|
||||||
|
sync.enqueue([message('answer', 'new answer')])
|
||||||
|
await sync.flush()
|
||||||
|
assert.deepEqual(plain(sync.restore([message('answer', 'old answer')])), [message('answer', 'new answer')])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('saves only modified messages even when restored history exceeds 100 messages', async () => {
|
||||||
|
const batches = []
|
||||||
|
const sync = createAgentSessionSync({ storageKey: 'test', save: async batch => { batches.push(plain(batch)) } })
|
||||||
|
const history = Array.from({ length: 150 }, (_, index) => message(`message-${index}`))
|
||||||
|
sync.restore(history)
|
||||||
|
sync.enqueue([...history, message('new-answer')])
|
||||||
|
await sync.flush()
|
||||||
|
assert.deepEqual(batches, [[message('new-answer')]])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('chunks a large backlog below the server message-count limit', async () => {
|
||||||
|
const batches = []
|
||||||
|
const sync = createAgentSessionSync({ storageKey: 'test', save: async batch => { batches.push(plain(batch)) } })
|
||||||
|
sync.enqueue(Array.from({ length: 205 }, (_, index) => message(`message-${index}`)))
|
||||||
|
await sync.flush()
|
||||||
|
assert.deepEqual(batches.map(batch => batch.length), [100, 100, 5])
|
||||||
|
assert.equal(new Set(batches.flat().map(item => item.id)).size, 205)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('leaving the view does not cancel an accepted write', async () => {
|
||||||
|
const gate = deferred()
|
||||||
|
const disk = storage()
|
||||||
|
let saved = false
|
||||||
|
const sync = createAgentSessionSync({ storage: disk, storageKey: 'test', save: async () => { await gate.promise; saved = true } })
|
||||||
|
const unsubscribe = sync.subscribe(() => {})
|
||||||
|
sync.enqueue([message('answer')])
|
||||||
|
unsubscribe()
|
||||||
|
gate.resolve()
|
||||||
|
await sync.flush()
|
||||||
|
assert.equal(saved, true)
|
||||||
|
assert.equal(disk.getItem('test'), null)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('archives after outstanding writes complete and clears cached replies', async () => {
|
||||||
|
const gate = deferred()
|
||||||
|
const order = []
|
||||||
|
const disk = storage()
|
||||||
|
const sync = createAgentSessionSync({ storage: disk, storageKey: 'test', save: async () => { await gate.promise; order.push('saved') } })
|
||||||
|
sync.enqueue([message('answer')])
|
||||||
|
const clearing = sync.clear(async () => { order.push('archived') })
|
||||||
|
gate.resolve()
|
||||||
|
await clearing
|
||||||
|
assert.deepEqual(order, ['saved', 'archived'])
|
||||||
|
assert.deepEqual(plain(sync.restore([])), [])
|
||||||
|
assert.equal(disk.getItem('test'), null)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('pending replies stay isolated by student and course scope', async () => {
|
||||||
|
const disk = storage()
|
||||||
|
const first = createAgentSessionSync({ storage: disk, storageKey: 'student-a:course-1', save: async () => { throw Error('offline') } })
|
||||||
|
first.enqueue([message('private-answer')])
|
||||||
|
await first.flush()
|
||||||
|
const second = createAgentSessionSync({ storage: disk, storageKey: 'student-b:course-1', save: async () => {} })
|
||||||
|
assert.deepEqual(plain(second.restore([])), [])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('late command updates cannot recreate a conversation while it is being cleared', async () => {
|
||||||
|
const archive = deferred()
|
||||||
|
const calls = []
|
||||||
|
const sync = createAgentSessionSync({ storageKey: 'test', save: async batch => { calls.push(plain(batch)) } })
|
||||||
|
sync.enqueue([message('answer')])
|
||||||
|
await sync.flush()
|
||||||
|
const clearing = sync.clear(() => archive.promise)
|
||||||
|
sync.enqueue([message('answer', 'late command result')])
|
||||||
|
archive.resolve()
|
||||||
|
await clearing
|
||||||
|
await sync.flush()
|
||||||
|
assert.equal(calls.length, 1)
|
||||||
|
assert.deepEqual(plain(sync.restore([])), [])
|
||||||
|
})
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
const { test } = require('node:test')
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const path = require('node:path')
|
||||||
|
const ts = require('typescript')
|
||||||
|
|
||||||
|
const source = ts.createSourceFile('index.ts', fs.readFileSync(path.join(__dirname, '../src/main/index.ts'), 'utf8'), ts.ScriptTarget.Latest, true)
|
||||||
|
const names = ['buildFastDdsPeerConfig', 'parseCameraLanAddresses', 'cameraLanEnabled', 'rosNetworkEnabled', 'prepareRemoteRosNetworkConfig']
|
||||||
|
const code = source.statements.filter(node => ts.isFunctionDeclaration(node) && names.includes(node.name?.text)).map(node => node.getText(source)).join('\n')
|
||||||
|
function load(overrides = {}) {
|
||||||
|
const dependencies = {
|
||||||
|
parseRosDomainId: value => Number(value ?? 0),
|
||||||
|
readLocalTailscaleIpv4: async () => '100.64.0.10',
|
||||||
|
readRemoteTailscaleIpv4: async () => '100.64.0.20',
|
||||||
|
runSshOutput: async () => JSON.stringify([{ addr_info: [{ family: 'inet', scope: 'global', local: '192.168.1.20' }] }]),
|
||||||
|
runSsh: async () => {},
|
||||||
|
quoteShellSingle: value => "'" + value.replaceAll("'", "'\\''") + "'",
|
||||||
|
expandRemotePathVar: () => [],
|
||||||
|
encodeBase64: value => Buffer.from(value).toString('base64'),
|
||||||
|
...overrides,
|
||||||
|
}
|
||||||
|
return new Function(...Object.keys(dependencies), ts.transpile(code, { target: ts.ScriptTarget.ES2022 }) + `;return {${names.join(',')}}`)(...Object.values(dependencies))
|
||||||
|
}
|
||||||
|
|
||||||
|
test('LAN discovery adds physical addresses and multicast while keeping Tailscale peers', () => {
|
||||||
|
const { buildFastDdsPeerConfig } = load()
|
||||||
|
const xml = buildFastDdsPeerConfig('100.64.0.10', '100.64.0.20', '77', ['192.168.1.20'])
|
||||||
|
const whitelist = xml.match(/<interfaceWhiteList>(.*?)<\/interfaceWhiteList>/s)[1]
|
||||||
|
for (const address of ['127.0.0.1', '100.64.0.20', '192.168.1.20']) assert.ok(whitelist.includes(address))
|
||||||
|
assert.ok(xml.includes('<address>100.64.0.10</address>'))
|
||||||
|
assert.ok(xml.includes('<address>239.255.0.1</address><port>26650</port>'))
|
||||||
|
assert.ok(xml.includes('<metatrafficMulticastLocatorList>'))
|
||||||
|
const original = buildFastDdsPeerConfig('100.64.0.10', '100.64.0.20', '77')
|
||||||
|
assert.ok(!original.includes('192.168.1.20'))
|
||||||
|
assert.ok(!original.includes('239.255.0.1'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('only the Yahboom camera opts in', () => {
|
||||||
|
const { cameraLanEnabled } = load()
|
||||||
|
assert.equal(cameraLanEnabled('[lms-nav2-yahboom-camera]yahboom_camera_v1.0.0_ws'), true)
|
||||||
|
for (const role of ['vehicle', 'agent', 'control', 'slam', 'planning']) {
|
||||||
|
assert.equal(cameraLanEnabled(`[lms-nav2-yahboom-${role}]yahboom_${role}_v1.0.0_ws`), false)
|
||||||
|
}
|
||||||
|
assert.equal(cameraLanEnabled('[lms-nav2-custom]camera_v1.0.0_ws'), false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('physical interface responses are parsed, validated and deduplicated', () => {
|
||||||
|
const { parseCameraLanAddresses } = load()
|
||||||
|
const output = JSON.stringify([{ addr_info: [
|
||||||
|
{ family: 'inet', scope: 'global', local: '192.168.1.20' },
|
||||||
|
{ family: 'inet', scope: 'global', local: '999.1.1.1' },
|
||||||
|
{ family: 'inet6', scope: 'global', local: 'fe80::1' },
|
||||||
|
] }])
|
||||||
|
assert.deepEqual(parseCameraLanAddresses(output + '\n' + output), ['192.168.1.20'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('camera config uses its own path and LAN works without ROS platform linking', async () => {
|
||||||
|
let script = ''
|
||||||
|
const api = load({
|
||||||
|
readLocalTailscaleIpv4: async () => { throw new Error('Must not require local Tailscale') },
|
||||||
|
readRemoteTailscaleIpv4: async () => { throw new Error('Must not require remote Tailscale') },
|
||||||
|
runSsh: async (_target, command) => { script = command },
|
||||||
|
})
|
||||||
|
assert.equal(await api.prepareRemoteRosNetworkConfig({}, '/runtime', { enabled: false }, '77'), null)
|
||||||
|
const result = await api.prepareRemoteRosNetworkConfig({}, '/runtime', { enabled: false }, '77', true)
|
||||||
|
assert.ok(result.endsWith('-camera-lan.xml'))
|
||||||
|
const encoded = script.match(/printf %s '([^']+)'/)[1]
|
||||||
|
const xml = Buffer.from(encoded, 'base64').toString()
|
||||||
|
assert.ok(xml.includes('192.168.1.20'))
|
||||||
|
assert.ok(xml.includes('239.255.0.1'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('linked camera retains both networks and does not overwrite restricted config', async () => {
|
||||||
|
const api = load()
|
||||||
|
const restricted = await api.prepareRemoteRosNetworkConfig({}, '/runtime', { enabled: true }, '77')
|
||||||
|
const camera = await api.prepareRemoteRosNetworkConfig({}, '/runtime', { enabled: true }, '77', true)
|
||||||
|
assert.notEqual(camera, restricted)
|
||||||
|
assert.ok(camera.includes('100-64-0-10'))
|
||||||
|
const noLan = load({ runSshOutput: async () => '' })
|
||||||
|
await assert.rejects(noLan.prepareRemoteRosNetworkConfig({}, '/runtime', { enabled: true }, '77', true), /IPv4/)
|
||||||
|
})
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
const { test } = require('node:test')
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const vm = require('node:vm')
|
||||||
|
const ts = require('typescript')
|
||||||
|
const output = {}
|
||||||
|
vm.runInNewContext(ts.transpileModule(fs.readFileSync(require.resolve('../src/renderer/src/components/chatAttachments.ts'), 'utf8'), {
|
||||||
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }
|
||||||
|
}).outputText, { exports: output, TextDecoder })
|
||||||
|
const { readChatAttachments } = output
|
||||||
|
const file = (name, content) => new File([content], name)
|
||||||
|
|
||||||
|
test('reads source/config/log files and preserves Korean UTF-8 text', async () => {
|
||||||
|
const files = await readChatAttachments([file('control.py', '# 조향 설정'), file('config.yaml', 'robot: mkmini'), file('runtime.log', 'started')], [])
|
||||||
|
assert.equal(files[0].content, '# 조향 설정')
|
||||||
|
assert.equal(files[2].name, 'runtime.log')
|
||||||
|
})
|
||||||
|
test('rejects unsupported image/PDF files', async () => {
|
||||||
|
await assert.rejects(readChatAttachments([file('photo.png', 'image')], []), /이미지와 PDF/)
|
||||||
|
await assert.rejects(readChatAttachments([file('guide.pdf', 'pdf')], []), /이미지와 PDF/)
|
||||||
|
})
|
||||||
|
test('rejects more than five attachments including existing ones', async () => {
|
||||||
|
await assert.rejects(readChatAttachments([file('a.txt', 'a')], Array(5).fill({ name: 'b.txt', content: '' })), /최대 5개/)
|
||||||
|
})
|
||||||
|
test('rejects binary and non-UTF8 content even under a text extension', async () => {
|
||||||
|
await assert.rejects(readChatAttachments([file('a.txt', 'a\0b')], []), /바이너리/)
|
||||||
|
await assert.rejects(readChatAttachments([file('a.txt', new Uint8Array([255, 254]))], []), /UTF-8/)
|
||||||
|
})
|
||||||
|
test('rejects oversized files instead of silently truncating', async () => {
|
||||||
|
await assert.rejects(readChatAttachments([file('a.txt', 'a'.repeat(100_001))], []), /100KB/)
|
||||||
|
await assert.rejects(readChatAttachments([file('a.txt', 'a'.repeat(50_001))], []), /5만 자/)
|
||||||
|
})
|
||||||
|
test('rejects aggregate attachment context above 100000 characters', async () => {
|
||||||
|
await assert.rejects(readChatAttachments([file('a.txt', 'a'.repeat(50_000)), file('b.txt', 'a'.repeat(50_000))], []), /10만 자/)
|
||||||
|
})
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
const { test } = require('node:test')
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const vm = require('node:vm')
|
||||||
|
const ts = require('typescript')
|
||||||
|
const React = require('react')
|
||||||
|
const { renderToStaticMarkup } = require('react-dom/server')
|
||||||
|
|
||||||
|
function compile(file, overrides = {}, globals = {}) {
|
||||||
|
const exports = {}
|
||||||
|
const source = fs.readFileSync(require.resolve(file), 'utf8')
|
||||||
|
const code = ts.transpileModule(source, {
|
||||||
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX }
|
||||||
|
}).outputText
|
||||||
|
vm.runInNewContext(code, { exports, require: name => overrides[name] ?? require(name), ...globals })
|
||||||
|
return exports
|
||||||
|
}
|
||||||
|
|
||||||
|
const i18n = { t: key => key, useLocaleRender() {} }
|
||||||
|
const { ConfirmStatusModal } = compile('../src/renderer/src/components/AppStatusModals.tsx', { '../i18n': i18n })
|
||||||
|
const QaInputBox = () => null
|
||||||
|
|
||||||
|
function elements(root, predicate) {
|
||||||
|
if (!React.isValidElement(root)) return []
|
||||||
|
return [...(predicate(root) ? [root] : []), ...React.Children.toArray(root.props.children).flatMap(child => elements(child, predicate))]
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup(overrides = {}) {
|
||||||
|
const state = []
|
||||||
|
const effects = []
|
||||||
|
let index = 0
|
||||||
|
let cleared = 0
|
||||||
|
const modeChanges = []
|
||||||
|
const panel = compile('../src/renderer/src/features/workspace/WorkspaceQaPanel.tsx', {
|
||||||
|
'../../i18n': i18n,
|
||||||
|
'../../components/AppStatusModals': { ConfirmStatusModal },
|
||||||
|
'../../components/ChatWidgets': { QaInputBox },
|
||||||
|
'react-dom': { createPortal: child => child },
|
||||||
|
react: {
|
||||||
|
...React,
|
||||||
|
useEffect(effect) { effects.push(effect) },
|
||||||
|
useState(initial) {
|
||||||
|
const slot = index++
|
||||||
|
if (!(slot in state)) state[slot] = initial
|
||||||
|
return [state[slot], value => { state[slot] = value }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { document: { body: {} } }).default
|
||||||
|
const props = {
|
||||||
|
dark: true, workspaceActive: true, qaMode: 'ai', agentEnabled: true, visibleAiMessages: [{ id: '1', content: 'hello' }],
|
||||||
|
visibleTeacherMessages: [], agentMessages: [], workspaceChatScrollRef: { current: null },
|
||||||
|
onModeChange(mode) { modeChanges.push(mode) },
|
||||||
|
onClearConversation() { cleared++ }, ...overrides
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
render() { index = 0; effects.length = 0; return panel(props) },
|
||||||
|
cleared: () => cleared,
|
||||||
|
runEffects() { effects.forEach(effect => effect()) },
|
||||||
|
modeChanges
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const qaMode of ['ai', 'agent', 'teacher']) {
|
||||||
|
test(`${qaMode} history is deleted only after shared modal confirmation`, () => {
|
||||||
|
const harness = setup({ qaMode, agentMessages: [{ id: 'a', content: 'answer' }], visibleTeacherMessages: [{ id: 't', content: 'answer' }] })
|
||||||
|
const input = tree => elements(tree, node => node.type === QaInputBox)[0]
|
||||||
|
const modal = tree => elements(tree, node => node.type === ConfirmStatusModal)[0]
|
||||||
|
assert.equal(modal(harness.render()), undefined)
|
||||||
|
input(harness.render()).props.onClearConversation()
|
||||||
|
assert.equal(harness.cleared(), 0)
|
||||||
|
modal(harness.render()).props.onCancel()
|
||||||
|
assert.equal(modal(harness.render()), undefined)
|
||||||
|
assert.equal(harness.cleared(), 0)
|
||||||
|
input(harness.render()).props.onClearConversation()
|
||||||
|
modal(harness.render()).props.onConfirm()
|
||||||
|
assert.equal(harness.cleared(), 1)
|
||||||
|
assert.equal(modal(harness.render()), undefined)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test('context menu deletion also requests confirmation', () => {
|
||||||
|
const harness = setup()
|
||||||
|
const root = harness.render()
|
||||||
|
const area = elements(root, node => Boolean(node.props.onContextMenu))[0]
|
||||||
|
area.props.onContextMenu({ target: { closest: () => null }, preventDefault() {}, clientX: 10, clientY: 10 })
|
||||||
|
const clear = elements(harness.render(), node => node.type === 'button' && node.props.children === '대화내역 삭제')[0]
|
||||||
|
clear.props.onClick()
|
||||||
|
assert.equal(harness.cleared(), 0)
|
||||||
|
assert.equal(elements(harness.render(), node => node.type === ConfirmStatusModal).length, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const props of [{ inputBusy: true }, { qaMessagesLoading: true }, { visibleAiMessages: [] }]) {
|
||||||
|
test(`does not open deletion confirmation when disabled: ${JSON.stringify(props)}`, () => {
|
||||||
|
const harness = setup(props)
|
||||||
|
const input = elements(harness.render(), node => node.type === QaInputBox)[0]
|
||||||
|
assert.equal(input.props.clearDisabled, true)
|
||||||
|
input.props.onClearConversation()
|
||||||
|
assert.equal(elements(harness.render(), node => node.type === ConfirmStatusModal).length, 0)
|
||||||
|
assert.equal(harness.cleared(), 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const dark of [false, true]) {
|
||||||
|
test(`shared confirmation renders cancel left and delete right (${dark ? 'dark' : 'light'})`, () => {
|
||||||
|
const harness = setup({ dark })
|
||||||
|
elements(harness.render(), node => node.type === QaInputBox)[0].props.onClearConversation()
|
||||||
|
const modal = elements(harness.render(), node => node.type === ConfirmStatusModal)[0]
|
||||||
|
assert.equal(modal.props.dark, dark)
|
||||||
|
assert.match(modal.props.overlayClassName, /fixed.*z-\[180\]/)
|
||||||
|
const html = renderToStaticMarkup(modal)
|
||||||
|
assert.match(html, /취소<\/button><button[^>]*>삭제<\/button>/)
|
||||||
|
assert.match(html, /bg-\[#d14b4b\]/)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test('input no longer opens a native confirmation dialog', () => {
|
||||||
|
const source = fs.readFileSync(require.resolve('../src/renderer/src/components/ChatWidgets.tsx'), 'utf8')
|
||||||
|
assert.doesNotMatch(source, /window\.confirm/)
|
||||||
|
assert.match(source, /onClick=\{onClearConversation\}/)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const dark of [false, true]) {
|
||||||
|
for (const agentEnabled of [false, true]) {
|
||||||
|
test(`agent availability controls tab count and layout (${dark ? 'dark' : 'light'}, enabled=${agentEnabled})`, () => {
|
||||||
|
const harness = setup({ dark, agentEnabled })
|
||||||
|
const grids = elements(harness.render(), node => node.props.className?.includes('mt-3 grid'))
|
||||||
|
assert.equal(grids.length, 1)
|
||||||
|
const tabs = elements(grids[0], node => node.type === 'button')
|
||||||
|
assert.deepEqual(tabs.map(tab => tab.props.children[1].props.children), agentEnabled ? ['AI', '에이전트', '강사'] : ['AI', '강사'])
|
||||||
|
assert.ok(grids[0].props.className.includes(agentEnabled ? 'grid-cols-3' : 'grid-cols-2'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('returns to AI when the selected agent is disabled after status loading', () => {
|
||||||
|
const harness = setup({ qaMode: 'agent', agentEnabled: false, agentStatusLoading: false, minimized: true })
|
||||||
|
harness.render()
|
||||||
|
harness.runEffects()
|
||||||
|
assert.deepEqual(harness.modeChanges, ['ai'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not reset the selected agent while status is still loading', () => {
|
||||||
|
const harness = setup({ qaMode: 'agent', agentEnabled: false, agentStatusLoading: true, minimized: true })
|
||||||
|
harness.render()
|
||||||
|
harness.runEffects()
|
||||||
|
assert.deepEqual(harness.modeChanges, [])
|
||||||
|
})
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
const { test } = require('node:test')
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const vm = require('node:vm')
|
||||||
|
const ts = require('typescript')
|
||||||
|
const React = require('react')
|
||||||
|
const { renderToStaticMarkup } = require('react-dom/server')
|
||||||
|
|
||||||
|
const source = fs.readFileSync(require.resolve('../src/renderer/src/components/AppStatusModals.tsx'), 'utf8')
|
||||||
|
const compiled = ts.transpileModule(source, {
|
||||||
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX }
|
||||||
|
}).outputText
|
||||||
|
|
||||||
|
function loadModals() {
|
||||||
|
const exports = {}
|
||||||
|
vm.runInNewContext(compiled, {
|
||||||
|
exports,
|
||||||
|
require: name => name === '../i18n' ? { t: key => key, useLocaleRender() {} } : require(name)
|
||||||
|
})
|
||||||
|
return exports
|
||||||
|
}
|
||||||
|
|
||||||
|
function buttons(element) {
|
||||||
|
if (!React.isValidElement(element)) return []
|
||||||
|
if (element.type === 'button') return [element]
|
||||||
|
return React.Children.toArray(element.props.children).flatMap(buttons)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const dark of [false, true]) {
|
||||||
|
test(`extension action stays on the right in both dialogs (${dark ? 'dark' : 'light'})`, () => {
|
||||||
|
const { StatusNoticeModal, ControlExtensionConfirmModal } = loadModals()
|
||||||
|
let confirmed = 0
|
||||||
|
let extended = 0
|
||||||
|
const notice = StatusNoticeModal({
|
||||||
|
overlayClassName: 'overlay', dark, tone: 'clock', title: '제어 시간 안내', message: '제어 시간이 곧 종료됩니다.',
|
||||||
|
actionLabel: '연장', onAction: () => { extended++ }, onConfirm: () => { confirmed++ }
|
||||||
|
})
|
||||||
|
const noticeButtons = buttons(notice)
|
||||||
|
assert.deepEqual(noticeButtons.map(button => button.props.children), ['확인', '연장'])
|
||||||
|
assert.match(noticeButtons[1].props.className, /bg-\[#6f4df6\]/)
|
||||||
|
assert.doesNotMatch(noticeButtons[0].props.className, /bg-\[#6f4df6\]/)
|
||||||
|
noticeButtons[0].props.onClick()
|
||||||
|
noticeButtons[1].props.onClick()
|
||||||
|
assert.equal(confirmed, 1)
|
||||||
|
assert.equal(extended, 1)
|
||||||
|
|
||||||
|
const confirmation = ControlExtensionConfirmModal({ overlayClassName: 'overlay', dark, platformName: 'MK-mini', onCancel() {}, onConfirm() {} })
|
||||||
|
assert.deepEqual(buttons(confirmation).map(button => button.props.children), ['취소', '연장'])
|
||||||
|
assert.match(buttons(confirmation)[1].props.className, /bg-\[#6f4df6\]/)
|
||||||
|
assert.match(renderToStaticMarkup(confirmation), /취소<\/button><button[^>]*>연장<\/button>/)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test('notices without an extension action retain their single primary confirmation button', () => {
|
||||||
|
const { StatusNoticeModal } = loadModals()
|
||||||
|
const notice = StatusNoticeModal({ overlayClassName: 'overlay', dark: true, tone: 'warning', title: '안내', message: '메시지', onConfirm() {} })
|
||||||
|
assert.equal(buttons(notice).length, 1)
|
||||||
|
assert.equal(buttons(notice)[0].props.children, '확인')
|
||||||
|
assert.match(buttons(notice)[0].props.className, /bg-\[#6f4df6\]/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('the extension confirmation keeps the right button disabled while submitting', () => {
|
||||||
|
const { ControlExtensionConfirmModal } = loadModals()
|
||||||
|
const confirmation = ControlExtensionConfirmModal({ overlayClassName: 'overlay', dark: true, platformName: 'MK-mini', submitting: true, onCancel() {}, onConfirm() {} })
|
||||||
|
assert.deepEqual(buttons(confirmation).map(button => button.props.children), ['취소', '연장 중'])
|
||||||
|
assert.ok(buttons(confirmation).every(button => button.props.disabled))
|
||||||
|
})
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
const { test } = require('node:test')
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const path = require('node:path')
|
||||||
|
const root = path.resolve(__dirname, '../src')
|
||||||
|
const app = fs.readFileSync(path.join(root, 'renderer/src/App.tsx'), 'utf8')
|
||||||
|
const view = fs.readFileSync(path.join(root, 'renderer/src/features/workspace/ModuleListView.tsx'), 'utf8')
|
||||||
|
|
||||||
|
test('remote creation and Git import are enabled while raw folder import remains local', () => {
|
||||||
|
assert.match(app, /allowCustomModuleCreation=\{true\}/)
|
||||||
|
assert.match(app, /allowCustomModuleImport=\{true\}/)
|
||||||
|
assert.match(app, /remoteGitImport=\{remoteWorkspace \? \{ workspaceRoot: localVisualizationWorkspaceRoot, onImport: importGitModule \} : undefined\}/)
|
||||||
|
assert.match(view, /remoteGitImport \? <RemoteModuleImportDialog/)
|
||||||
|
assert.match(app, /const importCustomModule[\s\S]*?if \(remoteWorkspace\) throw new Error/)
|
||||||
|
const importButton = view.slice(view.indexOf('aria-label={t("모듈 불러오기")}'), view.indexOf('{deleteConfirmModule &&'))
|
||||||
|
assert.match(importButton, /disabled=\{!allowCustomModuleImport\}/)
|
||||||
|
assert.doesNotMatch(importButton, /allowCustomModuleCreation/)
|
||||||
|
assert.match(view, /allowCustomModuleImport && moduleImportDialogOpen/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('new module dialog requests images from the current workspace', () => {
|
||||||
|
assert.match(app, /loadDockerImages=\{\(\) => remoteWorkspace\s*\? window\.remoteWorkspace\.modules\.listDockerImages\(getRemoteWorkspaceTarget\(\)\)/)
|
||||||
|
const dialog = view.slice(view.indexOf('function ModuleAddDialog('), view.indexOf('function ModuleImportDialog('))
|
||||||
|
assert.match(dialog, /void loadDockerImages\(\)/)
|
||||||
|
assert.doesNotMatch(dialog, /window\.localWorkspace/)
|
||||||
|
const preload = fs.readFileSync(path.join(root, 'preload/index.ts'), 'utf8')
|
||||||
|
const main = fs.readFileSync(path.join(root, 'main/index.ts'), 'utf8')
|
||||||
|
assert.match(preload, /invoke\('remote-workspace:modules:docker-images', target\)/)
|
||||||
|
assert.match(main, /handle\('remote-workspace:modules:docker-images'/)
|
||||||
|
})
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
const { test } = require('node:test')
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const os = require('node:os')
|
||||||
|
const path = require('node:path')
|
||||||
|
const vm = require('node:vm')
|
||||||
|
const ts = require('typescript')
|
||||||
|
const exportsObject = {}
|
||||||
|
const source = fs.readFileSync(path.resolve(__dirname, '../src/main/dailyLog.ts'), 'utf8')
|
||||||
|
vm.runInNewContext(ts.transpileModule(source, {
|
||||||
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }
|
||||||
|
}).outputText, { exports: exportsObject, require })
|
||||||
|
const { createDailyLog, redactLog } = exportsObject
|
||||||
|
|
||||||
|
test('appends and rotates by local date, retaining unrelated files', (t) => {
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'lms-log-test-'))
|
||||||
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }))
|
||||||
|
fs.writeFileSync(path.join(root, 'renderer.log'), 'existing log')
|
||||||
|
let date = new Date(2026, 8, 29, 23, 59)
|
||||||
|
const log = createDailyLog([root], () => date)
|
||||||
|
log.write('test', 'first')
|
||||||
|
log.write('test', 'second')
|
||||||
|
const oldPath = log.path()
|
||||||
|
assert.match(oldPath, /lms-2026-09-29\.log$/)
|
||||||
|
assert.match(fs.readFileSync(oldPath, 'utf8'), /first[\s\S]*second/)
|
||||||
|
assert.equal(fs.statSync(oldPath).mode & 0o777, 0o600)
|
||||||
|
date = new Date(2026, 8, 30, 0, 1)
|
||||||
|
log.write('test', 'next day')
|
||||||
|
assert.match(log.path(), /lms-2026-09-30\.log$/)
|
||||||
|
assert.equal(fs.existsSync(oldPath), false)
|
||||||
|
assert.equal(fs.readFileSync(path.join(root, 'renderer.log'), 'utf8'), 'existing log')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('falls back when the primary directory is unusable', (t) => {
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'lms-log-test-'))
|
||||||
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }))
|
||||||
|
const blocked = path.join(root, 'file')
|
||||||
|
fs.writeFileSync(blocked, '')
|
||||||
|
const log = createDailyLog([blocked, path.join(root, 'fallback')])
|
||||||
|
log.write('test', 'fallback')
|
||||||
|
assert.equal(path.dirname(log.path()), path.join(root, 'fallback'))
|
||||||
|
const unavailable = createDailyLog([blocked])
|
||||||
|
assert.doesNotThrow(() => unavailable.write('test', 'ignored'))
|
||||||
|
assert.equal(unavailable.path(), '')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('redacts common authentication fields and credential URLs', () => {
|
||||||
|
const result = redactLog('https://oauth2:secret123@example.com/repo.git Bearer secret456 --handoff-token=secret789 ?password=secret000 {"accessToken":"secret111"} password=secret222')
|
||||||
|
for (const secret of ['secret123', 'secret456', 'secret789', 'secret000', 'secret111', 'secret222']) {
|
||||||
|
assert.equal(result.includes(secret), false)
|
||||||
|
}
|
||||||
|
assert.match(result, /example.com\/repo.git/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('bootstrap logs dependency loading failures before exiting', async () => {
|
||||||
|
const events = []
|
||||||
|
const bootstrap = fs.readFileSync(path.resolve(__dirname, '../src/main/bootstrap.ts'), 'utf8')
|
||||||
|
const code = ts.transpileModule(bootstrap, {
|
||||||
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }
|
||||||
|
}).outputText
|
||||||
|
vm.runInNewContext(code, {
|
||||||
|
exports: {}, Error,
|
||||||
|
require: (name) => {
|
||||||
|
if (name === 'electron') return { app: { exit: (code) => events.push(['exit', code]) } }
|
||||||
|
if (name === './appLogging') {
|
||||||
|
events.push(['logger-ready'])
|
||||||
|
return { appLog: { write: (...args) => events.push(args) } }
|
||||||
|
}
|
||||||
|
if (name === './index') throw new Error('test native module failure')
|
||||||
|
throw new Error(`Unexpected dependency: ${name}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await new Promise((resolve) => setImmediate(resolve))
|
||||||
|
assert.equal(events[0][0], 'logger-ready')
|
||||||
|
assert.equal(events[1][0], 'startup-error')
|
||||||
|
assert.match(events[1][1], /test native module failure/)
|
||||||
|
assert.deepEqual(events[2], ['exit', 1])
|
||||||
|
})
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
const { test } = require('node:test')
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs/promises')
|
||||||
|
const path = require('node:path')
|
||||||
|
const os = require('node:os')
|
||||||
|
const vm = require('node:vm')
|
||||||
|
const ts = require('typescript')
|
||||||
|
const { execFile } = require('node:child_process')
|
||||||
|
const { promisify } = require('node:util')
|
||||||
|
const exec = promisify(execFile)
|
||||||
|
const root = path.resolve(__dirname, '..')
|
||||||
|
|
||||||
|
async function loadHelpers() {
|
||||||
|
const exports = {}
|
||||||
|
const code = ts.transpileModule(await fs.readFile(path.join(root, 'src/main/gitModuleImport.ts'), 'utf8'), { compilerOptions: { module: ts.ModuleKind.CommonJS } }).outputText
|
||||||
|
vm.runInNewContext(code, { exports, require, URL })
|
||||||
|
return exports
|
||||||
|
}
|
||||||
|
|
||||||
|
test('repository addresses strip credentials and reject local/helper protocols', async () => {
|
||||||
|
const api = await loadHelpers()
|
||||||
|
assert.equal(api.repositoryAddress('https://user:secret@example.com/team/module.git?token=secret'), 'https://example.com/team/module.git')
|
||||||
|
assert.equal(api.repositoryAddress('git@example.com:team/module.git'), 'ssh://git@example.com/team/module.git')
|
||||||
|
for (const url of ['file:///tmp/repo', 'ext::command', '/tmp/repo']) assert.throws(() => api.repositoryAddress(url))
|
||||||
|
assert.equal(api.remoteBranches('a'.repeat(40) + '\trefs/heads/main\ninvalid')[0].name, 'main')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('local module discovery ignores malformed folders and rejects symlink escapes', async () => {
|
||||||
|
const api = await loadHelpers()
|
||||||
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'lms-git-list-'))
|
||||||
|
try {
|
||||||
|
const workspace = path.join(dir, 'workspace')
|
||||||
|
const folder = '[lms-nav2-custom]demo_v1.0.0_ws'
|
||||||
|
await fs.mkdir(path.join(workspace, folder), { recursive: true })
|
||||||
|
await fs.writeFile(path.join(workspace, folder, '.autonomy-studio-module.json'), JSON.stringify({ sourceType: 'CUSTOM', name: 'Demo', category: 'Control', packageName: 'demo', launchFile: 'demo.launch.py' }))
|
||||||
|
assert.equal((await api.listLocalGitModules(workspace))[0].name, 'Demo')
|
||||||
|
await fs.symlink(dir, path.join(workspace, '[lms-nav2-custom]escape_v1.0.0_ws'))
|
||||||
|
await assert.rejects(api.localGitModulePath(workspace, '[lms-nav2-custom]escape_v1.0.0_ws'), /outside/)
|
||||||
|
await assert.rejects(api.localGitModulePath(workspace, '../escape'), /Invalid/)
|
||||||
|
assert.equal((await api.listLocalGitModules(path.join(dir, 'missing'))).length, 0)
|
||||||
|
} finally { await fs.rm(dir, { recursive: true, force: true }) }
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Git clone imports only the selected commit, refuses overwrite and cleans failed staging', async () => {
|
||||||
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'lms-git-import-'))
|
||||||
|
const repo = path.join(dir, 'source')
|
||||||
|
const workspace = path.join(dir, 'remote')
|
||||||
|
const folderName = '[lms-nav2-custom]demo_v1.0.0_ws'
|
||||||
|
const git = (...args) => exec('git', args, { cwd: repo })
|
||||||
|
try {
|
||||||
|
await fs.mkdir(path.join(repo, 'src/demo/launch'), { recursive: true })
|
||||||
|
await fs.writeFile(path.join(repo, 'src/demo/package.xml'), '<package><name>demo</name></package>')
|
||||||
|
await fs.writeFile(path.join(repo, 'src/demo/launch/demo.launch.py'), '# launch')
|
||||||
|
for (const file of ['build.sh', 'run.sh']) await fs.writeFile(path.join(repo, file), '#!/bin/bash\nexit 0\n')
|
||||||
|
await git('init', '-b', 'main')
|
||||||
|
await git('add', '.')
|
||||||
|
await git('-c', 'user.name=Test', '-c', 'user.email=test@example.com', 'commit', '-m', 'initial')
|
||||||
|
const commit = (await git('rev-parse', 'HEAD')).stdout.trim()
|
||||||
|
await fs.writeFile(path.join(repo, 'uncommitted.txt'), 'must not transfer')
|
||||||
|
let dirty = false
|
||||||
|
let pushed = true
|
||||||
|
let requestedCommit = commit
|
||||||
|
const index = await fs.readFile(path.join(root, 'src/main/index.ts'), 'utf8')
|
||||||
|
const fn = index.slice(index.indexOf('async function importRemoteGitModule('), index.indexOf('async function transferLocalWorkspaceModuleToRemote('))
|
||||||
|
const quote = value => "'" + value.replaceAll("'", "'\\''") + "'"
|
||||||
|
const context = {
|
||||||
|
URL, Buffer,
|
||||||
|
normalizeRemoteTarget: target => target,
|
||||||
|
validateCustomModuleScaffold: input => input,
|
||||||
|
inspectLocalGitModule: async () => ({ repository: 'https://example.com/module.git', dirty, pushed, branches: [{ name: 'main', commit: requestedCommit }] }),
|
||||||
|
remoteRepositoryUrl: () => 'file://' + repo,
|
||||||
|
withGitCredential: value => value,
|
||||||
|
customModuleConfigContent: input => JSON.stringify(input),
|
||||||
|
encodeBase64: value => Buffer.from(value).toString('base64'),
|
||||||
|
quoteShellSingle: quote,
|
||||||
|
expandRemotePathVar: () => [],
|
||||||
|
runSshOutput: async (_target, script) => (await exec('bash', ['-c', script], { timeout: 15000 })).stdout,
|
||||||
|
}
|
||||||
|
vm.runInNewContext(ts.transpileModule(fn, { compilerOptions: { target: ts.ScriptTarget.ES2022 } }).outputText, context)
|
||||||
|
const input = { sourceFolder: folderName, folderName, branch: 'main', commit, name: 'Remote demo', category: 'Control', packageName: 'demo', launchFile: 'demo.launch.py', dockerImage: 'vehicle:arm64' }
|
||||||
|
const run = () => context.importRemoteGitModule({ workspaceRoot: workspace }, repo, input)
|
||||||
|
dirty = true
|
||||||
|
await assert.rejects(run(), /커밋/)
|
||||||
|
dirty = false
|
||||||
|
pushed = false
|
||||||
|
await assert.rejects(run(), /푸시/)
|
||||||
|
pushed = true
|
||||||
|
assert.equal((await run()).ok, true)
|
||||||
|
const target = path.join(workspace, folderName)
|
||||||
|
assert.equal((await exec('git', ['rev-parse', 'HEAD'], { cwd: target })).stdout.trim(), commit)
|
||||||
|
await assert.rejects(fs.stat(path.join(target, 'uncommitted.txt')), { code: 'ENOENT' })
|
||||||
|
assert.equal(JSON.parse(await fs.readFile(path.join(target, '.autonomy-studio-module.json'), 'utf8')).dockerImage, 'vehicle:arm64')
|
||||||
|
await fs.writeFile(path.join(target, 'keep.txt'), 'keep')
|
||||||
|
await assert.rejects(run(), /복제에 실패/)
|
||||||
|
assert.equal(await fs.readFile(path.join(target, 'keep.txt'), 'utf8'), 'keep')
|
||||||
|
await fs.rm(target, { recursive: true })
|
||||||
|
requestedCommit = 'a'.repeat(40)
|
||||||
|
input.commit = requestedCommit
|
||||||
|
await assert.rejects(run(), /복제에 실패/)
|
||||||
|
assert.equal((await fs.readdir(workspace)).length, 0)
|
||||||
|
await fs.mkdir(path.join(repo, 'install'))
|
||||||
|
await fs.writeFile(path.join(repo, 'install/binary'), 'not portable')
|
||||||
|
await git('add', 'install')
|
||||||
|
await git('-c', 'user.name=Test', '-c', 'user.email=test@example.com', 'commit', '-m', 'bad artifacts')
|
||||||
|
input.commit = requestedCommit = (await git('rev-parse', 'HEAD')).stdout.trim()
|
||||||
|
await assert.rejects(run(), /복제에 실패/)
|
||||||
|
assert.equal((await fs.readdir(workspace)).length, 0)
|
||||||
|
} finally { await fs.rm(dir, { recursive: true, force: true }) }
|
||||||
|
})
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const path = require('node:path')
|
||||||
|
const vm = require('node:vm')
|
||||||
|
const { test } = require('node:test')
|
||||||
|
const ts = require('typescript')
|
||||||
|
const { createInstance } = require('i18next')
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, '../src/renderer/src')
|
||||||
|
const dictionary = JSON.parse(fs.readFileSync(path.join(root, 'i18n/en.json'), 'utf8'))
|
||||||
|
|
||||||
|
test('close studio modal translates the built-in Local PC title but preserves vehicle names', () => {
|
||||||
|
const source = fs.readFileSync(path.join(root, 'App.tsx'), 'utf8')
|
||||||
|
const expression = source.match(/platformTitle=\{(runningPlatformCloseTarget\.platformKey[^\n]+)\}/)?.[1]
|
||||||
|
assert.ok(expression)
|
||||||
|
for (const language of ['en', 'ko']) {
|
||||||
|
const { api } = setup(language)
|
||||||
|
const title = vm.runInNewContext(expression, {
|
||||||
|
runningPlatformCloseTarget: { platformKey: 'futuredrive-local-pc', title: '로컬 PC' }, t: api.t
|
||||||
|
})
|
||||||
|
assert.equal(title, language === 'en' ? 'Local PC' : '로컬 PC')
|
||||||
|
if (language === 'en') assert.equal(api.t('{{v0}} 개발 스튜디오를 종료할까요?', { v0: title }), 'Close the Local PC studio?')
|
||||||
|
assert.equal(vm.runInNewContext(expression, {
|
||||||
|
runningPlatformCloseTarget: { platformKey: 'vehicle', title: '교육용 차량' }, t: api.t
|
||||||
|
}), '교육용 차량')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('session confirmation uses a localized platform display name', () => {
|
||||||
|
const source = fs.readFileSync(path.join(root, 'App.tsx'), 'utf8')
|
||||||
|
const expression = source.match(/const activePlatformDisplayName = ([\s\S]*?)\n const sessionPlatformLabel/)[1]
|
||||||
|
for (const language of ['en', 'ko']) {
|
||||||
|
const { api } = setup(language)
|
||||||
|
const name = vm.runInNewContext(expression, {
|
||||||
|
workspacePlatform: null,
|
||||||
|
sessionPlatform: { platformKey: 'futuredrive-local-pc' },
|
||||||
|
activePlatformName: '로컬 PC', t: api.t
|
||||||
|
})
|
||||||
|
assert.equal(name, language === 'en' ? 'Local PC' : '로컬 PC')
|
||||||
|
if (language === 'en') assert.equal(api.t('{{v0}} 개발 스튜디오 연결만 종료합니다. 플랫폼 제어권은 반납되지 않습니다.', { v0: name }),
|
||||||
|
'Disconnect from the Local PC studio only. Platform control is retained.')
|
||||||
|
assert.equal(vm.runInNewContext(expression, {
|
||||||
|
workspacePlatform: { platformKey: 'vehicle' }, sessionPlatform: null,
|
||||||
|
activePlatformName: '교육용 차량', t: api.t
|
||||||
|
}), '교육용 차량')
|
||||||
|
}
|
||||||
|
assert.match(source, /<SessionConfirmModal[\s\S]*?platformName=\{activePlatformDisplayName\}/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('close and disconnect modals localize raw Local PC names at render time', () => {
|
||||||
|
const source = fs.readFileSync(path.join(root, 'components/AppStatusModals.tsx'), 'utf8')
|
||||||
|
for (const [key, variable] of [
|
||||||
|
['{{v0}} 개발 스튜디오를 종료할까요?', 'platformTitle'],
|
||||||
|
['{{v0}} 개발 스튜디오 연결만 종료합니다. 플랫폼 제어권은 반납되지 않습니다.', 'platformName']
|
||||||
|
]) {
|
||||||
|
const line = source.split('\n').find(line => line.includes(key))
|
||||||
|
const expression = line.match(/v0: (.*?) \}/)[1]
|
||||||
|
for (const language of ['ko', 'en']) {
|
||||||
|
const { api } = setup(language)
|
||||||
|
const name = vm.runInNewContext(expression, { [variable]: '로컬 PC', t: api.t })
|
||||||
|
assert.equal(name, language === 'en' ? 'Local PC' : '로컬 PC')
|
||||||
|
if (language === 'en') assert.ok(!api.t(key, { v0: name }).includes('로컬'))
|
||||||
|
assert.equal(vm.runInNewContext(expression, { [variable]: 'Yahboom Micro #02', t: api.t }), 'Yahboom Micro #02')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('all execution guide page titles have English translations', () => {
|
||||||
|
const source = fs.readFileSync(path.join(root, 'features/workspace/ModuleListView.tsx'), 'utf8')
|
||||||
|
const titles = [...source.matchAll(/environment: '([^']+)'/g)].map(match => match[1])
|
||||||
|
assert.equal(titles.length, 4)
|
||||||
|
for (const title of titles) assert.ok(dictionary[title], title)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('visualization progress uses exact runtime translation keys', () => {
|
||||||
|
const { api } = setup('en')
|
||||||
|
assert.equal(api.t('맵 환경 준비 중'), 'Preparing map environment')
|
||||||
|
assert.equal(api.t('시작 중'), 'Starting')
|
||||||
|
assert.equal(api.t('종료 중'), 'Stopping')
|
||||||
|
const panel = fs.readFileSync(path.join(root, 'features/workspace/WorkspaceVisualizationPanel.tsx'), 'utf8')
|
||||||
|
assert.ok(panel.includes('busy ? t(statusMessage)'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('viewer translates before 3D initialization and accepts language only from its parent', () => {
|
||||||
|
const events = new Map()
|
||||||
|
const sent = []
|
||||||
|
const parent = { postMessage: message => sent.push(message) }
|
||||||
|
const label = { dataset: { i18n: '응답을 기다리는 중입니다.' }, textContent: '' }
|
||||||
|
const document = {
|
||||||
|
documentElement: {},
|
||||||
|
querySelectorAll: selector => selector === '[data-i18n]' ? [label] : []
|
||||||
|
}
|
||||||
|
const source = fs.readFileSync(path.join(root, '../public/slam-viewer/locale.js'), 'utf8')
|
||||||
|
const code = ts.transpileModule(source, { compilerOptions: {
|
||||||
|
module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, allowJs: true
|
||||||
|
} }).outputText
|
||||||
|
vm.runInNewContext(code, {
|
||||||
|
exports: {}, document,
|
||||||
|
localStorage: { getItem: () => 'ko' },
|
||||||
|
window: { parent, addEventListener: (type, listener) => events.set(type, listener) }
|
||||||
|
})
|
||||||
|
assert.equal(sent[0].type, 'lms:language:ready')
|
||||||
|
events.get('message')({ source: {}, data: { type: 'lms:language', language: 'en' } })
|
||||||
|
assert.equal(label.textContent, '응답을 기다리는 중입니다.')
|
||||||
|
events.get('message')({ source: parent, data: { type: 'lms:language', language: 'en' } })
|
||||||
|
assert.equal(label.textContent, 'Waiting for a response.')
|
||||||
|
events.get('message')({ source: parent, data: { type: 'lms:language', language: 'ko' } })
|
||||||
|
assert.equal(label.textContent, '응답을 기다리는 중입니다.')
|
||||||
|
const html = fs.readFileSync(path.join(root, '../public/slam-viewer/index.html'), 'utf8')
|
||||||
|
assert.ok(html.includes('<script type="module" src="./locale.js"></script>'))
|
||||||
|
})
|
||||||
|
|
||||||
|
function setup(savedLanguage, storageBlocked = false) {
|
||||||
|
const instance = createInstance()
|
||||||
|
const storage = new Map([['autonomy-studio.lms.language', savedLanguage]])
|
||||||
|
const events = new Map()
|
||||||
|
const document = { documentElement: { lang: '' } }
|
||||||
|
const exports = {}
|
||||||
|
const source = fs.readFileSync(path.join(root, 'i18n/index.ts'), 'utf8')
|
||||||
|
const code = ts.transpileModule(source, { compilerOptions: {
|
||||||
|
module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, esModuleInterop: true
|
||||||
|
} }).outputText
|
||||||
|
vm.runInNewContext(code, {
|
||||||
|
exports, document,
|
||||||
|
localStorage: {
|
||||||
|
getItem(key) { if (storageBlocked) throw Error('blocked'); return storage.get(key) },
|
||||||
|
setItem(key, value) { if (storageBlocked) throw Error('blocked'); storage.set(key, value) }
|
||||||
|
},
|
||||||
|
window: { addEventListener: (name, listener) => events.set(name, listener) },
|
||||||
|
require(name) {
|
||||||
|
if (name === 'i18next') return instance
|
||||||
|
if (name === './en.json') return dictionary
|
||||||
|
return require(name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return { api: exports, storage, document, events }
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Korean defaults, English persists, interpolation and fallback preserve user content', () => {
|
||||||
|
const { api, storage, document } = setup('invalid')
|
||||||
|
assert.equal(api.t('설정'), '설정')
|
||||||
|
assert.equal(document.documentElement.lang, 'ko')
|
||||||
|
api.setLanguage('en')
|
||||||
|
assert.equal(api.t('설정'), 'Settings')
|
||||||
|
assert.equal(api.t('{{week}}주차', { week: 3 }), 'Week 3')
|
||||||
|
assert.equal(api.t('강사가 작성한 강좌 제목'), '강사가 작성한 강좌 제목')
|
||||||
|
assert.equal(api.t('sim_mode:=false'), 'sim_mode:=false')
|
||||||
|
assert.equal(storage.get(api.LANGUAGE_STORAGE_KEY), 'en')
|
||||||
|
assert.equal(document.documentElement.lang, 'en')
|
||||||
|
api.setLanguage('ko')
|
||||||
|
assert.equal(api.t('{{week}}주차', { week: 3 }), '3주차')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('restores preference and synchronizes other renderer windows', () => {
|
||||||
|
const { api, document, events } = setup('en')
|
||||||
|
assert.equal(api.t('설정'), 'Settings')
|
||||||
|
events.get('storage')({ key: api.LANGUAGE_STORAGE_KEY, newValue: 'ko' })
|
||||||
|
assert.equal(document.documentElement.lang, 'ko')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('blocked storage does not prevent switching language', () => {
|
||||||
|
const { api } = setup(undefined, true)
|
||||||
|
api.setLanguage('en')
|
||||||
|
assert.equal(api.t('설정'), 'Settings')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('language selector edits a draft without applying or persisting it', () => {
|
||||||
|
const { api, storage, document } = setup('ko')
|
||||||
|
const exports = {}
|
||||||
|
const source = fs.readFileSync(path.join(root, 'i18n/LanguageSelect.tsx'), 'utf8')
|
||||||
|
const code = ts.transpileModule(source, { compilerOptions: {
|
||||||
|
module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022,
|
||||||
|
jsx: ts.JsxEmit.ReactJSX, esModuleInterop: true
|
||||||
|
} }).outputText
|
||||||
|
vm.runInNewContext(code, { exports, require: name => name === './index' ? api : require(name) })
|
||||||
|
let draft = 'ko'
|
||||||
|
const element = exports.default({ dark: false, value: draft, onChange: value => { draft = value } })
|
||||||
|
const select = element.props.children.find(child => child.type === 'select')
|
||||||
|
select.props.onChange({ target: { value: 'en' } })
|
||||||
|
assert.equal(draft, 'en')
|
||||||
|
assert.equal(document.documentElement.lang, 'ko')
|
||||||
|
assert.equal(storage.get(api.LANGUAGE_STORAGE_KEY), 'ko')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('built-in map labels follow language without changing map data or saved names', () => {
|
||||||
|
const { api } = setup('ko')
|
||||||
|
const exports = {}
|
||||||
|
const source = fs.readFileSync(path.join(root, 'features/workspace/WorldMapSelectorModal.tsx'), 'utf8')
|
||||||
|
const code = ts.transpileModule(source, { compilerOptions: {
|
||||||
|
module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022,
|
||||||
|
jsx: ts.JsxEmit.ReactJSX, esModuleInterop: true
|
||||||
|
} }).outputText
|
||||||
|
vm.runInNewContext(code, { exports, require: name => {
|
||||||
|
if (name === '../../i18n') return api
|
||||||
|
if (name === './studioTheme') return { useStudioTheme: () => 'dark' }
|
||||||
|
if (name.endsWith('.css')) return {}
|
||||||
|
return require(name)
|
||||||
|
} })
|
||||||
|
const map = exports.NAV2_MAP_OPTIONS[0]
|
||||||
|
assert.equal(exports.getMapDisplayName(map), '기본 월드')
|
||||||
|
api.setLanguage('en')
|
||||||
|
assert.equal(exports.getMapDisplayName(map), 'Default world')
|
||||||
|
assert.equal(exports.getMapDisplayName({ ...map, saved: true }), '기본 월드')
|
||||||
|
assert.equal(map.name, '기본 월드')
|
||||||
|
assert.equal(map.worldFile, 'turtlebot3_world.world')
|
||||||
|
assert.equal(api.t('새 2D 맵'), 'New 2D map')
|
||||||
|
assert.equal(api.t('새 3D 맵'), 'New 3D map')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('translations preserve every interpolation placeholder', () => {
|
||||||
|
const placeholders = value => [...value.matchAll(/{{\s*([^}]+)\s*}}/g)].map(match => match[1]).sort()
|
||||||
|
for (const [key, value] of Object.entries(dictionary)) {
|
||||||
|
assert.ok(value.trim(), `Empty translation: ${key}`)
|
||||||
|
assert.deepEqual(placeholders(value), placeholders(key), key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('learning statistics translate API labels but preserve numeric values', () => {
|
||||||
|
const { api } = setup('ko')
|
||||||
|
const exports = {}
|
||||||
|
const source = fs.readFileSync(path.join(root, 'features/curriculum/LearningStatsCard.tsx'), 'utf8')
|
||||||
|
const code = ts.transpileModule(source, { compilerOptions: {
|
||||||
|
module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022,
|
||||||
|
jsx: ts.JsxEmit.ReactJSX, esModuleInterop: true
|
||||||
|
} }).outputText
|
||||||
|
vm.runInNewContext(code, { exports, require: name => name === '../../i18n'
|
||||||
|
? { ...api, useLocaleRender() {} } : require(name) })
|
||||||
|
const stats = [
|
||||||
|
{ key: 'weeks', label: '완료한 주차', value: '0/3', iconKey: 'check' },
|
||||||
|
{ key: 'time', label: '학습 시간', value: '02:08', iconKey: 'clock' },
|
||||||
|
{ key: 'commits', label: '커밋 수', value: '0', iconKey: 'trending' },
|
||||||
|
{ key: 'score', label: '평균 점수', value: '0', iconKey: 'gauge' }
|
||||||
|
]
|
||||||
|
const { createElement } = require('react')
|
||||||
|
const { renderToStaticMarkup } = require('react-dom/server')
|
||||||
|
const render = () => renderToStaticMarkup(createElement(exports.default, { stats }))
|
||||||
|
assert.ok(render().includes('완료한 주차'))
|
||||||
|
api.setLanguage('en')
|
||||||
|
const html = render()
|
||||||
|
for (const text of ['Completed weeks', 'Learning time', 'Commits', 'Average score', '0/3', '02:08']) {
|
||||||
|
assert.ok(html.includes(text), text)
|
||||||
|
}
|
||||||
|
for (const item of stats) assert.ok(!html.includes(item.label), item.label)
|
||||||
|
api.setLanguage('ko')
|
||||||
|
assert.ok(render().includes('평균 점수'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('all literal translation calls have an English entry', () => {
|
||||||
|
function check(dir) {
|
||||||
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||||
|
const file = path.join(dir, entry.name)
|
||||||
|
if (entry.isDirectory()) { check(file); continue }
|
||||||
|
if (!/\.tsx?$/.test(file)) continue
|
||||||
|
const source = ts.createSourceFile(file, fs.readFileSync(file, 'utf8'), ts.ScriptTarget.Latest, true)
|
||||||
|
function visit(node) {
|
||||||
|
if (ts.isCallExpression(node) && node.expression.getText(source) === 't' && node.arguments[0] && ts.isStringLiteral(node.arguments[0])) {
|
||||||
|
assert.ok(Object.hasOwn(dictionary, node.arguments[0].text), `${file}: ${node.arguments[0].text}`)
|
||||||
|
}
|
||||||
|
ts.forEachChild(node, visit)
|
||||||
|
}
|
||||||
|
visit(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(root)
|
||||||
|
})
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
const { test } = require('node:test')
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const vm = require('node:vm')
|
||||||
|
const ts = require('typescript')
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
const data = new Map()
|
||||||
|
const exports = {}
|
||||||
|
vm.runInNewContext(ts.transpileModule(fs.readFileSync(require.resolve('../src/renderer/src/features/workspace/sourceSettings.ts'), 'utf8'), {
|
||||||
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 },
|
||||||
|
}).outputText, {
|
||||||
|
exports,
|
||||||
|
require: () => ({ STUDIO_SOURCE_SETTINGS_STORAGE_KEY: 'test-settings' }),
|
||||||
|
window: { localStorage: { getItem: key => data.get(key) ?? null, setItem: (key, value) => data.set(key, value) } },
|
||||||
|
})
|
||||||
|
return exports
|
||||||
|
}
|
||||||
|
|
||||||
|
test('remote workspace reads local domain and linked platform by actual platform ID', () => {
|
||||||
|
const api = setup()
|
||||||
|
api.storeStudioSourceSettings('local-platform-uuid', { ...api.defaultStudioSourceSettings, rosDomainId: '75', rosLinkedPlatformId: 'yahboom-uuid' })
|
||||||
|
const result = api.readLocalStudioSourceSettings([
|
||||||
|
{ id: 'yahboom-uuid', platformKey: 'yahboom', sourceSettings: { rosDomainId: '77' } },
|
||||||
|
{ id: 'local-platform-uuid', platformKey: 'futuredrive-local-pc' },
|
||||||
|
])
|
||||||
|
assert.equal(result.rosDomainId, '75')
|
||||||
|
assert.equal(result.rosLinkedPlatformId, 'yahboom-uuid')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('local platform defaults and unlinked selection are preserved', () => {
|
||||||
|
const api = setup()
|
||||||
|
const platforms = [{ id: 'local-platform-uuid', platformKey: 'futuredrive-local-pc', sourceSettings: { rosDomainId: '76' } }]
|
||||||
|
assert.equal(api.readLocalStudioSourceSettings(platforms).rosDomainId, '76')
|
||||||
|
api.storeStudioSourceSettings('local-platform-uuid', { ...api.defaultStudioSourceSettings, rosDomainId: '75', rosLinkedPlatformId: null })
|
||||||
|
assert.equal(api.readLocalStudioSourceSettings(platforms).rosLinkedPlatformId, null)
|
||||||
|
})
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const { test } = require('node:test')
|
||||||
|
const { readFileSync, mkdtempSync, writeFileSync, rmSync } = require('node:fs')
|
||||||
|
const { tmpdir } = require('node:os')
|
||||||
|
const { join } = require('node:path')
|
||||||
|
const { spawn } = require('node:child_process')
|
||||||
|
const vm = require('node:vm')
|
||||||
|
const ts = require('typescript')
|
||||||
|
|
||||||
|
const transpile = (source) => ts.transpileModule(source, {
|
||||||
|
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS }
|
||||||
|
}).outputText
|
||||||
|
const helpers = { exports: {} }
|
||||||
|
vm.runInNewContext(transpile(readFileSync(join(__dirname, '../src/main/remoteShell.ts'), 'utf8')), helpers)
|
||||||
|
const { remoteShellCommand, remoteShellEnvironment } = helpers.exports
|
||||||
|
const source = readFileSync(join(__dirname, '../src/main/index.ts'), 'utf8')
|
||||||
|
const start = source.indexOf('async function runCommandOutput(')
|
||||||
|
const end = source.indexOf('\nasync function runCommand(', start)
|
||||||
|
const context = { spawn, process, setTimeout, clearTimeout, createModuleProgressParser: () => () => {} }
|
||||||
|
vm.runInNewContext(transpile(source.slice(start, end)), context)
|
||||||
|
const run = context.runCommandOutput
|
||||||
|
|
||||||
|
test('SSH data output excludes locale warnings even when command succeeds', async () => {
|
||||||
|
const result = await run('/bin/bash', ['--noprofile', '--norc', '-c', 'printf %s "/tmp/module with spaces"'], undefined,
|
||||||
|
{ LC_ALL: 'missing_TEST.UTF-8' }, 5000, 'stdout')
|
||||||
|
assert.equal(result, '/tmp/module with spaces')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('failed commands retain stderr diagnostics and stdout', async () => {
|
||||||
|
await assert.rejects(run('/bin/sh', ['-c', 'printf context; printf "permission denied" >&2; exit 7'], undefined, undefined, 5000, 'stdout'),
|
||||||
|
error => error.message.includes('context') && error.message.includes('permission denied'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('non-SSH combined output stays compatible and explicit log merging works', async () => {
|
||||||
|
const command = 'printf output; printf diagnostic >&2'
|
||||||
|
const combined = await run('/bin/sh', ['-c', command])
|
||||||
|
assert.match(combined, /output/)
|
||||||
|
assert.match(combined, /diagnostic/)
|
||||||
|
const logs = await run('/bin/sh', ['-c', `{ ${command}; } 2>&1`], undefined, undefined, 5000, 'stdout')
|
||||||
|
assert.match(logs, /diagnostic/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('SSH environment neutralizes locale forwarding without losing other settings', () => {
|
||||||
|
const original = { LANG: 'ko_KR.UTF-8', LC_ALL: 'en_US.UTF-8', LC_CTYPE: 'bad', LANGUAGE: 'ko:en', PATH: '/bin', TERM: 'xterm-256color' }
|
||||||
|
const env = remoteShellEnvironment(original)
|
||||||
|
assert.equal(env.LANG, 'C')
|
||||||
|
assert.equal(env.LC_ALL, 'C')
|
||||||
|
assert.equal(env.LC_CTYPE, undefined)
|
||||||
|
assert.equal(env.LANGUAGE, undefined)
|
||||||
|
assert.equal(env.PATH, '/bin')
|
||||||
|
assert.equal(env.TERM, 'xterm-256color')
|
||||||
|
assert.equal(original.LC_CTYPE, 'bad')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('remote wrapper preserves multiline commands, Unicode, quotes and exit status', async () => {
|
||||||
|
const command = `cat <<'END'
|
||||||
|
/tmp/한글 module's folder
|
||||||
|
END`
|
||||||
|
const result = await run('/bin/sh', ['-c', remoteShellCommand(command)], undefined, remoteShellEnvironment(process.env), 5000, 'stdout')
|
||||||
|
assert.equal(result, "/tmp/한글 module's folder\n")
|
||||||
|
await assert.rejects(run('/bin/sh', ['-c', remoteShellCommand('echo remote-failure >&2; exit 9')], undefined, undefined, 5000, 'stdout'), /remote-failure/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('locale selection prefers C UTF-8, supports other UTF-8 locales and falls back to C', async () => {
|
||||||
|
const dir = mkdtempSync(join(tmpdir(), 'lms-locale-test-'))
|
||||||
|
try {
|
||||||
|
for (const [locales, expected] of [['en_GB.utf8 C.utf8', 'C.utf8'], ['C en_GB.utf8', 'en_GB.utf8'], ['C POSIX', 'C']]) {
|
||||||
|
writeFileSync(join(dir, 'locale'), `#!/bin/sh\nprintf '%s\\n' ${locales}\n`, { mode: 0o755 })
|
||||||
|
const result = await run('/bin/sh', ['-c', remoteShellCommand('printf "%s|%s" "$LANG" "$LC_ALL"')], undefined,
|
||||||
|
{ PATH: `${dir}:${process.env.PATH}`, LC_ALL: 'C' }, 5000, 'stdout')
|
||||||
|
assert.equal(result, `${expected}|${expected}`)
|
||||||
|
}
|
||||||
|
} finally { rmSync(dir, { recursive: true, force: true }) }
|
||||||
|
})
|
||||||
|
|
||||||
|
test('queued SSH, stats probes and remote PTY use normalization; container logs keep stderr', () => {
|
||||||
|
const ssh = source.slice(source.indexOf('async function runSshOutput('), source.indexOf('async function readRemoteDockerStatsOutput('))
|
||||||
|
assert.equal((ssh.match(/remoteShellCommand\(command\)/g) || []).length, 2)
|
||||||
|
assert.equal((ssh.match(/'stdout'/g) || []).length, 2)
|
||||||
|
const terminal = source.slice(source.indexOf("ipcMain.handle('remote-workspace:terminal:start'"), source.indexOf("ipcMain.handle('terminal-resize'"))
|
||||||
|
assert.match(terminal, /remoteShellCommand\(mode === 'container'/)
|
||||||
|
assert.match(terminal, /remoteShellEnvironment\(terminalEnvironment/)
|
||||||
|
assert.match(source, /target \? remoteShellCommand\(init\)/)
|
||||||
|
assert.match(source, /docker logs --timestamps --tail 300.*2>&1/)
|
||||||
|
})
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
const { test } = require('node:test')
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const path = require('node:path')
|
||||||
|
const vm = require('node:vm')
|
||||||
|
const ts = require('typescript')
|
||||||
|
const exportsObject = {}
|
||||||
|
const source = fs.readFileSync(path.resolve(__dirname, '../src/renderer/src/features/workspace/WorkspaceRosConnectionsView.tsx'), 'utf8')
|
||||||
|
vm.runInNewContext(ts.transpileModule(source, {
|
||||||
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX }
|
||||||
|
}).outputText, { exports: exportsObject, require: () => ({}) })
|
||||||
|
const { rosGraphConnections } = exportsObject
|
||||||
|
const endpoint = (node, type = 'sensor_msgs/msg/CompressedImage') => ({ node, type })
|
||||||
|
|
||||||
|
test('shows image and custom topics without module definitions', () => {
|
||||||
|
const connections = rosGraphConnections({ topics: [
|
||||||
|
{ name: '/camera/image/compressed', types: ['sensor_msgs/msg/CompressedImage'], publishers: [endpoint('/camera')], subscribers: [endpoint('/bridge')] },
|
||||||
|
{ name: '/custom/new_topic', types: ['custom_msgs/msg/Value'], publishers: [], subscribers: [endpoint('/custom', 'custom_msgs/msg/Value')] },
|
||||||
|
] })
|
||||||
|
assert.equal(connections.length, 2)
|
||||||
|
assert.equal(connections[0].topic.name, '/camera/image/compressed')
|
||||||
|
assert.equal(connections[0].topic.type, 'sensor_msgs/msg/CompressedImage')
|
||||||
|
assert.equal(connections[0].active, true)
|
||||||
|
assert.equal(connections[1].publishers.length, 0)
|
||||||
|
assert.equal(connections[1].active, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('empty and failed queries never synthesize topics or nodes', () => {
|
||||||
|
assert.equal(rosGraphConnections({ topics: [] }).length, 0)
|
||||||
|
assert.equal(rosGraphConnections({ topics: [], message: 'SSH timeout' }).length, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('keeps discovered topics without endpoints and deduplicates node entries', () => {
|
||||||
|
const connections = rosGraphConnections({ topics: [
|
||||||
|
{ name: '/z', types: [], publishers: [], subscribers: [] },
|
||||||
|
{ name: '/a', types: ['sensor_msgs/msg/CompressedImage'], publishers: [endpoint('/camera'), endpoint('/camera')], subscribers: [] },
|
||||||
|
] })
|
||||||
|
assert.equal(connections.length, 2)
|
||||||
|
assert.equal(connections[0].topic.name, '/a')
|
||||||
|
assert.equal(connections[0].publishers.length, 1)
|
||||||
|
assert.equal(connections[1].topic.name, '/z')
|
||||||
|
assert.equal(connections.every((connection) => !connection.active), true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('module selection and guessed topic mappings are removed from the view', () => {
|
||||||
|
assert.doesNotMatch(source, /selectedModuleKeys|moduleTopics|fallbackPublishers|fallbackSubscribers|onToggleModule|ListChecks|ListX/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('active toggle filters only topics, preserves totals and hides filtered details', () => {
|
||||||
|
const graph = { topics: [
|
||||||
|
{ name: '/active', types: [], publishers: [endpoint('/pub')], subscribers: [endpoint('/sub')] },
|
||||||
|
{ name: '/waiting', types: [], publishers: [endpoint('/pub')], subscribers: [] },
|
||||||
|
] }
|
||||||
|
function render(activeOnly) {
|
||||||
|
const states = [graph, false, activeOnly, '/waiting', true, 320]
|
||||||
|
let index = 0
|
||||||
|
const api = {}
|
||||||
|
const jsx = (type, props, key) => ({ type, props, key })
|
||||||
|
vm.runInNewContext(ts.transpileModule(source, {
|
||||||
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX }
|
||||||
|
}).outputText, { exports: api, require: (name) => {
|
||||||
|
if (name === 'react') return { useState: () => [states[index++], () => {}], useMemo: fn => fn(), useEffect: () => {} }
|
||||||
|
if (name === 'react/jsx-runtime') return { jsx, jsxs: jsx }
|
||||||
|
if (name === '../../i18n') return { t: value => value, useLocaleRender: () => {} }
|
||||||
|
return {}
|
||||||
|
} })
|
||||||
|
const tree = api.default({ dark: false, modules: [{ name: 'Running module', state: 'running' }], remoteWorkspace: false, rosDomainId: '77' })
|
||||||
|
const nodes = []
|
||||||
|
function visit(node) {
|
||||||
|
if (Array.isArray(node)) return node.forEach(visit)
|
||||||
|
if (!node || typeof node !== 'object') return
|
||||||
|
nodes.push(node)
|
||||||
|
visit(node.props?.children)
|
||||||
|
}
|
||||||
|
visit(tree)
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
for (const activeOnly of [false, true]) {
|
||||||
|
const nodes = render(activeOnly)
|
||||||
|
assert.equal(nodes.filter(node => node.type === 'button' && node.key?.startsWith('/')).length, activeOnly ? 1 : 2)
|
||||||
|
const toggle = nodes.find(node => node.props?.title === '활성 연결만 보기')
|
||||||
|
assert.equal(toggle.props['aria-pressed'], activeOnly)
|
||||||
|
assert.equal(toggle.props.children.at(-1), 1)
|
||||||
|
assert.equal(nodes.find(node => node.type?.name === 'RuntimeLane').props.modules.length, 1)
|
||||||
|
assert.equal(nodes.some(node => node.type?.name === 'TopicDetailDrawer'), !activeOnly)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
const { test } = require('node:test')
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const path = require('node:path')
|
||||||
|
const vm = require('node:vm')
|
||||||
|
const ts = require('typescript')
|
||||||
|
const root = path.resolve(__dirname, '../src/renderer/src/features/workspace')
|
||||||
|
|
||||||
|
function setup(saved, blocked = false) {
|
||||||
|
const storage = new Map([['autonomy-studio.lms.studio-theme', saved]])
|
||||||
|
const events = new Map()
|
||||||
|
const exports = {}
|
||||||
|
const code = ts.transpileModule(fs.readFileSync(path.join(root, 'studioTheme.ts'), 'utf8'), {
|
||||||
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }
|
||||||
|
}).outputText
|
||||||
|
vm.runInNewContext(code, { exports,
|
||||||
|
require: () => ({ useSyncExternalStore: (_subscribe, snapshot) => snapshot() }),
|
||||||
|
localStorage: {
|
||||||
|
getItem(key) { if (blocked) throw Error('blocked'); return storage.get(key) },
|
||||||
|
setItem(key, value) { if (blocked) throw Error('blocked'); storage.set(key, value) }
|
||||||
|
},
|
||||||
|
window: { addEventListener: (type, listener) => events.set(type, listener) }
|
||||||
|
})
|
||||||
|
return { api: exports, storage, events }
|
||||||
|
}
|
||||||
|
|
||||||
|
test('theme defaults dark, persists light, and restores the preference', () => {
|
||||||
|
const { api, storage } = setup('invalid')
|
||||||
|
assert.equal(api.useStudioTheme(), 'dark')
|
||||||
|
api.setStudioTheme('light')
|
||||||
|
assert.equal(api.useStudioTheme(), 'light')
|
||||||
|
assert.equal(storage.get(api.STUDIO_THEME_KEY), 'light')
|
||||||
|
assert.equal(setup('light').api.useStudioTheme(), 'light')
|
||||||
|
})
|
||||||
|
test('theme remains usable when storage is blocked', () => {
|
||||||
|
const { api } = setup(null, true)
|
||||||
|
api.setStudioTheme('light')
|
||||||
|
assert.equal(api.useStudioTheme(), 'light')
|
||||||
|
})
|
||||||
|
test('theme synchronizes across windows without writing runtime settings', () => {
|
||||||
|
const { api, storage, events } = setup('dark')
|
||||||
|
storage.set(api.STUDIO_THEME_KEY, 'light')
|
||||||
|
events.get('storage')({ key: api.STUDIO_THEME_KEY })
|
||||||
|
assert.equal(api.useStudioTheme(), 'light')
|
||||||
|
storage.clear()
|
||||||
|
events.get('storage')({ key: null })
|
||||||
|
assert.equal(api.useStudioTheme(), 'dark')
|
||||||
|
})
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { app } from 'electron'
|
||||||
|
import { dirname, join } from 'path'
|
||||||
|
import { inspect } from 'util'
|
||||||
|
import { createDailyLog } from './dailyLog'
|
||||||
|
|
||||||
|
const fallback = app.getPath('logs')
|
||||||
|
export const appLog = createDailyLog([
|
||||||
|
process.env.APPIMAGE
|
||||||
|
? join(dirname(process.env.APPIMAGE), 'logs')
|
||||||
|
: app.isPackaged ? join(dirname(process.execPath), 'logs') : fallback,
|
||||||
|
fallback
|
||||||
|
])
|
||||||
|
|
||||||
|
for (const level of ['warn', 'error'] as const) {
|
||||||
|
const original = console[level].bind(console)
|
||||||
|
console[level] = (...args: unknown[]) => {
|
||||||
|
appLog.write(level, args.map((value) => typeof value === 'string' ? value : inspect(value, { depth: 4 })).join(' '))
|
||||||
|
original(...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monitor fatal exceptions without changing Node's normal termination behavior.
|
||||||
|
process.on('uncaughtExceptionMonitor', (error, origin) => {
|
||||||
|
appLog.write(origin, error.stack ?? String(error))
|
||||||
|
})
|
||||||
|
process.on('warning', (warning) => appLog.write('warning', warning.stack ?? warning.message))
|
||||||
|
process.on('exit', (code) => appLog.write('exit', `code=${code}`))
|
||||||
|
app.on('child-process-gone', (_event, details) => appLog.write('child-process-gone', JSON.stringify(details)))
|
||||||
|
appLog.write('startup', `version=${app.getVersion()} platform=${process.platform} arch=${process.arch} electron=${process.versions.electron} executable=${process.env.APPIMAGE || process.execPath}`)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { app } from 'electron'
|
||||||
|
import { appLog } from './appLogging'
|
||||||
|
|
||||||
|
// Load native dependencies only after the startup logger is ready.
|
||||||
|
void import('./index').catch((error: unknown) => {
|
||||||
|
appLog.write('startup-error', error instanceof Error ? error.stack ?? error.message : String(error))
|
||||||
|
app.exit(1)
|
||||||
|
})
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { strict as assert } from 'node:assert'
|
||||||
|
import { test } from 'node:test'
|
||||||
|
import { bridgeArtifact, jetsonBridgeImage, localBridgeImage } from './bridgeArtifacts'
|
||||||
|
|
||||||
|
test('PC and Jetson bridges have separate pinned downloads', () => {
|
||||||
|
const pc = bridgeArtifact(localBridgeImage)!
|
||||||
|
const jetson = bridgeArtifact(jetsonBridgeImage)!
|
||||||
|
assert.equal(pc.architecture, 'amd64')
|
||||||
|
assert.equal(jetson.architecture, 'arm64')
|
||||||
|
assert.notEqual(pc.url, jetson.url)
|
||||||
|
assert.notEqual(pc.fileName, jetson.fileName)
|
||||||
|
for (const artifact of [pc, jetson]) {
|
||||||
|
assert.match(artifact.checksumSha256, /^[a-f0-9]{64}$/)
|
||||||
|
assert.equal(new URL(artifact.url).protocol, 'https:')
|
||||||
|
assert.ok(artifact.url.includes(artifact.checksumSha256.slice(0, 12)))
|
||||||
|
assert.equal(artifact.required, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('custom images do not resolve to an unrelated built-in artifact', () => {
|
||||||
|
assert.equal(bridgeArtifact('custom-bridge:test'), undefined)
|
||||||
|
})
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
const root = 'https://pub-ca72c40797144462bdb7d200c215d361.r2.dev/studio-artifacts/runtime-images/slam-ros-bridge'
|
||||||
|
|
||||||
|
export const localBridgeImage = 'slam-ros-bridge:20260813-3d-map-v2'
|
||||||
|
export const jetsonBridgeImage = 'slam-ros-bridge:jetson_orin'
|
||||||
|
|
||||||
|
const artifacts = [
|
||||||
|
{
|
||||||
|
image: localBridgeImage,
|
||||||
|
architecture: 'amd64',
|
||||||
|
url: `${root}/amd64/20260915-489a3e8b370c/slam-ros-bridge-amd64.tar.zst`,
|
||||||
|
fileName: 'slam-ros-bridge-amd64-489a3e8b370c.tar.zst',
|
||||||
|
checksumSha256: '489a3e8b370c19903043a39e92935f2e32e4b4423c28210f130df1acab1a9889',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: jetsonBridgeImage,
|
||||||
|
architecture: 'arm64',
|
||||||
|
url: `${root}/arm64/20261013-21acdf0cbdee/slam-ros-bridge-jetson-orin.tar.zst`,
|
||||||
|
fileName: 'slam-ros-bridge-jetson-orin-21acdf0cbdee.tar.zst',
|
||||||
|
checksumSha256: '21acdf0cbdee45d8b7b9dba78c3d321d67d8d7a2467511c4af0826e36ea0c74f',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function bridgeArtifact(image: string) {
|
||||||
|
return artifacts.find((artifact) => artifact.image === image)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
const image = 'future-nav2-camera-test:humble-jetson_orin'
|
||||||
|
|
||||||
|
export function cameraRuntimeArtifacts<T extends { image?: string | null }>(
|
||||||
|
folder: string,
|
||||||
|
configured: T[] | null | undefined,
|
||||||
|
): T[] | null | undefined {
|
||||||
|
if (!configured) return configured
|
||||||
|
const camera = folder.includes('lms-nav2-camera-test')
|
||||||
|
// The optional camera image must not be downloaded for other Nav2 modules.
|
||||||
|
return configured.filter((item) => (item.image?.trim() === image) === camera)
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { strict as assert } from 'node:assert'
|
||||||
|
import { test } from 'node:test'
|
||||||
|
import { resolveContainerTerminal } from './containerTerminal'
|
||||||
|
|
||||||
|
const inspection = (running = true) => JSON.stringify([{
|
||||||
|
Id: 'container-id', State: { Running: running }, Mounts: [
|
||||||
|
{ Source: '/home/user/ws', Destination: '/workspace' },
|
||||||
|
{ Source: '/home/user/ws/module', Destination: '/module' }
|
||||||
|
]
|
||||||
|
}])
|
||||||
|
|
||||||
|
test('resolves module path from the most specific actual mount', () => {
|
||||||
|
assert.deepEqual(resolveContainerTerminal(inspection(), '/home/user/ws/module'), { id: 'container-id', cwd: '/module' })
|
||||||
|
assert.equal(resolveContainerTerminal(inspection(), '/home/user/ws/other').cwd, '/workspace/other')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects stopped containers and unrelated workspace paths', () => {
|
||||||
|
assert.throws(() => resolveContainerTerminal(inspection(false), '/home/user/ws/module'), /not running/)
|
||||||
|
assert.throws(() => resolveContainerTerminal(inspection(), '/home/user/ws-other/module'), /not mounted/)
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { posix } from 'node:path'
|
||||||
|
|
||||||
|
export function resolveContainerTerminal(inspectJson: string, moduleRoot: string): { id: string; cwd: string } {
|
||||||
|
const [container] = JSON.parse(inspectJson)
|
||||||
|
if (!container?.State?.Running) throw new Error('Module container is not running')
|
||||||
|
const root = posix.resolve(moduleRoot)
|
||||||
|
const mounts = (container.Mounts ?? []) as { Source: string; Destination: string }[]
|
||||||
|
const mount = mounts
|
||||||
|
.filter((item) => root === item.Source || root.startsWith(`${item.Source.replace(/\/$/, '')}/`))
|
||||||
|
.sort((a, b) => b.Source.length - a.Source.length)[0]
|
||||||
|
if (!mount) throw new Error('Module workspace is not mounted in this container')
|
||||||
|
return { id: container.Id, cwd: posix.join(mount.Destination, posix.relative(mount.Source, root)) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { appendFileSync, closeSync, mkdirSync, openSync, readdirSync, unlinkSync } from 'fs'
|
||||||
|
import { join } from 'path'
|
||||||
|
|
||||||
|
export function redactLog(value: string): string {
|
||||||
|
return value
|
||||||
|
.replace(/(https?:\/\/)[^\s/@]+@/gi, '$1[REDACTED]@')
|
||||||
|
.replace(/\bBearer\s+[^\s"',;]+/gi, 'Bearer [REDACTED]')
|
||||||
|
.replace(/((?:[?&]|--)(?:[^\s=&]*token|password|secret|code|key)=)[^\s&#"']+/gi, '$1[REDACTED]')
|
||||||
|
.replace(/(["']?(?:[\w-]*token|password|secret|authorization|api[_-]?key)["']?\s*[:=]\s*)(?:"[^"]*"|'[^']*'|[^\s,;}]+)/gi, '$1[REDACTED]')
|
||||||
|
}
|
||||||
|
|
||||||
|
function localDate(now: Date): string {
|
||||||
|
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDailyLog(directories: string[], now: () => Date = () => new Date()) {
|
||||||
|
let activePath = ''
|
||||||
|
let activeDay = ''
|
||||||
|
|
||||||
|
function selectFile(): string {
|
||||||
|
const day = localDate(now())
|
||||||
|
if (activePath && activeDay === day) return activePath
|
||||||
|
for (const directory of [...new Set(directories)]) {
|
||||||
|
try {
|
||||||
|
mkdirSync(directory, { recursive: true })
|
||||||
|
const path = join(directory, `lms-${day}.log`)
|
||||||
|
closeSync(openSync(path, 'a', 0o600))
|
||||||
|
activePath = path
|
||||||
|
activeDay = day
|
||||||
|
for (const name of readdirSync(directory)) {
|
||||||
|
if (/^lms-\d{4}-\d{2}-\d{2}\.log$/.test(name) && name < `lms-${day}.log`) {
|
||||||
|
try { unlinkSync(join(directory, name)) } catch { /* Retention must not block startup. */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
} catch { /* Try the user-writable fallback directory. */ }
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: selectFile,
|
||||||
|
write(kind: string, message: string): void {
|
||||||
|
try {
|
||||||
|
const path = selectFile()
|
||||||
|
if (!path) return
|
||||||
|
appendFileSync(path, `[${now().toISOString()}] [${redactLog(kind)}] ${redactLog(message)}\n`, { mode: 0o600 })
|
||||||
|
} catch { /* Diagnostics must not interrupt the app. */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { strict as assert } from 'node:assert'
|
||||||
|
import { test } from 'node:test'
|
||||||
|
import { classifyDockerError } from './dockerStatus'
|
||||||
|
|
||||||
|
test('missing CLI is distinguished from an unavailable daemon', () => {
|
||||||
|
assert.equal(classifyDockerError('ENOENT', 'spawn docker ENOENT'), 'missing')
|
||||||
|
assert.equal(classifyDockerError(1, 'Cannot connect to the Docker daemon'), 'unavailable')
|
||||||
|
assert.equal(classifyDockerError(1, 'dial unix /run/docker.sock: connect: connection refused'), 'unavailable')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('permission errors take precedence over connection errors', () => {
|
||||||
|
assert.equal(classifyDockerError(1, 'Cannot connect: permission denied'), 'permission')
|
||||||
|
assert.equal(classifyDockerError('EACCES', 'spawn docker EACCES'), 'permission')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('timeouts and unknown failures do not imply Docker needs installing', () => {
|
||||||
|
assert.equal(classifyDockerError(null, 'Command failed: docker info'), 'error')
|
||||||
|
assert.equal(classifyDockerError(1, 'context not found'), 'error')
|
||||||
|
})
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { execFile } from 'node:child_process'
|
||||||
|
|
||||||
|
export type DockerStatus = 'ready' | 'missing' | 'permission' | 'unavailable' | 'error'
|
||||||
|
|
||||||
|
export function classifyDockerError(code: string | number | null | undefined, message: string): DockerStatus {
|
||||||
|
if (code === 'ENOENT') return 'missing'
|
||||||
|
if (/permission denied|access is denied|EACCES/i.test(message)) return 'permission'
|
||||||
|
if (/cannot connect|connection refused|is the docker daemon running/i.test(message)) return 'unavailable'
|
||||||
|
return 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkDockerStatus(): Promise<{ status: DockerStatus; platform: string }> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
execFile('docker', ['info', '--format', '{{.ServerVersion}}'], {
|
||||||
|
timeout: 5000,
|
||||||
|
maxBuffer: 256 * 1024,
|
||||||
|
env: { ...process.env, LC_ALL: 'C', LANG: 'C' },
|
||||||
|
}, (error, stdout, stderr) => {
|
||||||
|
resolve({
|
||||||
|
status: error
|
||||||
|
? classifyDockerError(error.code, `${error.message}\n${stderr}`)
|
||||||
|
: stdout.trim() ? 'ready' : 'error',
|
||||||
|
platform: process.platform,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { readdir, readFile, realpath } from 'fs/promises'
|
||||||
|
import { resolve, sep } from 'path'
|
||||||
|
import type { LocalGitModule } from '../shared/gitModuleImport'
|
||||||
|
|
||||||
|
export async function localGitModulePath(root: string, folder: string): Promise<string> {
|
||||||
|
if (!/^\[lms-nav2-custom\][a-z][a-z0-9_]*_v[^/\\]+_ws$/.test(folder)) throw new Error('Invalid custom module folder')
|
||||||
|
const base = await realpath(root)
|
||||||
|
const target = await realpath(resolve(base, folder))
|
||||||
|
if (!target.startsWith(base + sep)) throw new Error('Module is outside the local workspace')
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listLocalGitModules(root: string): Promise<LocalGitModule[]> {
|
||||||
|
const entries = await readdir(root, { withFileTypes: true }).catch((error: NodeJS.ErrnoException) => {
|
||||||
|
if (error.code === 'ENOENT') return []
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
const modules: LocalGitModule[] = []
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isDirectory() || !entry.name.startsWith('[lms-nav2-custom]')) continue
|
||||||
|
try {
|
||||||
|
const target = await localGitModulePath(root, entry.name)
|
||||||
|
const data = JSON.parse(await readFile(resolve(target, '.autonomy-studio-module.json'), 'utf8'))
|
||||||
|
if (data.sourceType !== 'CUSTOM' || !['name', 'category', 'packageName', 'launchFile'].every(key => typeof data[key] === 'string' && data[key])) continue
|
||||||
|
modules.push({ name: data.name, category: data.category, packageName: data.packageName, launchFile: data.launchFile, folderName: entry.name })
|
||||||
|
} catch { /* Ignore incomplete module folders. */ }
|
||||||
|
}
|
||||||
|
return modules.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function repositoryAddress(value: string): string {
|
||||||
|
// Do not allow local paths, Git remote helpers, or credentials in the UI.
|
||||||
|
const normalized = /^git@[^:]+:.+/.test(value) ? value.replace(/^git@([^:]+):(.+)$/, 'ssh://git@$1/$2') : value
|
||||||
|
const url = new URL(normalized)
|
||||||
|
if (!['https:', 'http:', 'ssh:'].includes(url.protocol)) throw new Error('Unsupported Git repository protocol')
|
||||||
|
url.password = ''
|
||||||
|
if (url.protocol !== 'ssh:') url.username = ''
|
||||||
|
url.search = ''
|
||||||
|
url.hash = ''
|
||||||
|
return url.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remoteBranches(output: string): Array<{ name: string; commit: string }> {
|
||||||
|
return output.split('\n').flatMap(line => {
|
||||||
|
const match = line.trim().match(/^([a-f0-9]{40,64})\s+refs\/heads\/(.+)$/)
|
||||||
|
return match ? [{ name: match[2], commit: match[1] }] : []
|
||||||
|
}).sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { strict as assert } from 'node:assert'
|
||||||
|
import { test } from 'node:test'
|
||||||
|
import { createRequire } from 'node:module'
|
||||||
|
|
||||||
|
const { moduleProgress, createModuleProgressParser } = createRequire(import.meta.url)('./moduleProgress.ts') as typeof import('./moduleProgress')
|
||||||
|
|
||||||
|
test('progress parser handles split lines and ignores normal command output', () => {
|
||||||
|
const messages: string[] = []
|
||||||
|
const parse = moduleProgress.run((message) => messages.push(message), createModuleProgressParser)
|
||||||
|
parse('ordinary output\nLMS_MODULE_PRO')
|
||||||
|
parse('GRESS:download\nLMS_MODULE_PROGRESS:load\n')
|
||||||
|
assert.deepEqual(messages, ['download', 'load'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('concurrent operations retain their own progress callbacks', () => {
|
||||||
|
const first: string[] = []
|
||||||
|
const second: string[] = []
|
||||||
|
const a = moduleProgress.run((message) => first.push(message), createModuleProgressParser)
|
||||||
|
const b = moduleProgress.run((message) => second.push(message), createModuleProgressParser)
|
||||||
|
b('LMS_MODULE_PROGRESS:build\n')
|
||||||
|
a('LMS_MODULE_PROGRESS:download\n')
|
||||||
|
assert.deepEqual(first, ['download'])
|
||||||
|
assert.deepEqual(second, ['build'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('commands outside a module operation do not report progress', () => {
|
||||||
|
assert.doesNotThrow(() => createModuleProgressParser()('LMS_MODULE_PROGRESS:ignored\n'))
|
||||||
|
})
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
||||||
|
|
||||||
|
export const moduleProgress = new AsyncLocalStorage<(message: string) => void>()
|
||||||
|
|
||||||
|
export function reportModuleProgress(message: string): void {
|
||||||
|
moduleProgress.getStore()?.(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createModuleProgressParser(): (chunk: string) => void {
|
||||||
|
const report = moduleProgress.getStore()
|
||||||
|
let pending = ''
|
||||||
|
return (chunk) => {
|
||||||
|
if (!report) return
|
||||||
|
pending += chunk
|
||||||
|
const lines = pending.split('\n')
|
||||||
|
pending = lines.pop()!.slice(-4096)
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith('LMS_MODULE_PROGRESS:')) report(line.slice('LMS_MODULE_PROGRESS:'.length).trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
type Request = { action: 'list' } | { action: 'content'; path: string } | { action: 'search'; query: string }
|
||||||
|
|
||||||
|
export function remoteModuleFilesScript(root: string, request: Request): string {
|
||||||
|
const input = Buffer.from(JSON.stringify({ root, ...request })).toString('base64')
|
||||||
|
return `python3 - '${input}' <<'PY'
|
||||||
|
import base64, json, os, stat, sys
|
||||||
|
args = json.loads(base64.b64decode(sys.argv[1]))
|
||||||
|
root = os.path.realpath(args['root'])
|
||||||
|
skip = {'.git', 'build', 'install', 'log', 'node_modules', 'docker'}
|
||||||
|
extensions = {'.c', '.cc', '.cpp', '.cxx', '.h', '.hpp', '.hh', '.py', '.xml', '.urdf', '.xacro', '.yaml', '.yml', '.json', '.txt', '.md', '.cmake', '.launch', '.sh', '.bash', '.toml', '.ini', '.cfg', '.rviz'}
|
||||||
|
placeholder = 'Binary or large file preview is not available.'
|
||||||
|
def content(relative):
|
||||||
|
if os.path.isabs(relative) or '..' in relative.split('/'):
|
||||||
|
raise ValueError('Invalid module path')
|
||||||
|
path = os.path.realpath(os.path.join(root, relative))
|
||||||
|
if os.path.commonpath([root, path]) != root:
|
||||||
|
raise ValueError('Invalid module path')
|
||||||
|
info = os.stat(path)
|
||||||
|
if not stat.S_ISREG(info.st_mode):
|
||||||
|
raise ValueError('Not a regular file')
|
||||||
|
if os.path.splitext(path)[1].lower() not in extensions or info.st_size > 1048576:
|
||||||
|
return placeholder
|
||||||
|
with open(path, 'rb') as fp:
|
||||||
|
data = fp.read(1048577)
|
||||||
|
if len(data) > 1048576 or b'\\x00' in data:
|
||||||
|
return placeholder
|
||||||
|
return data.decode('utf-8', errors='replace')
|
||||||
|
def entries():
|
||||||
|
def fail(error):
|
||||||
|
raise error
|
||||||
|
for current, dirs, files in os.walk(root, onerror=fail, followlinks=False):
|
||||||
|
depth = len(os.path.relpath(current, root).split('/')) if current != root else 0
|
||||||
|
dirs[:] = sorted(d for d in dirs if d not in skip and not os.path.islink(os.path.join(current, d)))
|
||||||
|
if depth >= 10:
|
||||||
|
dirs[:] = []
|
||||||
|
continue
|
||||||
|
for name in dirs:
|
||||||
|
yield 'D', os.path.relpath(os.path.join(current, name), root)
|
||||||
|
for name in sorted(files):
|
||||||
|
path = os.path.join(current, name)
|
||||||
|
if not os.path.islink(path) and os.path.isfile(path):
|
||||||
|
yield 'F', os.path.relpath(path, root)
|
||||||
|
if args['action'] == 'content':
|
||||||
|
print(json.dumps({'content': content(args['path'])}))
|
||||||
|
elif args['action'] == 'list':
|
||||||
|
paths, folders = [], []
|
||||||
|
for kind, path in entries():
|
||||||
|
(folders if kind == 'D' else paths).append(path)
|
||||||
|
print(json.dumps({'filePaths': paths, 'folders': folders}))
|
||||||
|
else:
|
||||||
|
query = args['query'].strip().lower()
|
||||||
|
results = []
|
||||||
|
if len(query) >= 2:
|
||||||
|
for kind, path in entries():
|
||||||
|
if kind != 'F':
|
||||||
|
continue
|
||||||
|
text = content(path)
|
||||||
|
if text == placeholder:
|
||||||
|
continue
|
||||||
|
for number, line in enumerate(text.split('\\n'), 1):
|
||||||
|
if query in line.lower():
|
||||||
|
results.append({'file': path, 'lineNumber': number, 'lineText': (line.strip() or line)[:2000]})
|
||||||
|
if len(results) >= 200:
|
||||||
|
break
|
||||||
|
if len(results) >= 200:
|
||||||
|
break
|
||||||
|
print(json.dumps(results))
|
||||||
|
PY`
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
function quote(value: string): string {
|
||||||
|
return `'${value.replace(/'/g, `'"'"'`)}'`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSH may forward LANG/LC_* through the user's SendEnv configuration.
|
||||||
|
// Undefined entries also remove inherited keys when merged by the command runner.
|
||||||
|
export function remoteShellEnvironment(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||||
|
const result = { ...env }
|
||||||
|
for (const key of Object.keys(result)) {
|
||||||
|
if (key === 'LANGUAGE' || key.startsWith('LC_')) result[key] = undefined
|
||||||
|
}
|
||||||
|
return { ...result, LANG: 'C', LC_ALL: 'C' }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remoteShellCommand(command: string): string {
|
||||||
|
const script = [
|
||||||
|
'selected_locale=C',
|
||||||
|
// Probe under C so an inherited, unavailable locale cannot break the probe.
|
||||||
|
'available_locales=$(locale -a 2>/dev/null) || available_locales=',
|
||||||
|
'for candidate in $available_locales; do',
|
||||||
|
' case "$candidate" in C.utf8|C.UTF-8) selected_locale=$candidate; break ;;',
|
||||||
|
' *utf8|*UTF-8|*utf-8) if [ "$selected_locale" = C ]; then selected_locale=$candidate; fi ;;',
|
||||||
|
' esac',
|
||||||
|
'done',
|
||||||
|
`exec env LANG="$selected_locale" LC_ALL="$selected_locale" LANGUAGE=C /bin/bash --noprofile --norc -c ${quote(command)}`,
|
||||||
|
].join('\n')
|
||||||
|
return `env LANG=C LC_ALL=C LANGUAGE=C /bin/sh -c ${quote(script)}`
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import { strict as assert } from 'node:assert'
|
||||||
|
import { mkdtempSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs'
|
||||||
|
import { tmpdir } from 'node:os'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import { spawnSync } from 'node:child_process'
|
||||||
|
import { test } from 'node:test'
|
||||||
|
import ts from 'typescript'
|
||||||
|
|
||||||
|
const source = ts.createSourceFile('index.ts', readFileSync(new URL('./index.ts', import.meta.url), 'utf8'), ts.ScriptTarget.Latest, true)
|
||||||
|
function loadFunction(name: string, dependencies: Record<string, unknown>) {
|
||||||
|
const declaration = source.statements.find((item) => ts.isFunctionDeclaration(item) && item.name?.text === name)
|
||||||
|
assert.ok(declaration)
|
||||||
|
const code = ts.transpile(declaration.getText(source), { target: ts.ScriptTarget.ES2022 })
|
||||||
|
return new Function(...Object.keys(dependencies), `${code}; return ${name};`)(...Object.values(dependencies))
|
||||||
|
}
|
||||||
|
|
||||||
|
function command(executable: string, args: string[], cwd: string) {
|
||||||
|
const result = spawnSync(executable, args, {
|
||||||
|
cwd, encoding: 'utf8', timeout: 15000,
|
||||||
|
env: { ...process.env, GIT_AUTHOR_NAME: 'Test', GIT_AUTHOR_EMAIL: 'test@example.com', GIT_COMMITTER_NAME: 'Test', GIT_COMMITTER_EMAIL: 'test@example.com' },
|
||||||
|
})
|
||||||
|
assert.equal(result.status, 0, result.stderr || String(result.error))
|
||||||
|
return result.stdout.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixture() {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), 'lms-preserve-'))
|
||||||
|
const template = join(root, 'template')
|
||||||
|
mkdirSync(template)
|
||||||
|
command('git', ['init', '-b', 'main'], template)
|
||||||
|
writeFileSync(join(template, 'node.py'), 'template source\n')
|
||||||
|
writeFileSync(join(template, '.gitignore'), 'build/\ninstall/\nlog/\n')
|
||||||
|
command('git', ['add', '.'], template)
|
||||||
|
command('git', ['commit', '-m', 'template'], template)
|
||||||
|
const install = loadFunction('installRemoteWorkspaceModule', {
|
||||||
|
normalizeRemoteTarget: (target: unknown) => target,
|
||||||
|
remoteRepositoryUrl: (url: string) => url,
|
||||||
|
withGitCredential: (url: string) => url,
|
||||||
|
remotePathJoin: join,
|
||||||
|
safeArtifactFileName: () => 'unused.tar',
|
||||||
|
quoteShellSingle: (value: string) => "'" + value.replaceAll("'", "'\\''") + "'",
|
||||||
|
encodeBase64: (value: string) => Buffer.from(value).toString('base64'),
|
||||||
|
expandRemotePathVar: () => [],
|
||||||
|
runSsh: async (_target: unknown, script: string) => command('bash', ['-c', script], root),
|
||||||
|
parseModuleFolderName: () => ({ name: 'Test', category: 'Control' }),
|
||||||
|
})
|
||||||
|
const target = { workspaceRoot: join(root, 'workspace') }
|
||||||
|
return { root, template, target, install, modulePath: join(target.workspaceRoot, 'module') }
|
||||||
|
}
|
||||||
|
|
||||||
|
test('reinstall preserves saved source, local commits, untracked files and build outputs despite changed repository URLs', async () => {
|
||||||
|
const f = fixture()
|
||||||
|
try {
|
||||||
|
await f.install(f.target, 'Test', 'module', f.template)
|
||||||
|
writeFileSync(join(f.modulePath, 'node.py'), 'student commit\n')
|
||||||
|
command('git', ['add', 'node.py'], f.modulePath)
|
||||||
|
command('git', ['commit', '-m', 'student work'], f.modulePath)
|
||||||
|
const head = command('git', ['rev-parse', 'HEAD'], f.modulePath)
|
||||||
|
writeFileSync(join(f.modulePath, 'node.py'), 'saved but not committed\n')
|
||||||
|
writeFileSync(join(f.modulePath, 'new.py'), 'new student file\n')
|
||||||
|
for (const dir of ['build', 'install', 'log']) {
|
||||||
|
mkdirSync(join(f.modulePath, dir))
|
||||||
|
writeFileSync(join(f.modulePath, dir, 'keep'), dir)
|
||||||
|
}
|
||||||
|
const status = command('git', ['status', '--porcelain'], f.modulePath)
|
||||||
|
await f.install(f.target, 'Test', 'module', '/missing/new-template', '/missing/new-student', {
|
||||||
|
prebuiltArtifacts: [{ url: 'https://invalid.example/overwrite.tar', required: true }],
|
||||||
|
})
|
||||||
|
assert.equal(command('git', ['rev-parse', 'HEAD'], f.modulePath), head)
|
||||||
|
assert.equal(command('git', ['status', '--porcelain'], f.modulePath), status)
|
||||||
|
assert.equal(readFileSync(join(f.modulePath, 'node.py'), 'utf8'), 'saved but not committed\n')
|
||||||
|
assert.equal(readFileSync(join(f.modulePath, 'new.py'), 'utf8'), 'new student file\n')
|
||||||
|
for (const dir of ['build', 'install', 'log']) assert.equal(readFileSync(join(f.modulePath, dir, 'keep'), 'utf8'), dir)
|
||||||
|
} finally { rmSync(f.root, { recursive: true, force: true }) }
|
||||||
|
})
|
||||||
|
|
||||||
|
test('first install restores the student repository instead of replacing it with template source', async () => {
|
||||||
|
const f = fixture()
|
||||||
|
try {
|
||||||
|
const student = join(f.root, 'student')
|
||||||
|
command('git', ['clone', f.template, student], f.root)
|
||||||
|
writeFileSync(join(student, 'node.py'), 'submitted student work\n')
|
||||||
|
command('git', ['add', '.'], student)
|
||||||
|
command('git', ['commit', '-m', 'submission'], student)
|
||||||
|
await f.install(f.target, 'Test', 'module', f.template, student)
|
||||||
|
assert.equal(readFileSync(join(f.modulePath, 'node.py'), 'utf8'), 'submitted student work\n')
|
||||||
|
assert.equal(command('git', ['branch', '--show-current'], f.modulePath), 'student-work')
|
||||||
|
} finally { rmSync(f.root, { recursive: true, force: true }) }
|
||||||
|
})
|
||||||
|
|
||||||
|
test('first install initializes an empty student repository', async () => {
|
||||||
|
const f = fixture()
|
||||||
|
try {
|
||||||
|
const student = join(f.root, 'student.git')
|
||||||
|
command('git', ['init', '--bare', student], f.root)
|
||||||
|
await f.install(f.target, 'Test', 'module', f.template, student)
|
||||||
|
assert.equal(command('git', ['--git-dir', student, 'show', 'main:node.py'], f.root), 'template source')
|
||||||
|
} finally { rmSync(f.root, { recursive: true, force: true }) }
|
||||||
|
})
|
||||||
|
|
||||||
|
test('legacy session reset only stops modules and returns stop failures', async () => {
|
||||||
|
const target = { workspaceRoot: '/student/workspace' }
|
||||||
|
const modules = [{ moduleName: 'Test', folderName: 'module', templateRepositoryUrl: '/template' }]
|
||||||
|
const result = { ok: false, message: 'stop failed' }
|
||||||
|
let calls = 0
|
||||||
|
const reset = loadFunction('resetRemoteWorkspaceSession', {
|
||||||
|
stopRemoteWorkspaceModules: async (actualTarget: unknown, actualModules: unknown) => {
|
||||||
|
calls++
|
||||||
|
assert.equal(actualTarget, target)
|
||||||
|
assert.equal(actualModules, modules)
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.equal(await reset(target, modules), result)
|
||||||
|
assert.equal(calls, 1)
|
||||||
|
})
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
||||||
|
|
||||||
|
export const visualizationProgress = new AsyncLocalStorage<(message: string) => void>()
|
||||||
|
|
||||||
|
export function reportVisualizationProgress(message: string): void {
|
||||||
|
visualizationProgress.getStore()?.(message)
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { strict as assert } from 'node:assert'
|
||||||
|
import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
|
||||||
|
import { tmpdir } from 'node:os'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import { spawnSync } from 'node:child_process'
|
||||||
|
import { test } from 'node:test'
|
||||||
|
import { createRequire } from 'node:module'
|
||||||
|
import ts from 'typescript'
|
||||||
|
const { yahboomDevicePreflight, yahboomModuleCases, yahboomRuntimeImage, yahboomRuntimeArtifacts, yahboomSimulation } = createRequire(import.meta.url)('./yahboomRuntime.ts') as typeof import('./yahboomRuntime')
|
||||||
|
|
||||||
|
test('simulation is an explicit opt-in and excludes physical device modules', () => {
|
||||||
|
for (const role of ['vehicle', 'slam', 'map', 'localization', 'planning', 'control']) {
|
||||||
|
const folder = `[lms-nav2-yahboom-${role}]yahboom_${role}_v1.0.0_ws`
|
||||||
|
assert.equal(yahboomSimulation(folder, ''), false)
|
||||||
|
assert.equal(yahboomSimulation(folder, 'sim_mode:=false'), false)
|
||||||
|
assert.equal(yahboomSimulation(folder, 'foo:=bar sim_mode:=true'), true)
|
||||||
|
}
|
||||||
|
for (const role of ['agent', 'camera']) {
|
||||||
|
assert.equal(yahboomSimulation(`[lms-nav2-yahboom-${role}]yahboom_${role}_v1.0.0_ws`, 'sim_mode:=true'), false)
|
||||||
|
}
|
||||||
|
assert.equal(yahboomSimulation('[lms-nav2-mkmini]nav2-mkmini_v1.0.0_ws', 'sim_mode:=true'), false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('local simulation overrides ARM metadata and downloads only the PC image', async () => {
|
||||||
|
const source = ts.createSourceFile('index.ts', readFileSync(new URL('./index.ts', import.meta.url), 'utf8'), ts.ScriptTarget.Latest, true)
|
||||||
|
const declaration = source.statements.find((item): item is ts.FunctionDeclaration => ts.isFunctionDeclaration(item) && item.name?.text === 'resolveLocalModuleRuntime')
|
||||||
|
assert.ok(declaration)
|
||||||
|
const artifact = { image: 'future-nav2-gazebo:humble', url: 'https://example.com/gazebo.tar.zst' }
|
||||||
|
const resolver = new Function('yahboomSimulation', 'defaultNav2GazeboRuntimeImage', 'resolveCustomModuleRuntimeImage',
|
||||||
|
ts.transpile(declaration.getText(source), { target: ts.ScriptTarget.ES2022 }) + '; return resolveLocalModuleRuntime;'
|
||||||
|
)(yahboomSimulation, artifact, () => { throw new Error('ARM metadata must not override simulation') })
|
||||||
|
const result = await resolver('/module', '[lms-nav2-yahboom-vehicle]yahboom_vehicle_v1.0.0_ws', 'sim_mode:=true',
|
||||||
|
[{ image: 'future-nav2:humble-jetson_orin', url: 'https://example.com/arm.tar.zst' }])
|
||||||
|
assert.deepEqual(result, { image: artifact.image, artifacts: [artifact] })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('remote Yahboom downloads only its own image and preserves explicit overrides', () => {
|
||||||
|
const configured = [{ image: 'future-nav2:humble-jetson_orin', url: 'https://example.com/jetson.tar.zst' }]
|
||||||
|
assert.equal(yahboomRuntimeArtifacts('[lms-nav2-mkmini]nav2-mkmini_v1.0.0_ws', configured[0].image, configured), configured)
|
||||||
|
const folder = '[lms-nav2-yahboom-agent]yahboom_agent_v1.0.0_ws'
|
||||||
|
const image = 'future-nav2:humble-yahboom-agent'
|
||||||
|
const selected = yahboomRuntimeArtifacts(folder, image, configured)
|
||||||
|
assert.equal(selected?.length, 1)
|
||||||
|
assert.equal(selected?.[0].image, image)
|
||||||
|
assert.match(selected?.[0].checksumSha256 ?? '', /^[a-f0-9]{64}$/)
|
||||||
|
const override = { image, url: 'https://example.com/override.tar.zst' }
|
||||||
|
assert.deepEqual(yahboomRuntimeArtifacts(folder, image, [...configured, override]), [override])
|
||||||
|
assert.deepEqual(yahboomRuntimeArtifacts(folder, 'custom:tag', configured), [])
|
||||||
|
})
|
||||||
|
|
||||||
|
function runtimeScript(name: string): string {
|
||||||
|
const source = ts.createSourceFile('index.ts', readFileSync(new URL('./index.ts', import.meta.url), 'utf8'), ts.ScriptTarget.Latest, true)
|
||||||
|
const declaration = source.statements.find((item): item is ts.FunctionDeclaration => ts.isFunctionDeclaration(item) && item.name?.text === name)
|
||||||
|
assert.ok(declaration)
|
||||||
|
const code = ts.transpile(declaration.getText(source), { target: ts.ScriptTarget.ES2022 })
|
||||||
|
return new Function('yahboomModuleCases', 'yahboomDevicePreflight', `${code}; return ${name}();`)(yahboomModuleCases, yahboomDevicePreflight)
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Yahboom image routing does not change MKMini, Gazebo or custom modules', () => {
|
||||||
|
assert.equal(yahboomRuntimeImage('[lms-nav2-yahboom-agent]yahboom_agent_v1.0.0_ws'), 'future-nav2:humble-yahboom-agent')
|
||||||
|
assert.equal(yahboomRuntimeImage('[lms-nav2-yahboom-slam]yahboom_slam_v1.0.0_ws'), 'future-nav2:humble-yahboom')
|
||||||
|
for (const name of ['[lms-nav2-mkmini]nav2-mkmini_v1.0.0_ws', '[lms-nav2-gazebo-map]nav2-map_v1.0.0_ws', '[lms-nav2-custom]yahboom_agent_v1.0.0_ws', '[lms-nav2-yahboom-agent]other_v1.0.0_ws']) {
|
||||||
|
assert.equal(yahboomRuntimeImage(name), null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('all eight templates map to their own package in both runtime scripts', () => {
|
||||||
|
for (const build of [false, true]) {
|
||||||
|
for (const role of ['agent', 'vehicle', 'slam', 'map', 'localization', 'planning', 'control', 'camera']) {
|
||||||
|
const script = ['case "$1" in', ...yahboomModuleCases(build), 'esac', build ? 'printf "%s" "$TARGET"' : 'printf "%s %s" "$PACKAGE" "$LAUNCH"'].join('\n')
|
||||||
|
const result = spawnSync('bash', ['-c', script, '_', `[lms-nav2-yahboom-${role}]yahboom_${role}_v1.0.0_ws`], { encoding: 'utf8' })
|
||||||
|
assert.equal(result.status, 0, result.stderr)
|
||||||
|
assert.equal(result.stdout, build ? `yahboom_${role}` : `yahboom_${role} ${role}.launch.py`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('generated build and run shell scripts are syntactically valid', () => {
|
||||||
|
for (const name of ['buildNav2DockerRunScript', 'buildNav2DockerBuildScript']) {
|
||||||
|
const result = spawnSync('bash', ['-n'], { input: runtimeScript(name), encoding: 'utf8' })
|
||||||
|
assert.equal(result.status, 0, result.stderr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('invalid hardware path fails before Docker is called; MKMini still launches without extra device', () => {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), 'yahboom-runtime-'))
|
||||||
|
try {
|
||||||
|
const bin = join(root, 'bin')
|
||||||
|
mkdirSync(bin)
|
||||||
|
writeFileSync(join(bin, 'docker'), '#!/bin/bash\nprintf "%s\\n" "$@"\n', { mode: 0o755 })
|
||||||
|
const script = join(root, 'docker_run_nav2.sh')
|
||||||
|
writeFileSync(script, runtimeScript('buildNav2DockerRunScript'))
|
||||||
|
for (const [folder, parameter] of [
|
||||||
|
['[lms-nav2-yahboom-agent]yahboom_agent_v1.0.0_ws', 'serial_device:=/etc/passwd'],
|
||||||
|
['[lms-nav2-yahboom-camera]yahboom_camera_v1.0.0_ws', 'video_device:=/dev/../etc/passwd'],
|
||||||
|
['[lms-nav2-mkmini]nav2-mkmini_v1.0.0_ws', ''],
|
||||||
|
]) {
|
||||||
|
mkdirSync(join(root, folder, 'install'), { recursive: true })
|
||||||
|
writeFileSync(join(root, folder, 'install/setup.bash'), '')
|
||||||
|
const result = spawnSync('bash', [script, 'test-container', root, folder, 'test-image', '77', 'false', parameter], {
|
||||||
|
encoding: 'utf8', env: { ...process.env, PATH: `${bin}:${process.env.PATH}`, DISPLAY: '', FASTDDS_CONFIG_PATH: '' },
|
||||||
|
})
|
||||||
|
if (parameter) {
|
||||||
|
assert.equal(result.status, 64)
|
||||||
|
assert.equal(result.stdout, '')
|
||||||
|
} else {
|
||||||
|
assert.equal(result.status, 0, result.stderr)
|
||||||
|
assert.match(result.stdout, /vehicle_pkg\nvehicle.launch.py/)
|
||||||
|
assert.doesNotMatch(result.stdout, /ttyUSB|video0|privileged/)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
rmSync(root, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
const roles = ['agent', 'vehicle', 'slam', 'map', 'localization', 'planning', 'control', 'camera'] as const
|
||||||
|
|
||||||
|
export function yahboomSimulation(folderName: string | null | undefined, rosParams: string): boolean {
|
||||||
|
return Boolean(yahboomRuntimeImage(folderName))
|
||||||
|
&& !/yahboom-(agent|camera)\]/.test(folderName ?? '')
|
||||||
|
&& /(?:^|\s)sim_mode:=true(?:\s|$)/.test(rosParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuntimeArtifact = {
|
||||||
|
image?: string | null
|
||||||
|
url?: string | null
|
||||||
|
fileName?: string | null
|
||||||
|
checksumSha256?: string | null
|
||||||
|
required?: boolean | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const artifactRoot = 'https://pub-ca72c40797144462bdb7d200c215d361.r2.dev/studio-artifacts/runtime-images/nav2/yahboom/arm64'
|
||||||
|
const artifacts: RuntimeArtifact[] = [
|
||||||
|
['future-nav2:humble-yahboom', 'future-nav2-humble-yahboom.tar.zst', 'b2f168c45b961ab96badeeb7df68d1c997996c75dcb5e0cc677b0b6669b1ffd2'],
|
||||||
|
['future-nav2:humble-yahboom-agent', 'future-nav2-humble-yahboom-agent.tar.zst', 'e30404506c320af1d53c8733177eaf27997dcb2864019b8fcc6888874f02d191'],
|
||||||
|
].map(([image, fileName, checksumSha256]) => ({
|
||||||
|
image, fileName, checksumSha256, required: true,
|
||||||
|
url: `${artifactRoot}/${checksumSha256.slice(0, 16)}/${fileName}`,
|
||||||
|
}))
|
||||||
|
|
||||||
|
export function yahboomRuntimeArtifacts(
|
||||||
|
folderName: string,
|
||||||
|
runtimeImage: string | null,
|
||||||
|
configured?: RuntimeArtifact[] | null,
|
||||||
|
): RuntimeArtifact[] | null | undefined {
|
||||||
|
if (!yahboomRuntimeImage(folderName)) return configured
|
||||||
|
const selected = configured?.find((item) => item.image?.trim() === runtimeImage && item.url?.trim())
|
||||||
|
?? artifacts.find((item) => item.image === runtimeImage)
|
||||||
|
// Yahboom must not download unrelated Jetson images from an existing profile.
|
||||||
|
return selected ? [selected] : []
|
||||||
|
}
|
||||||
|
|
||||||
|
export function yahboomRuntimeImage(folderName?: string | null): string | null {
|
||||||
|
const role = folderName?.match(/^\[lms-nav2-yahboom-([a-z]+)\]yahboom_\1_v[^/]+_ws$/)?.[1]
|
||||||
|
if (!roles.some((value) => value === role)) return null
|
||||||
|
return role === 'agent' ? 'future-nav2:humble-yahboom-agent' : 'future-nav2:humble-yahboom'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function yahboomModuleCases(build = false): string[] {
|
||||||
|
return roles.map((role) => (
|
||||||
|
` "[lms-nav2-yahboom-${role}]yahboom_${role}_v"*_ws) ${build ? `TARGET=yahboom_${role}` : `PACKAGE=yahboom_${role}; LAUNCH=${role}.launch.py`} ;;`
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep hardware exposure limited to the two hardware-facing Yahboom templates.
|
||||||
|
export function yahboomDevicePreflight(): string[] {
|
||||||
|
return [
|
||||||
|
'YAHBOOM_DEVICE=""',
|
||||||
|
'case "$MODULE_FOLDER" in',
|
||||||
|
' "[lms-nav2-yahboom-agent]yahboom_agent_v"*_ws)',
|
||||||
|
' YAHBOOM_DEVICE=/dev/ttyUSB0',
|
||||||
|
' for arg in "$@"; do case "$arg" in serial_device:=*) YAHBOOM_DEVICE=${arg#serial_device:=} ;; esac; done',
|
||||||
|
' case "$YAHBOOM_DEVICE" in /dev/ttyUSB*|/dev/ttyACM*|/dev/serial/by-id/*) ;; *) echo "Invalid Yahboom serial device" >&2; exit 64 ;; esac',
|
||||||
|
' ;;',
|
||||||
|
' "[lms-nav2-yahboom-camera]yahboom_camera_v"*_ws)',
|
||||||
|
' YAHBOOM_DEVICE=/dev/video0',
|
||||||
|
' for arg in "$@"; do case "$arg" in video_device:=*) YAHBOOM_DEVICE=${arg#video_device:=} ;; esac; done',
|
||||||
|
' [[ "$YAHBOOM_DEVICE" =~ ^/dev/video[0-9]+$ ]] || { echo "Invalid Yahboom camera device" >&2; exit 64; }',
|
||||||
|
' ;;',
|
||||||
|
'esac',
|
||||||
|
'if [ -n "$YAHBOOM_DEVICE" ]; then',
|
||||||
|
' [[ "$YAHBOOM_DEVICE" != *".."* ]] && [ -c "$YAHBOOM_DEVICE" ] || { echo "Yahboom device not found: $YAHBOOM_DEVICE" >&2; exit 1; }',
|
||||||
|
'fi',
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,371 @@
|
|||||||
|
import { contextBridge, ipcRenderer } from 'electron'
|
||||||
|
import { electronAPI } from '@electron-toolkit/preload'
|
||||||
|
import { readFileSync, rmSync } from 'fs'
|
||||||
|
|
||||||
|
let visualizationRequestId = 0
|
||||||
|
async function invokeVisualizationStart(channel: string, args: unknown[], onProgress?: (message: string) => void) {
|
||||||
|
const requestId = `${Date.now()}:${++visualizationRequestId}`
|
||||||
|
const listener = (_event: Electron.IpcRendererEvent, id: string, message: string) => {
|
||||||
|
if (id === requestId) onProgress?.(message)
|
||||||
|
}
|
||||||
|
ipcRenderer.on('visualization:progress', listener)
|
||||||
|
try {
|
||||||
|
return await ipcRenderer.invoke(channel, ...args, requestId)
|
||||||
|
} finally {
|
||||||
|
ipcRenderer.removeListener('visualization:progress', listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type LauncherAuthPayload = {
|
||||||
|
appUrl?: string | null
|
||||||
|
handoffToken?: string | null
|
||||||
|
audienceApp?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemoteWorkspaceTarget = {
|
||||||
|
host: string
|
||||||
|
user?: string | null
|
||||||
|
port?: number | null
|
||||||
|
workspaceRoot?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModulePrebuiltArtifact = {
|
||||||
|
type?: string | null
|
||||||
|
url?: string | null
|
||||||
|
fileName?: string | null
|
||||||
|
checksumSha256?: string | null
|
||||||
|
extractPath?: string | null
|
||||||
|
architecture?: string | null
|
||||||
|
required?: boolean | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuntimeImageArtifact = {
|
||||||
|
image?: string | null
|
||||||
|
url?: string | null
|
||||||
|
fileName?: string | null
|
||||||
|
checksumSha256?: string | null
|
||||||
|
required?: boolean | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModuleInstallOptions = {
|
||||||
|
prebuiltArtifacts?: ModulePrebuiltArtifact[] | null
|
||||||
|
runtimeImages?: RuntimeImageArtifact[] | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type RosNetworkConfig = {
|
||||||
|
enabled?: boolean | null
|
||||||
|
allowExternal?: boolean
|
||||||
|
peer?: RemoteWorkspaceTarget | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type SavedMapArtifactName = 'yaml' | 'pgm' | 'database'
|
||||||
|
|
||||||
|
type SavedMapArtifactPath = {
|
||||||
|
name: SavedMapArtifactName
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SavedMapArtifactDownload = {
|
||||||
|
name: SavedMapArtifactName
|
||||||
|
fileName: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodeArgValue = (prefix: string): string | null => {
|
||||||
|
const rawValue = process.argv.find((arg) => arg.startsWith(prefix))?.slice(prefix.length)
|
||||||
|
if (!rawValue) return null
|
||||||
|
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(rawValue)
|
||||||
|
} catch {
|
||||||
|
return rawValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const readLauncherAuthFile = (): LauncherAuthPayload | null => {
|
||||||
|
const filePath = process.env.ATNMY_HANDOFF_FILE
|
||||||
|
if (!filePath) {
|
||||||
|
console.info('[lms-preload] launcher auth file env is empty')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(readFileSync(filePath, 'utf8')) as LauncherAuthPayload
|
||||||
|
rmSync(filePath, { force: true })
|
||||||
|
console.info('[lms-preload] launcher auth file loaded', {
|
||||||
|
hasHandoffToken: Boolean(payload.handoffToken),
|
||||||
|
audienceApp: payload.audienceApp ?? null
|
||||||
|
})
|
||||||
|
return payload
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[lms-preload] launcher auth file read failed', error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const launcherAuthFile = readLauncherAuthFile()
|
||||||
|
|
||||||
|
const launcherAuth = {
|
||||||
|
appUrl: launcherAuthFile?.appUrl ?? decodeArgValue('--url=') ?? process.env.ATNMY_APP_URL ?? null,
|
||||||
|
handoffToken: launcherAuthFile?.handoffToken ?? decodeArgValue('--handoff-token=') ?? process.env.ATNMY_HANDOFF_TOKEN ?? null,
|
||||||
|
audienceApp: launcherAuthFile?.audienceApp ?? decodeArgValue('--audience-app=') ?? process.env.ATNMY_AUDIENCE_APP ?? 'LMS'
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info('[lms-preload] launcher auth resolved', {
|
||||||
|
hasHandoffToken: Boolean(launcherAuth.handoffToken),
|
||||||
|
audienceApp: launcherAuth.audienceApp
|
||||||
|
})
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('electron', electronAPI)
|
||||||
|
contextBridge.exposeInMainWorld('launcherAuth', launcherAuth)
|
||||||
|
contextBridge.exposeInMainWorld('lmsBootSplash', {
|
||||||
|
status: (message: string) => ipcRenderer.send('lms-boot-status', message),
|
||||||
|
done: () => ipcRenderer.send('lms-boot-done')
|
||||||
|
})
|
||||||
|
contextBridge.exposeInMainWorld('titlebar', {
|
||||||
|
minimize: () => ipcRenderer.send('titlebar-minimize'),
|
||||||
|
maximize: () => ipcRenderer.send('titlebar-maximize'),
|
||||||
|
openDevTools: () => ipcRenderer.invoke('titlebar-open-devtools'),
|
||||||
|
close: () => ipcRenderer.send('titlebar-close')
|
||||||
|
})
|
||||||
|
contextBridge.exposeInMainWorld('appDiagnostics', {
|
||||||
|
read: () => ipcRenderer.invoke('app-diagnostics:read')
|
||||||
|
})
|
||||||
|
contextBridge.exposeInMainWorld('appLifecycle', {
|
||||||
|
quit: () => ipcRenderer.send('app-quit')
|
||||||
|
})
|
||||||
|
contextBridge.exposeInMainWorld('learningVideo', {
|
||||||
|
open: (url: string, title?: string) => ipcRenderer.invoke('learning-video:open', { url, title })
|
||||||
|
})
|
||||||
|
contextBridge.exposeInMainWorld('moduleFiles', {
|
||||||
|
read: (moduleName: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('module-files:read', moduleName, workspaceRoot, folderName),
|
||||||
|
image: (moduleName: string, path: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('module-files:image', moduleName, path, workspaceRoot, folderName),
|
||||||
|
create: (moduleName: string, path: string, kind: 'file' | 'folder', workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('module-files:create', moduleName, path, kind, workspaceRoot, folderName),
|
||||||
|
write: (moduleName: string, path: string, content: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('module-files:write', moduleName, path, content, workspaceRoot, folderName),
|
||||||
|
copy: (moduleName: string, fromPath: string, toPath: string, mode: 'copy' | 'cut', workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('module-files:copy', moduleName, fromPath, toPath, mode, workspaceRoot, folderName),
|
||||||
|
rename: (moduleName: string, fromPath: string, toPath: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('module-files:rename', moduleName, fromPath, toPath, workspaceRoot, folderName),
|
||||||
|
delete: (moduleName: string, path: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('module-files:delete', moduleName, path, workspaceRoot, folderName)
|
||||||
|
})
|
||||||
|
contextBridge.exposeInMainWorld('modules', {
|
||||||
|
list: (workspaceRoot?: string, modules?: Array<{ sortOrder?: number | null; category: string; name: string; folderName: string; templateRepositoryUrl?: string | null }>) => ipcRenderer.invoke('modules:list', workspaceRoot, modules),
|
||||||
|
install: (moduleName: string, workspaceRoot?: string, folderName?: string, templateRepositoryUrl?: string | null, studentRepositoryUrl?: string | null, options?: ModuleInstallOptions | null) => ipcRenderer.invoke('modules:install', moduleName, workspaceRoot, folderName, templateRepositoryUrl, studentRepositoryUrl, options),
|
||||||
|
delete: (moduleName: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('modules:delete', moduleName, workspaceRoot, folderName),
|
||||||
|
start: (moduleName: string, workspaceRoot?: string, folderName?: string, rosParams?: string, gpuEnabled?: boolean, rosDomainId?: string, runtimeImages?: RuntimeImageArtifact[] | null, rosNetwork?: RosNetworkConfig | null) => ipcRenderer.invoke('modules:start', moduleName, workspaceRoot, folderName, rosParams, gpuEnabled, rosDomainId, runtimeImages, rosNetwork),
|
||||||
|
build: (moduleName: string, workspaceRoot?: string, folderName?: string, gpuEnabled?: boolean, rosDomainId?: string, runtimeImages?: RuntimeImageArtifact[] | null, rosParams?: string) => ipcRenderer.invoke('modules:build', moduleName, workspaceRoot, folderName, gpuEnabled, rosDomainId, runtimeImages, rosParams),
|
||||||
|
stop: (moduleName: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('modules:stop', moduleName, workspaceRoot, folderName),
|
||||||
|
stopMany: (modules: Array<{ moduleName: string; folderName?: string | null }>, workspaceRoot?: string) => ipcRenderer.invoke('modules:stop-many', modules, workspaceRoot),
|
||||||
|
stopRunning: () => ipcRenderer.invoke('modules:stop-running'),
|
||||||
|
restart: (moduleName: string, workspaceRoot?: string, folderName?: string, rosParams?: string, gpuEnabled?: boolean, rosDomainId?: string, runtimeImages?: RuntimeImageArtifact[] | null, rosNetwork?: RosNetworkConfig | null) => ipcRenderer.invoke('modules:restart', moduleName, workspaceRoot, folderName, rosParams, gpuEnabled, rosDomainId, runtimeImages, rosNetwork),
|
||||||
|
logs: (moduleName: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('modules:logs', moduleName, workspaceRoot, folderName),
|
||||||
|
updateParameters: (moduleName: string, workspaceRoot?: string, folderName?: string, rosParams?: string) => ipcRenderer.invoke('modules:update-parameters', moduleName, workspaceRoot, folderName, rosParams)
|
||||||
|
})
|
||||||
|
contextBridge.exposeInMainWorld('git', {
|
||||||
|
status: (moduleName: string, workspaceRoot?: string, folderName?: string, selectedFilePath?: string | null) => ipcRenderer.invoke('git:status', moduleName, workspaceRoot, folderName, selectedFilePath),
|
||||||
|
submit: (moduleName: string, workspaceRoot?: string, folderName?: string, message?: string, authorName?: string, authorEmail?: string, submissionId?: string, repositoryUrl?: string) => ipcRenderer.invoke('git:submit', moduleName, workspaceRoot, folderName, message, authorName, authorEmail, submissionId, repositoryUrl),
|
||||||
|
teamPublish: (moduleName: string, workspaceRoot?: string, folderName?: string, message?: string, authorName?: string, authorEmail?: string) => ipcRenderer.invoke('git:team-publish', moduleName, workspaceRoot, folderName, message, authorName, authorEmail),
|
||||||
|
teamPull: (moduleName: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('git:team-pull', moduleName, workspaceRoot, folderName),
|
||||||
|
submissions: (modules: Array<{ moduleName: string; folderName?: string | null }>, workspaceRoot?: string) => ipcRenderer.invoke('git:submissions', modules, workspaceRoot),
|
||||||
|
commitDiff: (moduleName: string, workspaceRoot?: string, folderName?: string, commitHash?: string) => ipcRenderer.invoke('git:commit-diff', moduleName, workspaceRoot, folderName, commitHash)
|
||||||
|
})
|
||||||
|
contextBridge.exposeInMainWorld('exampleCode', {
|
||||||
|
read: () => ipcRenderer.invoke('example-code:read')
|
||||||
|
})
|
||||||
|
contextBridge.exposeInMainWorld('terminal', {
|
||||||
|
start: (moduleName?: string, cols?: number, rows?: number, sessionId?: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('terminal-start', moduleName, cols, rows, sessionId, workspaceRoot, folderName),
|
||||||
|
resize: (cols?: number, rows?: number, sessionId?: string) => ipcRenderer.invoke('terminal-resize', cols, rows, sessionId),
|
||||||
|
changeDirectory: (moduleName: string, sessionId?: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('terminal-change-directory', moduleName, sessionId, workspaceRoot, folderName),
|
||||||
|
write: (data: string, sessionId?: string) => ipcRenderer.send('terminal-write', data, sessionId),
|
||||||
|
stop: (sessionId?: string) => ipcRenderer.send('terminal-stop', sessionId),
|
||||||
|
onData: (callback: (data: string) => void, sessionId = 'default') => {
|
||||||
|
const listener = (_event: Electron.IpcRendererEvent, dataSessionId: string, data: string): void => {
|
||||||
|
if (dataSessionId === sessionId) callback(data)
|
||||||
|
}
|
||||||
|
ipcRenderer.on('terminal-data', listener)
|
||||||
|
return () => ipcRenderer.removeListener('terminal-data', listener)
|
||||||
|
},
|
||||||
|
onSuppressData: (callback: (data: string) => void, sessionId = 'default') => {
|
||||||
|
const listener = (_event: Electron.IpcRendererEvent, dataSessionId: string, data: string): void => {
|
||||||
|
if (dataSessionId === sessionId) callback(data)
|
||||||
|
}
|
||||||
|
ipcRenderer.on('terminal-suppress-data', listener)
|
||||||
|
return () => ipcRenderer.removeListener('terminal-suppress-data', listener)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
contextBridge.exposeInMainWorld('visualization', {
|
||||||
|
status: () => ipcRenderer.invoke('visualization:status'),
|
||||||
|
start: (stack?: 'autoware' | 'nav2' | 'slam' | 'slam3d', workspaceRoot?: string, rosDomainId?: string, environmentProfile?: unknown, rosNetwork?: RosNetworkConfig | null) => ipcRenderer.invoke('visualization:start', stack, workspaceRoot, rosDomainId, environmentProfile, rosNetwork),
|
||||||
|
stop: (environmentProfile?: unknown) => ipcRenderer.invoke('visualization:stop', environmentProfile)
|
||||||
|
})
|
||||||
|
contextBridge.exposeInMainWorld('localWorkspace', {
|
||||||
|
onModuleProgress: (callback: (folderName: string, message: string) => void) => {
|
||||||
|
const listener = (_event: Electron.IpcRendererEvent, folderName: string, message: string) => callback(folderName, message)
|
||||||
|
ipcRenderer.on('module:progress', listener)
|
||||||
|
return () => ipcRenderer.removeListener('module:progress', listener)
|
||||||
|
},
|
||||||
|
docker: {
|
||||||
|
status: () => ipcRenderer.invoke('local-workspace:docker:status'),
|
||||||
|
openGuide: () => ipcRenderer.invoke('local-workspace:docker:guide'),
|
||||||
|
},
|
||||||
|
moduleFiles: {
|
||||||
|
read: (moduleName: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('local-workspace:module-files:read', moduleName, workspaceRoot, folderName),
|
||||||
|
image: (moduleName: string, path: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('local-workspace:module-files:image', moduleName, path, workspaceRoot, folderName),
|
||||||
|
create: (moduleName: string, path: string, kind: 'file' | 'folder', workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('local-workspace:module-files:create', moduleName, path, kind, workspaceRoot, folderName),
|
||||||
|
write: (moduleName: string, path: string, content: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('local-workspace:module-files:write', moduleName, path, content, workspaceRoot, folderName),
|
||||||
|
copy: (moduleName: string, fromPath: string, toPath: string, mode: 'copy' | 'cut', workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('local-workspace:module-files:copy', moduleName, fromPath, toPath, mode, workspaceRoot, folderName),
|
||||||
|
rename: (moduleName: string, fromPath: string, toPath: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('local-workspace:module-files:rename', moduleName, fromPath, toPath, workspaceRoot, folderName),
|
||||||
|
delete: (moduleName: string, path: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('local-workspace:module-files:delete', moduleName, path, workspaceRoot, folderName)
|
||||||
|
},
|
||||||
|
agent: {
|
||||||
|
runCommand: (moduleName: string, command: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('local-workspace:agent-command:run', moduleName, command, workspaceRoot, folderName)
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
list: (workspaceRoot?: string, modules?: Array<{ sortOrder?: number | null; category: string; name: string; folderName: string; templateRepositoryUrl?: string | null }>) => ipcRenderer.invoke('local-workspace:modules:list', workspaceRoot, modules),
|
||||||
|
listDockerImages: () => ipcRenderer.invoke('local-workspace:modules:docker-images'),
|
||||||
|
listGitSources: (root: string) => ipcRenderer.invoke('local-workspace:modules:git-sources', root),
|
||||||
|
inspectGitSource: (root: string, folder: string) => ipcRenderer.invoke('local-workspace:modules:git-source', root, folder),
|
||||||
|
install: (moduleName: string, workspaceRoot?: string, folderName?: string, templateRepositoryUrl?: string | null, studentRepositoryUrl?: string | null, options?: ModuleInstallOptions | null) => ipcRenderer.invoke('local-workspace:modules:install', moduleName, workspaceRoot, folderName, templateRepositoryUrl, studentRepositoryUrl, options),
|
||||||
|
createCustom: (workspaceRoot: string | undefined, input: { name: string; category: string; folderName: string; dockerImage?: string | null; packageName: string; launchFile: string }) => ipcRenderer.invoke('local-workspace:modules:create-custom', workspaceRoot, input),
|
||||||
|
selectImportSource: () => ipcRenderer.invoke('local-workspace:modules:select-import-source'),
|
||||||
|
importCustom: (workspaceRoot: string | undefined, input: { name: string; category: string; folderName: string; dockerImage?: string | null; packageName: string; launchFile: string; sourcePath: string }) => ipcRenderer.invoke('local-workspace:modules:import-custom', workspaceRoot, input),
|
||||||
|
delete: (moduleName: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('local-workspace:modules:delete', moduleName, workspaceRoot, folderName),
|
||||||
|
start: (moduleName: string, workspaceRoot?: string, folderName?: string, rosParams?: string, gpuEnabled?: boolean, rosDomainId?: string, runtimeImages?: RuntimeImageArtifact[] | null, rosNetwork?: RosNetworkConfig | null) => ipcRenderer.invoke('local-workspace:modules:start', moduleName, workspaceRoot, folderName, rosParams, gpuEnabled, rosDomainId, runtimeImages, rosNetwork),
|
||||||
|
build: (moduleName: string, workspaceRoot?: string, folderName?: string, gpuEnabled?: boolean, rosDomainId?: string, runtimeImages?: RuntimeImageArtifact[] | null, rosParams?: string) => ipcRenderer.invoke('local-workspace:modules:build', moduleName, workspaceRoot, folderName, gpuEnabled, rosDomainId, runtimeImages, rosParams),
|
||||||
|
stop: (moduleName: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('local-workspace:modules:stop', moduleName, workspaceRoot, folderName),
|
||||||
|
stopMany: (modules: Array<{ moduleName: string; folderName?: string | null }>, workspaceRoot?: string) => ipcRenderer.invoke('local-workspace:modules:stop-many', modules, workspaceRoot),
|
||||||
|
stopRunning: () => ipcRenderer.invoke('local-workspace:modules:stop-running'),
|
||||||
|
restart: (moduleName: string, workspaceRoot?: string, folderName?: string, rosParams?: string, gpuEnabled?: boolean, rosDomainId?: string, runtimeImages?: RuntimeImageArtifact[] | null, rosNetwork?: RosNetworkConfig | null) => ipcRenderer.invoke('local-workspace:modules:restart', moduleName, workspaceRoot, folderName, rosParams, gpuEnabled, rosDomainId, runtimeImages, rosNetwork),
|
||||||
|
logs: (moduleName: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('local-workspace:modules:logs', moduleName, workspaceRoot, folderName),
|
||||||
|
updateParameters: (moduleName: string, workspaceRoot?: string, folderName?: string, rosParams?: string) => ipcRenderer.invoke('local-workspace:modules:update-parameters', moduleName, workspaceRoot, folderName, rosParams)
|
||||||
|
},
|
||||||
|
rosGraph: {
|
||||||
|
snapshot: () => ipcRenderer.invoke('local-workspace:ros-graph')
|
||||||
|
},
|
||||||
|
nav2: {
|
||||||
|
setSimulatorPose: (pose: { x: number; y: number; yaw: number }) => ipcRenderer.invoke('local-workspace:nav2:set-simulator-pose', pose),
|
||||||
|
addRuntimeObstacle: (obstacle: { id: string; x: number; y: number; yaw: number; length: number; depth: number; height: number }) => ipcRenderer.invoke('local-workspace:nav2:add-runtime-obstacle', obstacle),
|
||||||
|
clearRuntimeObstacles: () => ipcRenderer.invoke('local-workspace:nav2:clear-runtime-obstacles')
|
||||||
|
},
|
||||||
|
worldLayout: {
|
||||||
|
read: (workspaceRoot?: string) => ipcRenderer.invoke('local-workspace:world-layout:read', workspaceRoot),
|
||||||
|
write: (workspaceRoot: string | undefined, layout: unknown) => ipcRenderer.invoke('local-workspace:world-layout:write', workspaceRoot, layout)
|
||||||
|
},
|
||||||
|
maps: {
|
||||||
|
readArtifacts: (workspaceRoot: string | undefined, artifacts: SavedMapArtifactPath[]) => ipcRenderer.invoke('local-workspace:maps:read-artifacts', workspaceRoot, artifacts),
|
||||||
|
sync: (workspaceRoot: string | undefined, artifacts: SavedMapArtifactDownload[]) => ipcRenderer.invoke('local-workspace:maps:sync', workspaceRoot, artifacts),
|
||||||
|
generateCollisionLayout: (workspaceRoot: string | undefined, yamlPath: string, pgmPath: string) => ipcRenderer.invoke('local-workspace:maps:generate-collision-layout', workspaceRoot, yamlPath, pgmPath),
|
||||||
|
generatePointCloudCollisionLayout: (workspaceRoot: string | undefined, databasePath: string) => ipcRenderer.invoke('local-workspace:maps:generate-point-cloud-collision-layout', workspaceRoot, databasePath)
|
||||||
|
},
|
||||||
|
visualization: {
|
||||||
|
status: () => ipcRenderer.invoke('local-workspace:visualization:status'),
|
||||||
|
start: (stack?: 'autoware' | 'nav2' | 'slam' | 'slam3d', workspaceRoot?: string, rosDomainId?: string, environmentProfile?: unknown, rosNetwork?: RosNetworkConfig | null, savedMapDatabasePath?: string | null, savedMapWorldLayoutPath?: string | null, onProgress?: (message: string) => void) => invokeVisualizationStart('local-workspace:visualization:start', [stack, workspaceRoot, rosDomainId, environmentProfile, rosNetwork, savedMapDatabasePath, savedMapWorldLayoutPath], onProgress),
|
||||||
|
stop: (environmentProfile?: unknown) => ipcRenderer.invoke('local-workspace:visualization:stop', environmentProfile)
|
||||||
|
},
|
||||||
|
git: {
|
||||||
|
status: (moduleName: string, workspaceRoot?: string, folderName?: string, selectedFilePath?: string | null) => ipcRenderer.invoke('local-workspace:git:status', moduleName, workspaceRoot, folderName, selectedFilePath),
|
||||||
|
submit: (moduleName: string, workspaceRoot?: string, folderName?: string, message?: string, authorName?: string, authorEmail?: string, submissionId?: string, repositoryUrl?: string) => ipcRenderer.invoke('local-workspace:git:submit', moduleName, workspaceRoot, folderName, message, authorName, authorEmail, submissionId, repositoryUrl),
|
||||||
|
teamPublish: (moduleName: string, workspaceRoot?: string, folderName?: string, message?: string, authorName?: string, authorEmail?: string) => ipcRenderer.invoke('local-workspace:git:team-publish', moduleName, workspaceRoot, folderName, message, authorName, authorEmail),
|
||||||
|
teamPull: (moduleName: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('local-workspace:git:team-pull', moduleName, workspaceRoot, folderName),
|
||||||
|
restoreFile: (moduleName: string, workspaceRoot?: string, folderName?: string, filePath?: string, status?: string) => ipcRenderer.invoke('local-workspace:git:restore-file', moduleName, workspaceRoot, folderName, filePath, status),
|
||||||
|
submissions: (modules: Array<{ moduleName: string; folderName?: string | null }>, workspaceRoot?: string) => ipcRenderer.invoke('local-workspace:git:submissions', modules, workspaceRoot),
|
||||||
|
commitDiff: (moduleName: string, workspaceRoot?: string, folderName?: string, commitHash?: string) => ipcRenderer.invoke('local-workspace:git:commit-diff', moduleName, workspaceRoot, folderName, commitHash)
|
||||||
|
},
|
||||||
|
terminal: {
|
||||||
|
start: (moduleName?: string, cols?: number, rows?: number, sessionId?: string, workspaceRoot?: string, folderName?: string, mode?: 'host' | 'container') => ipcRenderer.invoke('local-workspace:terminal:start', moduleName, cols, rows, sessionId, workspaceRoot, folderName, mode),
|
||||||
|
resize: (cols?: number, rows?: number, sessionId?: string) => ipcRenderer.invoke('local-workspace:terminal:resize', cols, rows, sessionId),
|
||||||
|
changeDirectory: (moduleName: string, sessionId?: string, workspaceRoot?: string, folderName?: string) => ipcRenderer.invoke('local-workspace:terminal:change-directory', moduleName, sessionId, workspaceRoot, folderName),
|
||||||
|
write: (data: string, sessionId?: string) => ipcRenderer.send('local-workspace:terminal:write', data, sessionId),
|
||||||
|
stop: (sessionId?: string) => ipcRenderer.send('local-workspace:terminal:stop', sessionId),
|
||||||
|
onData: (callback: (data: string) => void, sessionId = 'default') => {
|
||||||
|
const listener = (_event: Electron.IpcRendererEvent, dataSessionId: string, data: string): void => {
|
||||||
|
if (dataSessionId === sessionId) callback(data)
|
||||||
|
}
|
||||||
|
ipcRenderer.on('terminal-data', listener)
|
||||||
|
return () => ipcRenderer.removeListener('terminal-data', listener)
|
||||||
|
},
|
||||||
|
onSuppressData: (callback: (data: string) => void, sessionId = 'default') => {
|
||||||
|
const listener = (_event: Electron.IpcRendererEvent, dataSessionId: string, data: string): void => {
|
||||||
|
if (dataSessionId === sessionId) callback(data)
|
||||||
|
}
|
||||||
|
ipcRenderer.on('terminal-suppress-data', listener)
|
||||||
|
return () => ipcRenderer.removeListener('terminal-suppress-data', listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
contextBridge.exposeInMainWorld('remoteWorkspace', {
|
||||||
|
moduleFiles: {
|
||||||
|
read: (target: RemoteWorkspaceTarget, moduleName: string, folderName?: string) => ipcRenderer.invoke('remote-workspace:module-files:read', target, moduleName, folderName),
|
||||||
|
content: (target: RemoteWorkspaceTarget, moduleName: string, path: string, folderName?: string) => ipcRenderer.invoke('remote-workspace:module-files:content', target, moduleName, path, folderName),
|
||||||
|
search: (target: RemoteWorkspaceTarget, moduleName: string, query: string, folderName?: string) => ipcRenderer.invoke('remote-workspace:module-files:search', target, moduleName, query, folderName),
|
||||||
|
image: (target: RemoteWorkspaceTarget, moduleName: string, path: string, folderName?: string) => ipcRenderer.invoke('remote-workspace:module-files:image', target, moduleName, path, folderName),
|
||||||
|
create: (target: RemoteWorkspaceTarget, moduleName: string, path: string, kind: 'file' | 'folder', folderName?: string) => ipcRenderer.invoke('remote-workspace:module-files:create', target, moduleName, path, kind, folderName),
|
||||||
|
write: (target: RemoteWorkspaceTarget, moduleName: string, path: string, content: string, folderName?: string) => ipcRenderer.invoke('remote-workspace:module-files:write', target, moduleName, path, content, folderName),
|
||||||
|
copy: (target: RemoteWorkspaceTarget, moduleName: string, fromPath: string, toPath: string, mode: 'copy' | 'cut', folderName?: string) => ipcRenderer.invoke('remote-workspace:module-files:copy', target, moduleName, fromPath, toPath, mode, folderName),
|
||||||
|
rename: (target: RemoteWorkspaceTarget, moduleName: string, fromPath: string, toPath: string, folderName?: string) => ipcRenderer.invoke('remote-workspace:module-files:rename', target, moduleName, fromPath, toPath, folderName),
|
||||||
|
delete: (target: RemoteWorkspaceTarget, moduleName: string, path: string, folderName?: string) => ipcRenderer.invoke('remote-workspace:module-files:delete', target, moduleName, path, folderName)
|
||||||
|
},
|
||||||
|
agent: {
|
||||||
|
runCommand: (target: RemoteWorkspaceTarget, moduleName: string, command: string, folderName?: string) => ipcRenderer.invoke('remote-workspace:agent-command:run', target, moduleName, command, folderName)
|
||||||
|
},
|
||||||
|
maps: {
|
||||||
|
readArtifacts: (target: RemoteWorkspaceTarget, artifacts: SavedMapArtifactPath[]) => ipcRenderer.invoke('remote-workspace:maps:read-artifacts', target, artifacts),
|
||||||
|
copyActiveRtabmapDatabaseToLocal: (target: RemoteWorkspaceTarget, localWorkspaceRoot: string | undefined, destinationPath: string) => ipcRenderer.invoke('remote-workspace:maps:copy-active-rtabmap-database', target, localWorkspaceRoot, destinationPath),
|
||||||
|
uploadRtabmapDatabase: (target: RemoteWorkspaceTarget, uploadUrl: string, databasePath?: string | null) => ipcRenderer.invoke('remote-workspace:maps:upload-rtabmap-database', target, uploadUrl, databasePath),
|
||||||
|
sync: (target: RemoteWorkspaceTarget, artifacts: SavedMapArtifactDownload[]) => ipcRenderer.invoke('remote-workspace:maps:sync', target, artifacts)
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
list: (target: RemoteWorkspaceTarget, modules?: Array<{ sortOrder?: number | null; category: string; name: string; folderName: string; templateRepositoryUrl?: string | null }>) => ipcRenderer.invoke('remote-workspace:modules:list', target, modules),
|
||||||
|
install: (target: RemoteWorkspaceTarget, moduleName: string, folderName?: string, templateRepositoryUrl?: string | null, studentRepositoryUrl?: string | null, options?: ModuleInstallOptions | null) => ipcRenderer.invoke('remote-workspace:modules:install', target, moduleName, folderName, templateRepositoryUrl, studentRepositoryUrl, options),
|
||||||
|
listDockerImages: (target: RemoteWorkspaceTarget) => ipcRenderer.invoke('remote-workspace:modules:docker-images', target),
|
||||||
|
importGit: (target: RemoteWorkspaceTarget, root: string, input: import('../shared/gitModuleImport').GitModuleImportInput & { folderName: string }) => ipcRenderer.invoke('remote-workspace:modules:import-git', target, root, input),
|
||||||
|
createCustom: (target: RemoteWorkspaceTarget, input: { name: string; category: string; folderName: string; dockerImage?: string | null; packageName: string; launchFile: string }) => ipcRenderer.invoke('remote-workspace:modules:create-custom', target, input),
|
||||||
|
transfer: (target: RemoteWorkspaceTarget, sourceWorkspaceRoot: string | undefined, moduleName: string, folderName?: string) => ipcRenderer.invoke('remote-workspace:modules:transfer', target, sourceWorkspaceRoot, moduleName, folderName),
|
||||||
|
start: (target: RemoteWorkspaceTarget, moduleName: string, folderName?: string, rosParams?: string, gpuEnabled?: boolean, rosDomainId?: string, runtimeImages?: RuntimeImageArtifact[] | null, rosNetwork?: RosNetworkConfig | null) => ipcRenderer.invoke('remote-workspace:modules:start', target, moduleName, folderName, rosParams, gpuEnabled, rosDomainId, runtimeImages, rosNetwork),
|
||||||
|
build: (target: RemoteWorkspaceTarget, moduleName: string, folderName?: string, gpuEnabled?: boolean, rosDomainId?: string, runtimeImages?: RuntimeImageArtifact[] | null) => ipcRenderer.invoke('remote-workspace:modules:build', target, moduleName, folderName, gpuEnabled, rosDomainId, runtimeImages),
|
||||||
|
stop: (target: RemoteWorkspaceTarget, moduleName: string, folderName?: string) => ipcRenderer.invoke('remote-workspace:modules:stop', target, moduleName, folderName),
|
||||||
|
stopMany: (target: RemoteWorkspaceTarget, modules: Array<{ moduleName: string; folderName?: string | null }>) => ipcRenderer.invoke('remote-workspace:modules:stop-many', target, modules),
|
||||||
|
resetSession: (target: RemoteWorkspaceTarget, modules: Array<{ moduleName: string; folderName?: string | null; templateRepositoryUrl?: string | null }>) => ipcRenderer.invoke('remote-workspace:modules:reset-session', target, modules),
|
||||||
|
restart: (target: RemoteWorkspaceTarget, moduleName: string, folderName?: string, rosParams?: string, gpuEnabled?: boolean, rosDomainId?: string, runtimeImages?: RuntimeImageArtifact[] | null, rosNetwork?: RosNetworkConfig | null) => ipcRenderer.invoke('remote-workspace:modules:restart', target, moduleName, folderName, rosParams, gpuEnabled, rosDomainId, runtimeImages, rosNetwork),
|
||||||
|
logs: (target: RemoteWorkspaceTarget, moduleName: string, folderName?: string) => ipcRenderer.invoke('remote-workspace:modules:logs', target, moduleName, folderName)
|
||||||
|
},
|
||||||
|
rosGraph: {
|
||||||
|
snapshot: (target: RemoteWorkspaceTarget) => ipcRenderer.invoke('remote-workspace:ros-graph', target)
|
||||||
|
},
|
||||||
|
ros: {
|
||||||
|
resetSession: (target: RemoteWorkspaceTarget) => ipcRenderer.invoke('remote-workspace:ros:reset-session', target)
|
||||||
|
},
|
||||||
|
visualization: {
|
||||||
|
status: (target: RemoteWorkspaceTarget) => ipcRenderer.invoke('remote-workspace:visualization:status', target),
|
||||||
|
start: (target: RemoteWorkspaceTarget, stack?: 'autoware' | 'nav2' | 'slam' | 'slam3d', rosDomainId?: string, environmentProfile?: unknown, rosNetwork?: RosNetworkConfig | null, onProgress?: (message: string) => void) => invokeVisualizationStart('remote-workspace:visualization:start', [target, stack, rosDomainId, environmentProfile, rosNetwork], onProgress),
|
||||||
|
stop: (target: RemoteWorkspaceTarget) => ipcRenderer.invoke('remote-workspace:visualization:stop', target)
|
||||||
|
},
|
||||||
|
git: {
|
||||||
|
status: (target: RemoteWorkspaceTarget, moduleName: string, folderName?: string, selectedFilePath?: string | null) => ipcRenderer.invoke('remote-workspace:git:status', target, moduleName, folderName, selectedFilePath),
|
||||||
|
submit: (target: RemoteWorkspaceTarget, moduleName: string, folderName?: string, message?: string, authorName?: string, authorEmail?: string, submissionId?: string, repositoryUrl?: string) => ipcRenderer.invoke('remote-workspace:git:submit', target, moduleName, folderName, message, authorName, authorEmail, submissionId, repositoryUrl),
|
||||||
|
teamPublish: (target: RemoteWorkspaceTarget, moduleName: string, folderName?: string, message?: string, authorName?: string, authorEmail?: string) => ipcRenderer.invoke('remote-workspace:git:team-publish', target, moduleName, folderName, message, authorName, authorEmail),
|
||||||
|
teamPull: (target: RemoteWorkspaceTarget, moduleName: string, folderName?: string) => ipcRenderer.invoke('remote-workspace:git:team-pull', target, moduleName, folderName),
|
||||||
|
restoreFile: (target: RemoteWorkspaceTarget, moduleName: string, folderName?: string, filePath?: string, status?: string) => ipcRenderer.invoke('remote-workspace:git:restore-file', target, moduleName, folderName, filePath, status),
|
||||||
|
submissions: (target: RemoteWorkspaceTarget, modules: Array<{ moduleName: string; folderName?: string | null }>) => ipcRenderer.invoke('remote-workspace:git:submissions', target, modules),
|
||||||
|
commitDiff: (target: RemoteWorkspaceTarget, moduleName: string, folderName?: string, commitHash?: string) => ipcRenderer.invoke('remote-workspace:git:commit-diff', target, moduleName, folderName, commitHash)
|
||||||
|
},
|
||||||
|
terminal: {
|
||||||
|
start: (target: RemoteWorkspaceTarget, moduleName?: string, cols?: number, rows?: number, sessionId?: string, folderName?: string, mode?: 'host' | 'container') => ipcRenderer.invoke('remote-workspace:terminal:start', target, moduleName, cols, rows, sessionId, folderName, mode),
|
||||||
|
resize: (target: RemoteWorkspaceTarget, cols?: number, rows?: number, sessionId?: string) => ipcRenderer.invoke('remote-workspace:terminal:resize', target, cols, rows, sessionId),
|
||||||
|
changeDirectory: (target: RemoteWorkspaceTarget, moduleName: string, sessionId?: string, folderName?: string) => ipcRenderer.invoke('remote-workspace:terminal:change-directory', target, moduleName, sessionId, folderName),
|
||||||
|
write: (data: string, sessionId?: string) => ipcRenderer.send('remote-workspace:terminal:write', data, sessionId),
|
||||||
|
stop: (sessionId?: string) => ipcRenderer.send('remote-workspace:terminal:stop', sessionId),
|
||||||
|
onData: (callback: (data: string) => void, sessionId = 'default') => {
|
||||||
|
const listener = (_event: Electron.IpcRendererEvent, incomingSessionId: string, data: string) => {
|
||||||
|
if (incomingSessionId === sessionId) callback(data)
|
||||||
|
}
|
||||||
|
ipcRenderer.on('terminal-data', listener)
|
||||||
|
return () => ipcRenderer.removeListener('terminal-data', listener)
|
||||||
|
},
|
||||||
|
onSuppressData: (callback: (data: string) => void, sessionId = 'default') => {
|
||||||
|
const listener = (_event: Electron.IpcRendererEvent, incomingSessionId: string, data: string) => {
|
||||||
|
if (incomingSessionId === sessionId) callback(data)
|
||||||
|
}
|
||||||
|
ipcRenderer.on('terminal-suppress-data', listener)
|
||||||
|
return () => ipcRenderer.removeListener('terminal-suppress-data', listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Autonomy Studio LMS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 718 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 346 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 961 KiB |
|
After Width: | Height: | Size: 628 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 62 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
projector_type: MGRS
|
||||||
|
vertical_datum: WGS84
|
||||||
|
mgrs_grid: 54SVE
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
projector_type: MGRS
|
||||||
|
vertical_datum: WGS84
|
||||||
|
mgrs_grid: 54SUE
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Draco 3D Data Compression
|
||||||
|
|
||||||
|
Draco is an open-source library for compressing and decompressing 3D geometric meshes and point clouds. It is intended to improve the storage and transmission of 3D graphics.
|
||||||
|
|
||||||
|
[Website](https://google.github.io/draco/) | [GitHub](https://github.com/google/draco)
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
This folder contains three utilities:
|
||||||
|
|
||||||
|
* `draco_decoder.js` — Emscripten-compiled decoder, compatible with any modern browser.
|
||||||
|
* `draco_decoder.wasm` — WebAssembly decoder, compatible with newer browsers and devices.
|
||||||
|
* `draco_wasm_wrapper.js` — JavaScript wrapper for the WASM decoder.
|
||||||
|
|
||||||
|
Each file is provided in two variations:
|
||||||
|
|
||||||
|
* **Default:** Latest stable builds, tracking the project's [master branch](https://github.com/google/draco).
|
||||||
|
* **glTF:** Builds targeted by the [glTF mesh compression extension](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression), tracking the [corresponding Draco branch](https://github.com/google/draco/tree/gltf_2.0_draco_extension).
|
||||||
|
|
||||||
|
Either variation may be used with `DRACOLoader`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var dracoLoader = new DRACOLoader();
|
||||||
|
dracoLoader.setDecoderPath('path/to/decoders/');
|
||||||
|
dracoLoader.setDecoderConfig({type: 'js'}); // (Optional) Override detection of WASM support.
|
||||||
|
```
|
||||||
|
|
||||||
|
Further [documentation on GitHub](https://github.com/google/draco/tree/master/javascript/example#static-loading-javascript-decoder).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[Apache License 2.0](https://github.com/google/draco/blob/master/LICENSE)
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.arrayIteratorImpl=function(k){var n=0;return function(){return n<k.length?{done:!1,value:k[n++]}:{done:!0}}};$jscomp.arrayIterator=function(k){return{next:$jscomp.arrayIteratorImpl(k)}};$jscomp.makeIterator=function(k){var n="undefined"!=typeof Symbol&&Symbol.iterator&&k[Symbol.iterator];return n?n.call(k):$jscomp.arrayIterator(k)};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;
|
||||||
|
$jscomp.ISOLATE_POLYFILLS=!1;$jscomp.FORCE_POLYFILL_PROMISE=!1;$jscomp.FORCE_POLYFILL_PROMISE_WHEN_NO_UNHANDLED_REJECTION=!1;$jscomp.getGlobal=function(k){k=["object"==typeof globalThis&&globalThis,k,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var n=0;n<k.length;++n){var l=k[n];if(l&&l.Math==Math)return l}throw Error("Cannot find global object");};$jscomp.global=$jscomp.getGlobal(this);
|
||||||
|
$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(k,n,l){if(k==Array.prototype||k==Object.prototype)return k;k[n]=l.value;return k};$jscomp.IS_SYMBOL_NATIVE="function"===typeof Symbol&&"symbol"===typeof Symbol("x");$jscomp.TRUST_ES6_POLYFILLS=!$jscomp.ISOLATE_POLYFILLS||$jscomp.IS_SYMBOL_NATIVE;$jscomp.polyfills={};$jscomp.propertyToPolyfillSymbol={};$jscomp.POLYFILL_PREFIX="$jscp$";
|
||||||
|
var $jscomp$lookupPolyfilledValue=function(k,n){var l=$jscomp.propertyToPolyfillSymbol[n];if(null==l)return k[n];l=k[l];return void 0!==l?l:k[n]};$jscomp.polyfill=function(k,n,l,p){n&&($jscomp.ISOLATE_POLYFILLS?$jscomp.polyfillIsolated(k,n,l,p):$jscomp.polyfillUnisolated(k,n,l,p))};
|
||||||
|
$jscomp.polyfillUnisolated=function(k,n,l,p){l=$jscomp.global;k=k.split(".");for(p=0;p<k.length-1;p++){var h=k[p];if(!(h in l))return;l=l[h]}k=k[k.length-1];p=l[k];n=n(p);n!=p&&null!=n&&$jscomp.defineProperty(l,k,{configurable:!0,writable:!0,value:n})};
|
||||||
|
$jscomp.polyfillIsolated=function(k,n,l,p){var h=k.split(".");k=1===h.length;p=h[0];p=!k&&p in $jscomp.polyfills?$jscomp.polyfills:$jscomp.global;for(var A=0;A<h.length-1;A++){var f=h[A];if(!(f in p))return;p=p[f]}h=h[h.length-1];l=$jscomp.IS_SYMBOL_NATIVE&&"es6"===l?p[h]:null;n=n(l);null!=n&&(k?$jscomp.defineProperty($jscomp.polyfills,h,{configurable:!0,writable:!0,value:n}):n!==l&&(void 0===$jscomp.propertyToPolyfillSymbol[h]&&(l=1E9*Math.random()>>>0,$jscomp.propertyToPolyfillSymbol[h]=$jscomp.IS_SYMBOL_NATIVE?
|
||||||
|
$jscomp.global.Symbol(h):$jscomp.POLYFILL_PREFIX+l+"$"+h),$jscomp.defineProperty(p,$jscomp.propertyToPolyfillSymbol[h],{configurable:!0,writable:!0,value:n})))};
|
||||||
|
$jscomp.polyfill("Promise",function(k){function n(){this.batch_=null}function l(f){return f instanceof h?f:new h(function(q,v){q(f)})}if(k&&(!($jscomp.FORCE_POLYFILL_PROMISE||$jscomp.FORCE_POLYFILL_PROMISE_WHEN_NO_UNHANDLED_REJECTION&&"undefined"===typeof $jscomp.global.PromiseRejectionEvent)||!$jscomp.global.Promise||-1===$jscomp.global.Promise.toString().indexOf("[native code]")))return k;n.prototype.asyncExecute=function(f){if(null==this.batch_){this.batch_=[];var q=this;this.asyncExecuteFunction(function(){q.executeBatch_()})}this.batch_.push(f)};
|
||||||
|
var p=$jscomp.global.setTimeout;n.prototype.asyncExecuteFunction=function(f){p(f,0)};n.prototype.executeBatch_=function(){for(;this.batch_&&this.batch_.length;){var f=this.batch_;this.batch_=[];for(var q=0;q<f.length;++q){var v=f[q];f[q]=null;try{v()}catch(z){this.asyncThrow_(z)}}}this.batch_=null};n.prototype.asyncThrow_=function(f){this.asyncExecuteFunction(function(){throw f;})};var h=function(f){this.state_=0;this.result_=void 0;this.onSettledCallbacks_=[];this.isRejectionHandled_=!1;var q=this.createResolveAndReject_();
|
||||||
|
try{f(q.resolve,q.reject)}catch(v){q.reject(v)}};h.prototype.createResolveAndReject_=function(){function f(z){return function(O){v||(v=!0,z.call(q,O))}}var q=this,v=!1;return{resolve:f(this.resolveTo_),reject:f(this.reject_)}};h.prototype.resolveTo_=function(f){if(f===this)this.reject_(new TypeError("A Promise cannot resolve to itself"));else if(f instanceof h)this.settleSameAsPromise_(f);else{a:switch(typeof f){case "object":var q=null!=f;break a;case "function":q=!0;break a;default:q=!1}q?this.resolveToNonPromiseObj_(f):
|
||||||
|
this.fulfill_(f)}};h.prototype.resolveToNonPromiseObj_=function(f){var q=void 0;try{q=f.then}catch(v){this.reject_(v);return}"function"==typeof q?this.settleSameAsThenable_(q,f):this.fulfill_(f)};h.prototype.reject_=function(f){this.settle_(2,f)};h.prototype.fulfill_=function(f){this.settle_(1,f)};h.prototype.settle_=function(f,q){if(0!=this.state_)throw Error("Cannot settle("+f+", "+q+"): Promise already settled in state"+this.state_);this.state_=f;this.result_=q;2===this.state_&&this.scheduleUnhandledRejectionCheck_();
|
||||||
|
this.executeOnSettledCallbacks_()};h.prototype.scheduleUnhandledRejectionCheck_=function(){var f=this;p(function(){if(f.notifyUnhandledRejection_()){var q=$jscomp.global.console;"undefined"!==typeof q&&q.error(f.result_)}},1)};h.prototype.notifyUnhandledRejection_=function(){if(this.isRejectionHandled_)return!1;var f=$jscomp.global.CustomEvent,q=$jscomp.global.Event,v=$jscomp.global.dispatchEvent;if("undefined"===typeof v)return!0;"function"===typeof f?f=new f("unhandledrejection",{cancelable:!0}):
|
||||||
|
"function"===typeof q?f=new q("unhandledrejection",{cancelable:!0}):(f=$jscomp.global.document.createEvent("CustomEvent"),f.initCustomEvent("unhandledrejection",!1,!0,f));f.promise=this;f.reason=this.result_;return v(f)};h.prototype.executeOnSettledCallbacks_=function(){if(null!=this.onSettledCallbacks_){for(var f=0;f<this.onSettledCallbacks_.length;++f)A.asyncExecute(this.onSettledCallbacks_[f]);this.onSettledCallbacks_=null}};var A=new n;h.prototype.settleSameAsPromise_=function(f){var q=this.createResolveAndReject_();
|
||||||
|
f.callWhenSettled_(q.resolve,q.reject)};h.prototype.settleSameAsThenable_=function(f,q){var v=this.createResolveAndReject_();try{f.call(q,v.resolve,v.reject)}catch(z){v.reject(z)}};h.prototype.then=function(f,q){function v(t,x){return"function"==typeof t?function(D){try{z(t(D))}catch(R){O(R)}}:x}var z,O,ba=new h(function(t,x){z=t;O=x});this.callWhenSettled_(v(f,z),v(q,O));return ba};h.prototype.catch=function(f){return this.then(void 0,f)};h.prototype.callWhenSettled_=function(f,q){function v(){switch(z.state_){case 1:f(z.result_);
|
||||||
|
break;case 2:q(z.result_);break;default:throw Error("Unexpected state: "+z.state_);}}var z=this;null==this.onSettledCallbacks_?A.asyncExecute(v):this.onSettledCallbacks_.push(v);this.isRejectionHandled_=!0};h.resolve=l;h.reject=function(f){return new h(function(q,v){v(f)})};h.race=function(f){return new h(function(q,v){for(var z=$jscomp.makeIterator(f),O=z.next();!O.done;O=z.next())l(O.value).callWhenSettled_(q,v)})};h.all=function(f){var q=$jscomp.makeIterator(f),v=q.next();return v.done?l([]):new h(function(z,
|
||||||
|
O){function ba(D){return function(R){t[D]=R;x--;0==x&&z(t)}}var t=[],x=0;do t.push(void 0),x++,l(v.value).callWhenSettled_(ba(t.length-1),O),v=q.next();while(!v.done)})};return h},"es6","es3");$jscomp.owns=function(k,n){return Object.prototype.hasOwnProperty.call(k,n)};$jscomp.assign=$jscomp.TRUST_ES6_POLYFILLS&&"function"==typeof Object.assign?Object.assign:function(k,n){for(var l=1;l<arguments.length;l++){var p=arguments[l];if(p)for(var h in p)$jscomp.owns(p,h)&&(k[h]=p[h])}return k};
|
||||||
|
$jscomp.polyfill("Object.assign",function(k){return k||$jscomp.assign},"es6","es3");$jscomp.checkStringArgs=function(k,n,l){if(null==k)throw new TypeError("The 'this' value for String.prototype."+l+" must not be null or undefined");if(n instanceof RegExp)throw new TypeError("First argument to String.prototype."+l+" must not be a regular expression");return k+""};
|
||||||
|
$jscomp.polyfill("String.prototype.startsWith",function(k){return k?k:function(n,l){var p=$jscomp.checkStringArgs(this,n,"startsWith");n+="";var h=p.length,A=n.length;l=Math.max(0,Math.min(l|0,p.length));for(var f=0;f<A&&l<h;)if(p[l++]!=n[f++])return!1;return f>=A}},"es6","es3");
|
||||||
|
$jscomp.polyfill("Array.prototype.copyWithin",function(k){function n(l){l=Number(l);return Infinity===l||-Infinity===l?l:l|0}return k?k:function(l,p,h){var A=this.length;l=n(l);p=n(p);h=void 0===h?A:n(h);l=0>l?Math.max(A+l,0):Math.min(l,A);p=0>p?Math.max(A+p,0):Math.min(p,A);h=0>h?Math.max(A+h,0):Math.min(h,A);if(l<p)for(;p<h;)p in this?this[l++]=this[p++]:(delete this[l++],p++);else for(h=Math.min(h,A+p-l),l+=h-p;h>p;)--h in this?this[--l]=this[h]:delete this[--l];return this}},"es6","es3");
|
||||||
|
$jscomp.typedArrayCopyWithin=function(k){return k?k:Array.prototype.copyWithin};$jscomp.polyfill("Int8Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint8Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint8ClampedArray.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Int16Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");
|
||||||
|
$jscomp.polyfill("Uint16Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Int32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Float32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Float64Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");
|
||||||
|
var DracoDecoderModule=function(){var k="undefined"!==typeof document&&document.currentScript?document.currentScript.src:void 0;"undefined"!==typeof __filename&&(k=k||__filename);return function(n){function l(e){return a.locateFile?a.locateFile(e,U):U+e}function p(e,b,c){var d=b+c;for(c=b;e[c]&&!(c>=d);)++c;if(16<c-b&&e.buffer&&va)return va.decode(e.subarray(b,c));for(d="";b<c;){var g=e[b++];if(g&128){var u=e[b++]&63;if(192==(g&224))d+=String.fromCharCode((g&31)<<6|u);else{var X=e[b++]&63;g=224==
|
||||||
|
(g&240)?(g&15)<<12|u<<6|X:(g&7)<<18|u<<12|X<<6|e[b++]&63;65536>g?d+=String.fromCharCode(g):(g-=65536,d+=String.fromCharCode(55296|g>>10,56320|g&1023))}}else d+=String.fromCharCode(g)}return d}function h(e,b){return e?p(ea,e,b):""}function A(){var e=ja.buffer;a.HEAP8=Y=new Int8Array(e);a.HEAP16=new Int16Array(e);a.HEAP32=ca=new Int32Array(e);a.HEAPU8=ea=new Uint8Array(e);a.HEAPU16=new Uint16Array(e);a.HEAPU32=V=new Uint32Array(e);a.HEAPF32=new Float32Array(e);a.HEAPF64=new Float64Array(e)}function f(e){if(a.onAbort)a.onAbort(e);
|
||||||
|
e="Aborted("+e+")";da(e);wa=!0;e=new WebAssembly.RuntimeError(e+". Build with -sASSERTIONS for more info.");ka(e);throw e;}function q(e){try{if(e==P&&fa)return new Uint8Array(fa);if(ma)return ma(e);throw"both async and sync fetching of the wasm failed";}catch(b){f(b)}}function v(){if(!fa&&(xa||ha)){if("function"==typeof fetch&&!P.startsWith("file://"))return fetch(P,{credentials:"same-origin"}).then(function(e){if(!e.ok)throw"failed to load wasm binary file at '"+P+"'";return e.arrayBuffer()}).catch(function(){return q(P)});
|
||||||
|
if(na)return new Promise(function(e,b){na(P,function(c){e(new Uint8Array(c))},b)})}return Promise.resolve().then(function(){return q(P)})}function z(e){for(;0<e.length;)e.shift()(a)}function O(e){this.excPtr=e;this.ptr=e-24;this.set_type=function(b){V[this.ptr+4>>2]=b};this.get_type=function(){return V[this.ptr+4>>2]};this.set_destructor=function(b){V[this.ptr+8>>2]=b};this.get_destructor=function(){return V[this.ptr+8>>2]};this.set_refcount=function(b){ca[this.ptr>>2]=b};this.set_caught=function(b){Y[this.ptr+
|
||||||
|
12>>0]=b?1:0};this.get_caught=function(){return 0!=Y[this.ptr+12>>0]};this.set_rethrown=function(b){Y[this.ptr+13>>0]=b?1:0};this.get_rethrown=function(){return 0!=Y[this.ptr+13>>0]};this.init=function(b,c){this.set_adjusted_ptr(0);this.set_type(b);this.set_destructor(c);this.set_refcount(0);this.set_caught(!1);this.set_rethrown(!1)};this.add_ref=function(){ca[this.ptr>>2]+=1};this.release_ref=function(){var b=ca[this.ptr>>2];ca[this.ptr>>2]=b-1;return 1===b};this.set_adjusted_ptr=function(b){V[this.ptr+
|
||||||
|
16>>2]=b};this.get_adjusted_ptr=function(){return V[this.ptr+16>>2]};this.get_exception_ptr=function(){if(ya(this.get_type()))return V[this.excPtr>>2];var b=this.get_adjusted_ptr();return 0!==b?b:this.excPtr}}function ba(){function e(){if(!la&&(la=!0,a.calledRun=!0,!wa)){za=!0;z(oa);Aa(a);if(a.onRuntimeInitialized)a.onRuntimeInitialized();if(a.postRun)for("function"==typeof a.postRun&&(a.postRun=[a.postRun]);a.postRun.length;)Ba.unshift(a.postRun.shift());z(Ba)}}if(!(0<aa)){if(a.preRun)for("function"==
|
||||||
|
typeof a.preRun&&(a.preRun=[a.preRun]);a.preRun.length;)Ca.unshift(a.preRun.shift());z(Ca);0<aa||(a.setStatus?(a.setStatus("Running..."),setTimeout(function(){setTimeout(function(){a.setStatus("")},1);e()},1)):e())}}function t(){}function x(e){return(e||t).__cache__}function D(e,b){var c=x(b),d=c[e];if(d)return d;d=Object.create((b||t).prototype);d.ptr=e;return c[e]=d}function R(e){if("string"===typeof e){for(var b=0,c=0;c<e.length;++c){var d=e.charCodeAt(c);127>=d?b++:2047>=d?b+=2:55296<=d&&57343>=
|
||||||
|
d?(b+=4,++c):b+=3}b=Array(b+1);c=0;d=b.length;if(0<d){d=c+d-1;for(var g=0;g<e.length;++g){var u=e.charCodeAt(g);if(55296<=u&&57343>=u){var X=e.charCodeAt(++g);u=65536+((u&1023)<<10)|X&1023}if(127>=u){if(c>=d)break;b[c++]=u}else{if(2047>=u){if(c+1>=d)break;b[c++]=192|u>>6}else{if(65535>=u){if(c+2>=d)break;b[c++]=224|u>>12}else{if(c+3>=d)break;b[c++]=240|u>>18;b[c++]=128|u>>12&63}b[c++]=128|u>>6&63}b[c++]=128|u&63}}b[c]=0}e=r.alloc(b,Y);r.copy(b,Y,e);return e}return e}function pa(e){if("object"===typeof e){var b=
|
||||||
|
r.alloc(e,Y);r.copy(e,Y,b);return b}return e}function Z(){throw"cannot construct a VoidPtr, no constructor in IDL";}function S(){this.ptr=Da();x(S)[this.ptr]=this}function Q(){this.ptr=Ea();x(Q)[this.ptr]=this}function W(){this.ptr=Fa();x(W)[this.ptr]=this}function w(){this.ptr=Ga();x(w)[this.ptr]=this}function C(){this.ptr=Ha();x(C)[this.ptr]=this}function F(){this.ptr=Ia();x(F)[this.ptr]=this}function G(){this.ptr=Ja();x(G)[this.ptr]=this}function E(){this.ptr=Ka();x(E)[this.ptr]=this}function T(){this.ptr=
|
||||||
|
La();x(T)[this.ptr]=this}function B(){throw"cannot construct a Status, no constructor in IDL";}function H(){this.ptr=Ma();x(H)[this.ptr]=this}function I(){this.ptr=Na();x(I)[this.ptr]=this}function J(){this.ptr=Oa();x(J)[this.ptr]=this}function K(){this.ptr=Pa();x(K)[this.ptr]=this}function L(){this.ptr=Qa();x(L)[this.ptr]=this}function M(){this.ptr=Ra();x(M)[this.ptr]=this}function N(){this.ptr=Sa();x(N)[this.ptr]=this}function y(){this.ptr=Ta();x(y)[this.ptr]=this}function m(){this.ptr=Ua();x(m)[this.ptr]=
|
||||||
|
this}n=void 0===n?{}:n;var a="undefined"!=typeof n?n:{},Aa,ka;a.ready=new Promise(function(e,b){Aa=e;ka=b});var Va=!1,Wa=!1;a.onRuntimeInitialized=function(){Va=!0;if(Wa&&"function"===typeof a.onModuleLoaded)a.onModuleLoaded(a)};a.onModuleParsed=function(){Wa=!0;if(Va&&"function"===typeof a.onModuleLoaded)a.onModuleLoaded(a)};a.isVersionSupported=function(e){if("string"!==typeof e)return!1;e=e.split(".");return 2>e.length||3<e.length?!1:1==e[0]&&0<=e[1]&&5>=e[1]?!0:0!=e[0]||10<e[1]?!1:!0};var Xa=
|
||||||
|
Object.assign({},a),xa="object"==typeof window,ha="function"==typeof importScripts,Ya="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,U="";if(Ya){var Za=require("fs"),qa=require("path");U=ha?qa.dirname(U)+"/":__dirname+"/";var $a=function(e,b){e=e.startsWith("file://")?new URL(e):qa.normalize(e);return Za.readFileSync(e,b?void 0:"utf8")};var ma=function(e){e=$a(e,!0);e.buffer||(e=new Uint8Array(e));return e};var na=function(e,b,c){e=e.startsWith("file://")?
|
||||||
|
new URL(e):qa.normalize(e);Za.readFile(e,function(d,g){d?c(d):b(g.buffer)})};1<process.argv.length&&process.argv[1].replace(/\\/g,"/");process.argv.slice(2);a.inspect=function(){return"[Emscripten Module object]"}}else if(xa||ha)ha?U=self.location.href:"undefined"!=typeof document&&document.currentScript&&(U=document.currentScript.src),k&&(U=k),U=0!==U.indexOf("blob:")?U.substr(0,U.replace(/[?#].*/,"").lastIndexOf("/")+1):"",$a=function(e){var b=new XMLHttpRequest;b.open("GET",e,!1);b.send(null);
|
||||||
|
return b.responseText},ha&&(ma=function(e){var b=new XMLHttpRequest;b.open("GET",e,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),na=function(e,b,c){var d=new XMLHttpRequest;d.open("GET",e,!0);d.responseType="arraybuffer";d.onload=function(){200==d.status||0==d.status&&d.response?b(d.response):c()};d.onerror=c;d.send(null)};var ud=a.print||console.log.bind(console),da=a.printErr||console.warn.bind(console);Object.assign(a,Xa);Xa=null;var fa;a.wasmBinary&&(fa=a.wasmBinary);
|
||||||
|
"object"!=typeof WebAssembly&&f("no native wasm support detected");var ja,wa=!1,va="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0,Y,ea,ca,V,Ca=[],oa=[],Ba=[],za=!1,aa=0,ra=null,ia=null;var P="draco_decoder.wasm";P.startsWith("data:application/octet-stream;base64,")||(P=l(P));var vd=0,wd=[null,[],[]],xd={b:function(e,b,c){(new O(e)).init(b,c);vd++;throw e;},a:function(){f("")},g:function(e,b,c){ea.copyWithin(e,b,b+c)},e:function(e){var b=ea.length;e>>>=0;if(2147483648<e)return!1;for(var c=
|
||||||
|
1;4>=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,e+100663296);var g=Math;d=Math.max(e,d);g=g.min.call(g,2147483648,d+(65536-d%65536)%65536);a:{d=ja.buffer;try{ja.grow(g-d.byteLength+65535>>>16);A();var u=1;break a}catch(X){}u=void 0}if(u)return!0}return!1},f:function(e){return 52},d:function(e,b,c,d,g){return 70},c:function(e,b,c,d){for(var g=0,u=0;u<c;u++){var X=V[b>>2],ab=V[b+4>>2];b+=8;for(var sa=0;sa<ab;sa++){var ta=ea[X+sa],ua=wd[e];0===ta||10===ta?((1===e?ud:da)(p(ua,0)),ua.length=0):ua.push(ta)}g+=
|
||||||
|
ab}V[d>>2]=g;return 0}};(function(){function e(g,u){a.asm=g.exports;ja=a.asm.h;A();oa.unshift(a.asm.i);aa--;a.monitorRunDependencies&&a.monitorRunDependencies(aa);0==aa&&(null!==ra&&(clearInterval(ra),ra=null),ia&&(g=ia,ia=null,g()))}function b(g){e(g.instance)}function c(g){return v().then(function(u){return WebAssembly.instantiate(u,d)}).then(function(u){return u}).then(g,function(u){da("failed to asynchronously prepare wasm: "+u);f(u)})}var d={a:xd};aa++;a.monitorRunDependencies&&a.monitorRunDependencies(aa);
|
||||||
|
if(a.instantiateWasm)try{return a.instantiateWasm(d,e)}catch(g){da("Module.instantiateWasm callback failed with error: "+g),ka(g)}(function(){return fa||"function"!=typeof WebAssembly.instantiateStreaming||P.startsWith("data:application/octet-stream;base64,")||P.startsWith("file://")||Ya||"function"!=typeof fetch?c(b):fetch(P,{credentials:"same-origin"}).then(function(g){return WebAssembly.instantiateStreaming(g,d).then(b,function(u){da("wasm streaming compile failed: "+u);da("falling back to ArrayBuffer instantiation");
|
||||||
|
return c(b)})})})().catch(ka);return{}})();var bb=a._emscripten_bind_VoidPtr___destroy___0=function(){return(bb=a._emscripten_bind_VoidPtr___destroy___0=a.asm.k).apply(null,arguments)},Da=a._emscripten_bind_DecoderBuffer_DecoderBuffer_0=function(){return(Da=a._emscripten_bind_DecoderBuffer_DecoderBuffer_0=a.asm.l).apply(null,arguments)},cb=a._emscripten_bind_DecoderBuffer_Init_2=function(){return(cb=a._emscripten_bind_DecoderBuffer_Init_2=a.asm.m).apply(null,arguments)},db=a._emscripten_bind_DecoderBuffer___destroy___0=
|
||||||
|
function(){return(db=a._emscripten_bind_DecoderBuffer___destroy___0=a.asm.n).apply(null,arguments)},Ea=a._emscripten_bind_AttributeTransformData_AttributeTransformData_0=function(){return(Ea=a._emscripten_bind_AttributeTransformData_AttributeTransformData_0=a.asm.o).apply(null,arguments)},eb=a._emscripten_bind_AttributeTransformData_transform_type_0=function(){return(eb=a._emscripten_bind_AttributeTransformData_transform_type_0=a.asm.p).apply(null,arguments)},fb=a._emscripten_bind_AttributeTransformData___destroy___0=
|
||||||
|
function(){return(fb=a._emscripten_bind_AttributeTransformData___destroy___0=a.asm.q).apply(null,arguments)},Fa=a._emscripten_bind_GeometryAttribute_GeometryAttribute_0=function(){return(Fa=a._emscripten_bind_GeometryAttribute_GeometryAttribute_0=a.asm.r).apply(null,arguments)},gb=a._emscripten_bind_GeometryAttribute___destroy___0=function(){return(gb=a._emscripten_bind_GeometryAttribute___destroy___0=a.asm.s).apply(null,arguments)},Ga=a._emscripten_bind_PointAttribute_PointAttribute_0=function(){return(Ga=
|
||||||
|
a._emscripten_bind_PointAttribute_PointAttribute_0=a.asm.t).apply(null,arguments)},hb=a._emscripten_bind_PointAttribute_size_0=function(){return(hb=a._emscripten_bind_PointAttribute_size_0=a.asm.u).apply(null,arguments)},ib=a._emscripten_bind_PointAttribute_GetAttributeTransformData_0=function(){return(ib=a._emscripten_bind_PointAttribute_GetAttributeTransformData_0=a.asm.v).apply(null,arguments)},jb=a._emscripten_bind_PointAttribute_attribute_type_0=function(){return(jb=a._emscripten_bind_PointAttribute_attribute_type_0=
|
||||||
|
a.asm.w).apply(null,arguments)},kb=a._emscripten_bind_PointAttribute_data_type_0=function(){return(kb=a._emscripten_bind_PointAttribute_data_type_0=a.asm.x).apply(null,arguments)},lb=a._emscripten_bind_PointAttribute_num_components_0=function(){return(lb=a._emscripten_bind_PointAttribute_num_components_0=a.asm.y).apply(null,arguments)},mb=a._emscripten_bind_PointAttribute_normalized_0=function(){return(mb=a._emscripten_bind_PointAttribute_normalized_0=a.asm.z).apply(null,arguments)},nb=a._emscripten_bind_PointAttribute_byte_stride_0=
|
||||||
|
function(){return(nb=a._emscripten_bind_PointAttribute_byte_stride_0=a.asm.A).apply(null,arguments)},ob=a._emscripten_bind_PointAttribute_byte_offset_0=function(){return(ob=a._emscripten_bind_PointAttribute_byte_offset_0=a.asm.B).apply(null,arguments)},pb=a._emscripten_bind_PointAttribute_unique_id_0=function(){return(pb=a._emscripten_bind_PointAttribute_unique_id_0=a.asm.C).apply(null,arguments)},qb=a._emscripten_bind_PointAttribute___destroy___0=function(){return(qb=a._emscripten_bind_PointAttribute___destroy___0=
|
||||||
|
a.asm.D).apply(null,arguments)},Ha=a._emscripten_bind_AttributeQuantizationTransform_AttributeQuantizationTransform_0=function(){return(Ha=a._emscripten_bind_AttributeQuantizationTransform_AttributeQuantizationTransform_0=a.asm.E).apply(null,arguments)},rb=a._emscripten_bind_AttributeQuantizationTransform_InitFromAttribute_1=function(){return(rb=a._emscripten_bind_AttributeQuantizationTransform_InitFromAttribute_1=a.asm.F).apply(null,arguments)},sb=a._emscripten_bind_AttributeQuantizationTransform_quantization_bits_0=
|
||||||
|
function(){return(sb=a._emscripten_bind_AttributeQuantizationTransform_quantization_bits_0=a.asm.G).apply(null,arguments)},tb=a._emscripten_bind_AttributeQuantizationTransform_min_value_1=function(){return(tb=a._emscripten_bind_AttributeQuantizationTransform_min_value_1=a.asm.H).apply(null,arguments)},ub=a._emscripten_bind_AttributeQuantizationTransform_range_0=function(){return(ub=a._emscripten_bind_AttributeQuantizationTransform_range_0=a.asm.I).apply(null,arguments)},vb=a._emscripten_bind_AttributeQuantizationTransform___destroy___0=
|
||||||
|
function(){return(vb=a._emscripten_bind_AttributeQuantizationTransform___destroy___0=a.asm.J).apply(null,arguments)},Ia=a._emscripten_bind_AttributeOctahedronTransform_AttributeOctahedronTransform_0=function(){return(Ia=a._emscripten_bind_AttributeOctahedronTransform_AttributeOctahedronTransform_0=a.asm.K).apply(null,arguments)},wb=a._emscripten_bind_AttributeOctahedronTransform_InitFromAttribute_1=function(){return(wb=a._emscripten_bind_AttributeOctahedronTransform_InitFromAttribute_1=a.asm.L).apply(null,
|
||||||
|
arguments)},xb=a._emscripten_bind_AttributeOctahedronTransform_quantization_bits_0=function(){return(xb=a._emscripten_bind_AttributeOctahedronTransform_quantization_bits_0=a.asm.M).apply(null,arguments)},yb=a._emscripten_bind_AttributeOctahedronTransform___destroy___0=function(){return(yb=a._emscripten_bind_AttributeOctahedronTransform___destroy___0=a.asm.N).apply(null,arguments)},Ja=a._emscripten_bind_PointCloud_PointCloud_0=function(){return(Ja=a._emscripten_bind_PointCloud_PointCloud_0=a.asm.O).apply(null,
|
||||||
|
arguments)},zb=a._emscripten_bind_PointCloud_num_attributes_0=function(){return(zb=a._emscripten_bind_PointCloud_num_attributes_0=a.asm.P).apply(null,arguments)},Ab=a._emscripten_bind_PointCloud_num_points_0=function(){return(Ab=a._emscripten_bind_PointCloud_num_points_0=a.asm.Q).apply(null,arguments)},Bb=a._emscripten_bind_PointCloud___destroy___0=function(){return(Bb=a._emscripten_bind_PointCloud___destroy___0=a.asm.R).apply(null,arguments)},Ka=a._emscripten_bind_Mesh_Mesh_0=function(){return(Ka=
|
||||||
|
a._emscripten_bind_Mesh_Mesh_0=a.asm.S).apply(null,arguments)},Cb=a._emscripten_bind_Mesh_num_faces_0=function(){return(Cb=a._emscripten_bind_Mesh_num_faces_0=a.asm.T).apply(null,arguments)},Db=a._emscripten_bind_Mesh_num_attributes_0=function(){return(Db=a._emscripten_bind_Mesh_num_attributes_0=a.asm.U).apply(null,arguments)},Eb=a._emscripten_bind_Mesh_num_points_0=function(){return(Eb=a._emscripten_bind_Mesh_num_points_0=a.asm.V).apply(null,arguments)},Fb=a._emscripten_bind_Mesh___destroy___0=function(){return(Fb=
|
||||||
|
a._emscripten_bind_Mesh___destroy___0=a.asm.W).apply(null,arguments)},La=a._emscripten_bind_Metadata_Metadata_0=function(){return(La=a._emscripten_bind_Metadata_Metadata_0=a.asm.X).apply(null,arguments)},Gb=a._emscripten_bind_Metadata___destroy___0=function(){return(Gb=a._emscripten_bind_Metadata___destroy___0=a.asm.Y).apply(null,arguments)},Hb=a._emscripten_bind_Status_code_0=function(){return(Hb=a._emscripten_bind_Status_code_0=a.asm.Z).apply(null,arguments)},Ib=a._emscripten_bind_Status_ok_0=function(){return(Ib=
|
||||||
|
a._emscripten_bind_Status_ok_0=a.asm._).apply(null,arguments)},Jb=a._emscripten_bind_Status_error_msg_0=function(){return(Jb=a._emscripten_bind_Status_error_msg_0=a.asm.$).apply(null,arguments)},Kb=a._emscripten_bind_Status___destroy___0=function(){return(Kb=a._emscripten_bind_Status___destroy___0=a.asm.aa).apply(null,arguments)},Ma=a._emscripten_bind_DracoFloat32Array_DracoFloat32Array_0=function(){return(Ma=a._emscripten_bind_DracoFloat32Array_DracoFloat32Array_0=a.asm.ba).apply(null,arguments)},
|
||||||
|
Lb=a._emscripten_bind_DracoFloat32Array_GetValue_1=function(){return(Lb=a._emscripten_bind_DracoFloat32Array_GetValue_1=a.asm.ca).apply(null,arguments)},Mb=a._emscripten_bind_DracoFloat32Array_size_0=function(){return(Mb=a._emscripten_bind_DracoFloat32Array_size_0=a.asm.da).apply(null,arguments)},Nb=a._emscripten_bind_DracoFloat32Array___destroy___0=function(){return(Nb=a._emscripten_bind_DracoFloat32Array___destroy___0=a.asm.ea).apply(null,arguments)},Na=a._emscripten_bind_DracoInt8Array_DracoInt8Array_0=
|
||||||
|
function(){return(Na=a._emscripten_bind_DracoInt8Array_DracoInt8Array_0=a.asm.fa).apply(null,arguments)},Ob=a._emscripten_bind_DracoInt8Array_GetValue_1=function(){return(Ob=a._emscripten_bind_DracoInt8Array_GetValue_1=a.asm.ga).apply(null,arguments)},Pb=a._emscripten_bind_DracoInt8Array_size_0=function(){return(Pb=a._emscripten_bind_DracoInt8Array_size_0=a.asm.ha).apply(null,arguments)},Qb=a._emscripten_bind_DracoInt8Array___destroy___0=function(){return(Qb=a._emscripten_bind_DracoInt8Array___destroy___0=
|
||||||
|
a.asm.ia).apply(null,arguments)},Oa=a._emscripten_bind_DracoUInt8Array_DracoUInt8Array_0=function(){return(Oa=a._emscripten_bind_DracoUInt8Array_DracoUInt8Array_0=a.asm.ja).apply(null,arguments)},Rb=a._emscripten_bind_DracoUInt8Array_GetValue_1=function(){return(Rb=a._emscripten_bind_DracoUInt8Array_GetValue_1=a.asm.ka).apply(null,arguments)},Sb=a._emscripten_bind_DracoUInt8Array_size_0=function(){return(Sb=a._emscripten_bind_DracoUInt8Array_size_0=a.asm.la).apply(null,arguments)},Tb=a._emscripten_bind_DracoUInt8Array___destroy___0=
|
||||||
|
function(){return(Tb=a._emscripten_bind_DracoUInt8Array___destroy___0=a.asm.ma).apply(null,arguments)},Pa=a._emscripten_bind_DracoInt16Array_DracoInt16Array_0=function(){return(Pa=a._emscripten_bind_DracoInt16Array_DracoInt16Array_0=a.asm.na).apply(null,arguments)},Ub=a._emscripten_bind_DracoInt16Array_GetValue_1=function(){return(Ub=a._emscripten_bind_DracoInt16Array_GetValue_1=a.asm.oa).apply(null,arguments)},Vb=a._emscripten_bind_DracoInt16Array_size_0=function(){return(Vb=a._emscripten_bind_DracoInt16Array_size_0=
|
||||||
|
a.asm.pa).apply(null,arguments)},Wb=a._emscripten_bind_DracoInt16Array___destroy___0=function(){return(Wb=a._emscripten_bind_DracoInt16Array___destroy___0=a.asm.qa).apply(null,arguments)},Qa=a._emscripten_bind_DracoUInt16Array_DracoUInt16Array_0=function(){return(Qa=a._emscripten_bind_DracoUInt16Array_DracoUInt16Array_0=a.asm.ra).apply(null,arguments)},Xb=a._emscripten_bind_DracoUInt16Array_GetValue_1=function(){return(Xb=a._emscripten_bind_DracoUInt16Array_GetValue_1=a.asm.sa).apply(null,arguments)},
|
||||||
|
Yb=a._emscripten_bind_DracoUInt16Array_size_0=function(){return(Yb=a._emscripten_bind_DracoUInt16Array_size_0=a.asm.ta).apply(null,arguments)},Zb=a._emscripten_bind_DracoUInt16Array___destroy___0=function(){return(Zb=a._emscripten_bind_DracoUInt16Array___destroy___0=a.asm.ua).apply(null,arguments)},Ra=a._emscripten_bind_DracoInt32Array_DracoInt32Array_0=function(){return(Ra=a._emscripten_bind_DracoInt32Array_DracoInt32Array_0=a.asm.va).apply(null,arguments)},$b=a._emscripten_bind_DracoInt32Array_GetValue_1=
|
||||||
|
function(){return($b=a._emscripten_bind_DracoInt32Array_GetValue_1=a.asm.wa).apply(null,arguments)},ac=a._emscripten_bind_DracoInt32Array_size_0=function(){return(ac=a._emscripten_bind_DracoInt32Array_size_0=a.asm.xa).apply(null,arguments)},bc=a._emscripten_bind_DracoInt32Array___destroy___0=function(){return(bc=a._emscripten_bind_DracoInt32Array___destroy___0=a.asm.ya).apply(null,arguments)},Sa=a._emscripten_bind_DracoUInt32Array_DracoUInt32Array_0=function(){return(Sa=a._emscripten_bind_DracoUInt32Array_DracoUInt32Array_0=
|
||||||
|
a.asm.za).apply(null,arguments)},cc=a._emscripten_bind_DracoUInt32Array_GetValue_1=function(){return(cc=a._emscripten_bind_DracoUInt32Array_GetValue_1=a.asm.Aa).apply(null,arguments)},dc=a._emscripten_bind_DracoUInt32Array_size_0=function(){return(dc=a._emscripten_bind_DracoUInt32Array_size_0=a.asm.Ba).apply(null,arguments)},ec=a._emscripten_bind_DracoUInt32Array___destroy___0=function(){return(ec=a._emscripten_bind_DracoUInt32Array___destroy___0=a.asm.Ca).apply(null,arguments)},Ta=a._emscripten_bind_MetadataQuerier_MetadataQuerier_0=
|
||||||
|
function(){return(Ta=a._emscripten_bind_MetadataQuerier_MetadataQuerier_0=a.asm.Da).apply(null,arguments)},fc=a._emscripten_bind_MetadataQuerier_HasEntry_2=function(){return(fc=a._emscripten_bind_MetadataQuerier_HasEntry_2=a.asm.Ea).apply(null,arguments)},gc=a._emscripten_bind_MetadataQuerier_GetIntEntry_2=function(){return(gc=a._emscripten_bind_MetadataQuerier_GetIntEntry_2=a.asm.Fa).apply(null,arguments)},hc=a._emscripten_bind_MetadataQuerier_GetIntEntryArray_3=function(){return(hc=a._emscripten_bind_MetadataQuerier_GetIntEntryArray_3=
|
||||||
|
a.asm.Ga).apply(null,arguments)},ic=a._emscripten_bind_MetadataQuerier_GetDoubleEntry_2=function(){return(ic=a._emscripten_bind_MetadataQuerier_GetDoubleEntry_2=a.asm.Ha).apply(null,arguments)},jc=a._emscripten_bind_MetadataQuerier_GetStringEntry_2=function(){return(jc=a._emscripten_bind_MetadataQuerier_GetStringEntry_2=a.asm.Ia).apply(null,arguments)},kc=a._emscripten_bind_MetadataQuerier_NumEntries_1=function(){return(kc=a._emscripten_bind_MetadataQuerier_NumEntries_1=a.asm.Ja).apply(null,arguments)},
|
||||||
|
lc=a._emscripten_bind_MetadataQuerier_GetEntryName_2=function(){return(lc=a._emscripten_bind_MetadataQuerier_GetEntryName_2=a.asm.Ka).apply(null,arguments)},mc=a._emscripten_bind_MetadataQuerier___destroy___0=function(){return(mc=a._emscripten_bind_MetadataQuerier___destroy___0=a.asm.La).apply(null,arguments)},Ua=a._emscripten_bind_Decoder_Decoder_0=function(){return(Ua=a._emscripten_bind_Decoder_Decoder_0=a.asm.Ma).apply(null,arguments)},nc=a._emscripten_bind_Decoder_DecodeArrayToPointCloud_3=function(){return(nc=
|
||||||
|
a._emscripten_bind_Decoder_DecodeArrayToPointCloud_3=a.asm.Na).apply(null,arguments)},oc=a._emscripten_bind_Decoder_DecodeArrayToMesh_3=function(){return(oc=a._emscripten_bind_Decoder_DecodeArrayToMesh_3=a.asm.Oa).apply(null,arguments)},pc=a._emscripten_bind_Decoder_GetAttributeId_2=function(){return(pc=a._emscripten_bind_Decoder_GetAttributeId_2=a.asm.Pa).apply(null,arguments)},qc=a._emscripten_bind_Decoder_GetAttributeIdByName_2=function(){return(qc=a._emscripten_bind_Decoder_GetAttributeIdByName_2=
|
||||||
|
a.asm.Qa).apply(null,arguments)},rc=a._emscripten_bind_Decoder_GetAttributeIdByMetadataEntry_3=function(){return(rc=a._emscripten_bind_Decoder_GetAttributeIdByMetadataEntry_3=a.asm.Ra).apply(null,arguments)},sc=a._emscripten_bind_Decoder_GetAttribute_2=function(){return(sc=a._emscripten_bind_Decoder_GetAttribute_2=a.asm.Sa).apply(null,arguments)},tc=a._emscripten_bind_Decoder_GetAttributeByUniqueId_2=function(){return(tc=a._emscripten_bind_Decoder_GetAttributeByUniqueId_2=a.asm.Ta).apply(null,arguments)},
|
||||||
|
uc=a._emscripten_bind_Decoder_GetMetadata_1=function(){return(uc=a._emscripten_bind_Decoder_GetMetadata_1=a.asm.Ua).apply(null,arguments)},vc=a._emscripten_bind_Decoder_GetAttributeMetadata_2=function(){return(vc=a._emscripten_bind_Decoder_GetAttributeMetadata_2=a.asm.Va).apply(null,arguments)},wc=a._emscripten_bind_Decoder_GetFaceFromMesh_3=function(){return(wc=a._emscripten_bind_Decoder_GetFaceFromMesh_3=a.asm.Wa).apply(null,arguments)},xc=a._emscripten_bind_Decoder_GetTriangleStripsFromMesh_2=
|
||||||
|
function(){return(xc=a._emscripten_bind_Decoder_GetTriangleStripsFromMesh_2=a.asm.Xa).apply(null,arguments)},yc=a._emscripten_bind_Decoder_GetTrianglesUInt16Array_3=function(){return(yc=a._emscripten_bind_Decoder_GetTrianglesUInt16Array_3=a.asm.Ya).apply(null,arguments)},zc=a._emscripten_bind_Decoder_GetTrianglesUInt32Array_3=function(){return(zc=a._emscripten_bind_Decoder_GetTrianglesUInt32Array_3=a.asm.Za).apply(null,arguments)},Ac=a._emscripten_bind_Decoder_GetAttributeFloat_3=function(){return(Ac=
|
||||||
|
a._emscripten_bind_Decoder_GetAttributeFloat_3=a.asm._a).apply(null,arguments)},Bc=a._emscripten_bind_Decoder_GetAttributeFloatForAllPoints_3=function(){return(Bc=a._emscripten_bind_Decoder_GetAttributeFloatForAllPoints_3=a.asm.$a).apply(null,arguments)},Cc=a._emscripten_bind_Decoder_GetAttributeIntForAllPoints_3=function(){return(Cc=a._emscripten_bind_Decoder_GetAttributeIntForAllPoints_3=a.asm.ab).apply(null,arguments)},Dc=a._emscripten_bind_Decoder_GetAttributeInt8ForAllPoints_3=function(){return(Dc=
|
||||||
|
a._emscripten_bind_Decoder_GetAttributeInt8ForAllPoints_3=a.asm.bb).apply(null,arguments)},Ec=a._emscripten_bind_Decoder_GetAttributeUInt8ForAllPoints_3=function(){return(Ec=a._emscripten_bind_Decoder_GetAttributeUInt8ForAllPoints_3=a.asm.cb).apply(null,arguments)},Fc=a._emscripten_bind_Decoder_GetAttributeInt16ForAllPoints_3=function(){return(Fc=a._emscripten_bind_Decoder_GetAttributeInt16ForAllPoints_3=a.asm.db).apply(null,arguments)},Gc=a._emscripten_bind_Decoder_GetAttributeUInt16ForAllPoints_3=
|
||||||
|
function(){return(Gc=a._emscripten_bind_Decoder_GetAttributeUInt16ForAllPoints_3=a.asm.eb).apply(null,arguments)},Hc=a._emscripten_bind_Decoder_GetAttributeInt32ForAllPoints_3=function(){return(Hc=a._emscripten_bind_Decoder_GetAttributeInt32ForAllPoints_3=a.asm.fb).apply(null,arguments)},Ic=a._emscripten_bind_Decoder_GetAttributeUInt32ForAllPoints_3=function(){return(Ic=a._emscripten_bind_Decoder_GetAttributeUInt32ForAllPoints_3=a.asm.gb).apply(null,arguments)},Jc=a._emscripten_bind_Decoder_GetAttributeDataArrayForAllPoints_5=
|
||||||
|
function(){return(Jc=a._emscripten_bind_Decoder_GetAttributeDataArrayForAllPoints_5=a.asm.hb).apply(null,arguments)},Kc=a._emscripten_bind_Decoder_SkipAttributeTransform_1=function(){return(Kc=a._emscripten_bind_Decoder_SkipAttributeTransform_1=a.asm.ib).apply(null,arguments)},Lc=a._emscripten_bind_Decoder_GetEncodedGeometryType_Deprecated_1=function(){return(Lc=a._emscripten_bind_Decoder_GetEncodedGeometryType_Deprecated_1=a.asm.jb).apply(null,arguments)},Mc=a._emscripten_bind_Decoder_DecodeBufferToPointCloud_2=
|
||||||
|
function(){return(Mc=a._emscripten_bind_Decoder_DecodeBufferToPointCloud_2=a.asm.kb).apply(null,arguments)},Nc=a._emscripten_bind_Decoder_DecodeBufferToMesh_2=function(){return(Nc=a._emscripten_bind_Decoder_DecodeBufferToMesh_2=a.asm.lb).apply(null,arguments)},Oc=a._emscripten_bind_Decoder___destroy___0=function(){return(Oc=a._emscripten_bind_Decoder___destroy___0=a.asm.mb).apply(null,arguments)},Pc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_INVALID_TRANSFORM=function(){return(Pc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_INVALID_TRANSFORM=
|
||||||
|
a.asm.nb).apply(null,arguments)},Qc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_NO_TRANSFORM=function(){return(Qc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_NO_TRANSFORM=a.asm.ob).apply(null,arguments)},Rc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_QUANTIZATION_TRANSFORM=function(){return(Rc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_QUANTIZATION_TRANSFORM=a.asm.pb).apply(null,arguments)},Sc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_OCTAHEDRON_TRANSFORM=
|
||||||
|
function(){return(Sc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_OCTAHEDRON_TRANSFORM=a.asm.qb).apply(null,arguments)},Tc=a._emscripten_enum_draco_GeometryAttribute_Type_INVALID=function(){return(Tc=a._emscripten_enum_draco_GeometryAttribute_Type_INVALID=a.asm.rb).apply(null,arguments)},Uc=a._emscripten_enum_draco_GeometryAttribute_Type_POSITION=function(){return(Uc=a._emscripten_enum_draco_GeometryAttribute_Type_POSITION=a.asm.sb).apply(null,arguments)},Vc=a._emscripten_enum_draco_GeometryAttribute_Type_NORMAL=
|
||||||
|
function(){return(Vc=a._emscripten_enum_draco_GeometryAttribute_Type_NORMAL=a.asm.tb).apply(null,arguments)},Wc=a._emscripten_enum_draco_GeometryAttribute_Type_COLOR=function(){return(Wc=a._emscripten_enum_draco_GeometryAttribute_Type_COLOR=a.asm.ub).apply(null,arguments)},Xc=a._emscripten_enum_draco_GeometryAttribute_Type_TEX_COORD=function(){return(Xc=a._emscripten_enum_draco_GeometryAttribute_Type_TEX_COORD=a.asm.vb).apply(null,arguments)},Yc=a._emscripten_enum_draco_GeometryAttribute_Type_GENERIC=
|
||||||
|
function(){return(Yc=a._emscripten_enum_draco_GeometryAttribute_Type_GENERIC=a.asm.wb).apply(null,arguments)},Zc=a._emscripten_enum_draco_EncodedGeometryType_INVALID_GEOMETRY_TYPE=function(){return(Zc=a._emscripten_enum_draco_EncodedGeometryType_INVALID_GEOMETRY_TYPE=a.asm.xb).apply(null,arguments)},$c=a._emscripten_enum_draco_EncodedGeometryType_POINT_CLOUD=function(){return($c=a._emscripten_enum_draco_EncodedGeometryType_POINT_CLOUD=a.asm.yb).apply(null,arguments)},ad=a._emscripten_enum_draco_EncodedGeometryType_TRIANGULAR_MESH=
|
||||||
|
function(){return(ad=a._emscripten_enum_draco_EncodedGeometryType_TRIANGULAR_MESH=a.asm.zb).apply(null,arguments)},bd=a._emscripten_enum_draco_DataType_DT_INVALID=function(){return(bd=a._emscripten_enum_draco_DataType_DT_INVALID=a.asm.Ab).apply(null,arguments)},cd=a._emscripten_enum_draco_DataType_DT_INT8=function(){return(cd=a._emscripten_enum_draco_DataType_DT_INT8=a.asm.Bb).apply(null,arguments)},dd=a._emscripten_enum_draco_DataType_DT_UINT8=function(){return(dd=a._emscripten_enum_draco_DataType_DT_UINT8=
|
||||||
|
a.asm.Cb).apply(null,arguments)},ed=a._emscripten_enum_draco_DataType_DT_INT16=function(){return(ed=a._emscripten_enum_draco_DataType_DT_INT16=a.asm.Db).apply(null,arguments)},fd=a._emscripten_enum_draco_DataType_DT_UINT16=function(){return(fd=a._emscripten_enum_draco_DataType_DT_UINT16=a.asm.Eb).apply(null,arguments)},gd=a._emscripten_enum_draco_DataType_DT_INT32=function(){return(gd=a._emscripten_enum_draco_DataType_DT_INT32=a.asm.Fb).apply(null,arguments)},hd=a._emscripten_enum_draco_DataType_DT_UINT32=
|
||||||
|
function(){return(hd=a._emscripten_enum_draco_DataType_DT_UINT32=a.asm.Gb).apply(null,arguments)},id=a._emscripten_enum_draco_DataType_DT_INT64=function(){return(id=a._emscripten_enum_draco_DataType_DT_INT64=a.asm.Hb).apply(null,arguments)},jd=a._emscripten_enum_draco_DataType_DT_UINT64=function(){return(jd=a._emscripten_enum_draco_DataType_DT_UINT64=a.asm.Ib).apply(null,arguments)},kd=a._emscripten_enum_draco_DataType_DT_FLOAT32=function(){return(kd=a._emscripten_enum_draco_DataType_DT_FLOAT32=a.asm.Jb).apply(null,
|
||||||
|
arguments)},ld=a._emscripten_enum_draco_DataType_DT_FLOAT64=function(){return(ld=a._emscripten_enum_draco_DataType_DT_FLOAT64=a.asm.Kb).apply(null,arguments)},md=a._emscripten_enum_draco_DataType_DT_BOOL=function(){return(md=a._emscripten_enum_draco_DataType_DT_BOOL=a.asm.Lb).apply(null,arguments)},nd=a._emscripten_enum_draco_DataType_DT_TYPES_COUNT=function(){return(nd=a._emscripten_enum_draco_DataType_DT_TYPES_COUNT=a.asm.Mb).apply(null,arguments)},od=a._emscripten_enum_draco_StatusCode_OK=function(){return(od=
|
||||||
|
a._emscripten_enum_draco_StatusCode_OK=a.asm.Nb).apply(null,arguments)},pd=a._emscripten_enum_draco_StatusCode_DRACO_ERROR=function(){return(pd=a._emscripten_enum_draco_StatusCode_DRACO_ERROR=a.asm.Ob).apply(null,arguments)},qd=a._emscripten_enum_draco_StatusCode_IO_ERROR=function(){return(qd=a._emscripten_enum_draco_StatusCode_IO_ERROR=a.asm.Pb).apply(null,arguments)},rd=a._emscripten_enum_draco_StatusCode_INVALID_PARAMETER=function(){return(rd=a._emscripten_enum_draco_StatusCode_INVALID_PARAMETER=
|
||||||
|
a.asm.Qb).apply(null,arguments)},sd=a._emscripten_enum_draco_StatusCode_UNSUPPORTED_VERSION=function(){return(sd=a._emscripten_enum_draco_StatusCode_UNSUPPORTED_VERSION=a.asm.Rb).apply(null,arguments)},td=a._emscripten_enum_draco_StatusCode_UNKNOWN_VERSION=function(){return(td=a._emscripten_enum_draco_StatusCode_UNKNOWN_VERSION=a.asm.Sb).apply(null,arguments)};a._malloc=function(){return(a._malloc=a.asm.Tb).apply(null,arguments)};a._free=function(){return(a._free=a.asm.Ub).apply(null,arguments)};
|
||||||
|
var ya=function(){return(ya=a.asm.Vb).apply(null,arguments)};a.___start_em_js=15856;a.___stop_em_js=15954;var la;ia=function b(){la||ba();la||(ia=b)};if(a.preInit)for("function"==typeof a.preInit&&(a.preInit=[a.preInit]);0<a.preInit.length;)a.preInit.pop()();ba();t.prototype=Object.create(t.prototype);t.prototype.constructor=t;t.prototype.__class__=t;t.__cache__={};a.WrapperObject=t;a.getCache=x;a.wrapPointer=D;a.castObject=function(b,c){return D(b.ptr,c)};a.NULL=D(0);a.destroy=function(b){if(!b.__destroy__)throw"Error: Cannot destroy object. (Did you create it yourself?)";
|
||||||
|
b.__destroy__();delete x(b.__class__)[b.ptr]};a.compare=function(b,c){return b.ptr===c.ptr};a.getPointer=function(b){return b.ptr};a.getClass=function(b){return b.__class__};var r={buffer:0,size:0,pos:0,temps:[],needed:0,prepare:function(){if(r.needed){for(var b=0;b<r.temps.length;b++)a._free(r.temps[b]);r.temps.length=0;a._free(r.buffer);r.buffer=0;r.size+=r.needed;r.needed=0}r.buffer||(r.size+=128,r.buffer=a._malloc(r.size),r.buffer||f(void 0));r.pos=0},alloc:function(b,c){r.buffer||f(void 0);b=
|
||||||
|
b.length*c.BYTES_PER_ELEMENT;b=b+7&-8;r.pos+b>=r.size?(0<b||f(void 0),r.needed+=b,c=a._malloc(b),r.temps.push(c)):(c=r.buffer+r.pos,r.pos+=b);return c},copy:function(b,c,d){d>>>=0;switch(c.BYTES_PER_ELEMENT){case 2:d>>>=1;break;case 4:d>>>=2;break;case 8:d>>>=3}for(var g=0;g<b.length;g++)c[d+g]=b[g]}};Z.prototype=Object.create(t.prototype);Z.prototype.constructor=Z;Z.prototype.__class__=Z;Z.__cache__={};a.VoidPtr=Z;Z.prototype.__destroy__=Z.prototype.__destroy__=function(){bb(this.ptr)};S.prototype=
|
||||||
|
Object.create(t.prototype);S.prototype.constructor=S;S.prototype.__class__=S;S.__cache__={};a.DecoderBuffer=S;S.prototype.Init=S.prototype.Init=function(b,c){var d=this.ptr;r.prepare();"object"==typeof b&&(b=pa(b));c&&"object"===typeof c&&(c=c.ptr);cb(d,b,c)};S.prototype.__destroy__=S.prototype.__destroy__=function(){db(this.ptr)};Q.prototype=Object.create(t.prototype);Q.prototype.constructor=Q;Q.prototype.__class__=Q;Q.__cache__={};a.AttributeTransformData=Q;Q.prototype.transform_type=Q.prototype.transform_type=
|
||||||
|
function(){return eb(this.ptr)};Q.prototype.__destroy__=Q.prototype.__destroy__=function(){fb(this.ptr)};W.prototype=Object.create(t.prototype);W.prototype.constructor=W;W.prototype.__class__=W;W.__cache__={};a.GeometryAttribute=W;W.prototype.__destroy__=W.prototype.__destroy__=function(){gb(this.ptr)};w.prototype=Object.create(t.prototype);w.prototype.constructor=w;w.prototype.__class__=w;w.__cache__={};a.PointAttribute=w;w.prototype.size=w.prototype.size=function(){return hb(this.ptr)};w.prototype.GetAttributeTransformData=
|
||||||
|
w.prototype.GetAttributeTransformData=function(){return D(ib(this.ptr),Q)};w.prototype.attribute_type=w.prototype.attribute_type=function(){return jb(this.ptr)};w.prototype.data_type=w.prototype.data_type=function(){return kb(this.ptr)};w.prototype.num_components=w.prototype.num_components=function(){return lb(this.ptr)};w.prototype.normalized=w.prototype.normalized=function(){return!!mb(this.ptr)};w.prototype.byte_stride=w.prototype.byte_stride=function(){return nb(this.ptr)};w.prototype.byte_offset=
|
||||||
|
w.prototype.byte_offset=function(){return ob(this.ptr)};w.prototype.unique_id=w.prototype.unique_id=function(){return pb(this.ptr)};w.prototype.__destroy__=w.prototype.__destroy__=function(){qb(this.ptr)};C.prototype=Object.create(t.prototype);C.prototype.constructor=C;C.prototype.__class__=C;C.__cache__={};a.AttributeQuantizationTransform=C;C.prototype.InitFromAttribute=C.prototype.InitFromAttribute=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return!!rb(c,b)};C.prototype.quantization_bits=
|
||||||
|
C.prototype.quantization_bits=function(){return sb(this.ptr)};C.prototype.min_value=C.prototype.min_value=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return tb(c,b)};C.prototype.range=C.prototype.range=function(){return ub(this.ptr)};C.prototype.__destroy__=C.prototype.__destroy__=function(){vb(this.ptr)};F.prototype=Object.create(t.prototype);F.prototype.constructor=F;F.prototype.__class__=F;F.__cache__={};a.AttributeOctahedronTransform=F;F.prototype.InitFromAttribute=F.prototype.InitFromAttribute=
|
||||||
|
function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return!!wb(c,b)};F.prototype.quantization_bits=F.prototype.quantization_bits=function(){return xb(this.ptr)};F.prototype.__destroy__=F.prototype.__destroy__=function(){yb(this.ptr)};G.prototype=Object.create(t.prototype);G.prototype.constructor=G;G.prototype.__class__=G;G.__cache__={};a.PointCloud=G;G.prototype.num_attributes=G.prototype.num_attributes=function(){return zb(this.ptr)};G.prototype.num_points=G.prototype.num_points=function(){return Ab(this.ptr)};
|
||||||
|
G.prototype.__destroy__=G.prototype.__destroy__=function(){Bb(this.ptr)};E.prototype=Object.create(t.prototype);E.prototype.constructor=E;E.prototype.__class__=E;E.__cache__={};a.Mesh=E;E.prototype.num_faces=E.prototype.num_faces=function(){return Cb(this.ptr)};E.prototype.num_attributes=E.prototype.num_attributes=function(){return Db(this.ptr)};E.prototype.num_points=E.prototype.num_points=function(){return Eb(this.ptr)};E.prototype.__destroy__=E.prototype.__destroy__=function(){Fb(this.ptr)};T.prototype=
|
||||||
|
Object.create(t.prototype);T.prototype.constructor=T;T.prototype.__class__=T;T.__cache__={};a.Metadata=T;T.prototype.__destroy__=T.prototype.__destroy__=function(){Gb(this.ptr)};B.prototype=Object.create(t.prototype);B.prototype.constructor=B;B.prototype.__class__=B;B.__cache__={};a.Status=B;B.prototype.code=B.prototype.code=function(){return Hb(this.ptr)};B.prototype.ok=B.prototype.ok=function(){return!!Ib(this.ptr)};B.prototype.error_msg=B.prototype.error_msg=function(){return h(Jb(this.ptr))};
|
||||||
|
B.prototype.__destroy__=B.prototype.__destroy__=function(){Kb(this.ptr)};H.prototype=Object.create(t.prototype);H.prototype.constructor=H;H.prototype.__class__=H;H.__cache__={};a.DracoFloat32Array=H;H.prototype.GetValue=H.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Lb(c,b)};H.prototype.size=H.prototype.size=function(){return Mb(this.ptr)};H.prototype.__destroy__=H.prototype.__destroy__=function(){Nb(this.ptr)};I.prototype=Object.create(t.prototype);I.prototype.constructor=
|
||||||
|
I;I.prototype.__class__=I;I.__cache__={};a.DracoInt8Array=I;I.prototype.GetValue=I.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Ob(c,b)};I.prototype.size=I.prototype.size=function(){return Pb(this.ptr)};I.prototype.__destroy__=I.prototype.__destroy__=function(){Qb(this.ptr)};J.prototype=Object.create(t.prototype);J.prototype.constructor=J;J.prototype.__class__=J;J.__cache__={};a.DracoUInt8Array=J;J.prototype.GetValue=J.prototype.GetValue=function(b){var c=
|
||||||
|
this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Rb(c,b)};J.prototype.size=J.prototype.size=function(){return Sb(this.ptr)};J.prototype.__destroy__=J.prototype.__destroy__=function(){Tb(this.ptr)};K.prototype=Object.create(t.prototype);K.prototype.constructor=K;K.prototype.__class__=K;K.__cache__={};a.DracoInt16Array=K;K.prototype.GetValue=K.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Ub(c,b)};K.prototype.size=K.prototype.size=function(){return Vb(this.ptr)};
|
||||||
|
K.prototype.__destroy__=K.prototype.__destroy__=function(){Wb(this.ptr)};L.prototype=Object.create(t.prototype);L.prototype.constructor=L;L.prototype.__class__=L;L.__cache__={};a.DracoUInt16Array=L;L.prototype.GetValue=L.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Xb(c,b)};L.prototype.size=L.prototype.size=function(){return Yb(this.ptr)};L.prototype.__destroy__=L.prototype.__destroy__=function(){Zb(this.ptr)};M.prototype=Object.create(t.prototype);M.prototype.constructor=
|
||||||
|
M;M.prototype.__class__=M;M.__cache__={};a.DracoInt32Array=M;M.prototype.GetValue=M.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return $b(c,b)};M.prototype.size=M.prototype.size=function(){return ac(this.ptr)};M.prototype.__destroy__=M.prototype.__destroy__=function(){bc(this.ptr)};N.prototype=Object.create(t.prototype);N.prototype.constructor=N;N.prototype.__class__=N;N.__cache__={};a.DracoUInt32Array=N;N.prototype.GetValue=N.prototype.GetValue=function(b){var c=
|
||||||
|
this.ptr;b&&"object"===typeof b&&(b=b.ptr);return cc(c,b)};N.prototype.size=N.prototype.size=function(){return dc(this.ptr)};N.prototype.__destroy__=N.prototype.__destroy__=function(){ec(this.ptr)};y.prototype=Object.create(t.prototype);y.prototype.constructor=y;y.prototype.__class__=y;y.__cache__={};a.MetadataQuerier=y;y.prototype.HasEntry=y.prototype.HasEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return!!fc(d,b,c)};y.prototype.GetIntEntry=
|
||||||
|
y.prototype.GetIntEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return gc(d,b,c)};y.prototype.GetIntEntryArray=y.prototype.GetIntEntryArray=function(b,c,d){var g=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);d&&"object"===typeof d&&(d=d.ptr);hc(g,b,c,d)};y.prototype.GetDoubleEntry=y.prototype.GetDoubleEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=
|
||||||
|
c&&"object"===typeof c?c.ptr:R(c);return ic(d,b,c)};y.prototype.GetStringEntry=y.prototype.GetStringEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return h(jc(d,b,c))};y.prototype.NumEntries=y.prototype.NumEntries=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return kc(c,b)};y.prototype.GetEntryName=y.prototype.GetEntryName=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=
|
||||||
|
c.ptr);return h(lc(d,b,c))};y.prototype.__destroy__=y.prototype.__destroy__=function(){mc(this.ptr)};m.prototype=Object.create(t.prototype);m.prototype.constructor=m;m.prototype.__class__=m;m.__cache__={};a.Decoder=m;m.prototype.DecodeArrayToPointCloud=m.prototype.DecodeArrayToPointCloud=function(b,c,d){var g=this.ptr;r.prepare();"object"==typeof b&&(b=pa(b));c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return D(nc(g,b,c,d),B)};m.prototype.DecodeArrayToMesh=m.prototype.DecodeArrayToMesh=
|
||||||
|
function(b,c,d){var g=this.ptr;r.prepare();"object"==typeof b&&(b=pa(b));c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return D(oc(g,b,c,d),B)};m.prototype.GetAttributeId=m.prototype.GetAttributeId=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return pc(d,b,c)};m.prototype.GetAttributeIdByName=m.prototype.GetAttributeIdByName=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?
|
||||||
|
c.ptr:R(c);return qc(d,b,c)};m.prototype.GetAttributeIdByMetadataEntry=m.prototype.GetAttributeIdByMetadataEntry=function(b,c,d){var g=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);d=d&&"object"===typeof d?d.ptr:R(d);return rc(g,b,c,d)};m.prototype.GetAttribute=m.prototype.GetAttribute=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return D(sc(d,b,c),w)};m.prototype.GetAttributeByUniqueId=m.prototype.GetAttributeByUniqueId=
|
||||||
|
function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return D(tc(d,b,c),w)};m.prototype.GetMetadata=m.prototype.GetMetadata=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return D(uc(c,b),T)};m.prototype.GetAttributeMetadata=m.prototype.GetAttributeMetadata=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return D(vc(d,b,c),T)};m.prototype.GetFaceFromMesh=m.prototype.GetFaceFromMesh=function(b,
|
||||||
|
c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!wc(g,b,c,d)};m.prototype.GetTriangleStripsFromMesh=m.prototype.GetTriangleStripsFromMesh=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return xc(d,b,c)};m.prototype.GetTrianglesUInt16Array=m.prototype.GetTrianglesUInt16Array=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);
|
||||||
|
d&&"object"===typeof d&&(d=d.ptr);return!!yc(g,b,c,d)};m.prototype.GetTrianglesUInt32Array=m.prototype.GetTrianglesUInt32Array=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!zc(g,b,c,d)};m.prototype.GetAttributeFloat=m.prototype.GetAttributeFloat=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Ac(g,b,c,d)};m.prototype.GetAttributeFloatForAllPoints=
|
||||||
|
m.prototype.GetAttributeFloatForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Bc(g,b,c,d)};m.prototype.GetAttributeIntForAllPoints=m.prototype.GetAttributeIntForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Cc(g,b,c,d)};m.prototype.GetAttributeInt8ForAllPoints=m.prototype.GetAttributeInt8ForAllPoints=
|
||||||
|
function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Dc(g,b,c,d)};m.prototype.GetAttributeUInt8ForAllPoints=m.prototype.GetAttributeUInt8ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Ec(g,b,c,d)};m.prototype.GetAttributeInt16ForAllPoints=m.prototype.GetAttributeInt16ForAllPoints=function(b,c,d){var g=this.ptr;
|
||||||
|
b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Fc(g,b,c,d)};m.prototype.GetAttributeUInt16ForAllPoints=m.prototype.GetAttributeUInt16ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Gc(g,b,c,d)};m.prototype.GetAttributeInt32ForAllPoints=m.prototype.GetAttributeInt32ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&
|
||||||
|
(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Hc(g,b,c,d)};m.prototype.GetAttributeUInt32ForAllPoints=m.prototype.GetAttributeUInt32ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Ic(g,b,c,d)};m.prototype.GetAttributeDataArrayForAllPoints=m.prototype.GetAttributeDataArrayForAllPoints=function(b,c,d,g,u){var X=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&
|
||||||
|
"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);g&&"object"===typeof g&&(g=g.ptr);u&&"object"===typeof u&&(u=u.ptr);return!!Jc(X,b,c,d,g,u)};m.prototype.SkipAttributeTransform=m.prototype.SkipAttributeTransform=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);Kc(c,b)};m.prototype.GetEncodedGeometryType_Deprecated=m.prototype.GetEncodedGeometryType_Deprecated=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Lc(c,b)};m.prototype.DecodeBufferToPointCloud=
|
||||||
|
m.prototype.DecodeBufferToPointCloud=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return D(Mc(d,b,c),B)};m.prototype.DecodeBufferToMesh=m.prototype.DecodeBufferToMesh=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return D(Nc(d,b,c),B)};m.prototype.__destroy__=m.prototype.__destroy__=function(){Oc(this.ptr)};(function(){function b(){a.ATTRIBUTE_INVALID_TRANSFORM=Pc();a.ATTRIBUTE_NO_TRANSFORM=Qc();
|
||||||
|
a.ATTRIBUTE_QUANTIZATION_TRANSFORM=Rc();a.ATTRIBUTE_OCTAHEDRON_TRANSFORM=Sc();a.INVALID=Tc();a.POSITION=Uc();a.NORMAL=Vc();a.COLOR=Wc();a.TEX_COORD=Xc();a.GENERIC=Yc();a.INVALID_GEOMETRY_TYPE=Zc();a.POINT_CLOUD=$c();a.TRIANGULAR_MESH=ad();a.DT_INVALID=bd();a.DT_INT8=cd();a.DT_UINT8=dd();a.DT_INT16=ed();a.DT_UINT16=fd();a.DT_INT32=gd();a.DT_UINT32=hd();a.DT_INT64=id();a.DT_UINT64=jd();a.DT_FLOAT32=kd();a.DT_FLOAT64=ld();a.DT_BOOL=md();a.DT_TYPES_COUNT=nd();a.OK=od();a.DRACO_ERROR=pd();a.IO_ERROR=qd();
|
||||||
|
a.INVALID_PARAMETER=rd();a.UNSUPPORTED_VERSION=sd();a.UNKNOWN_VERSION=td()}za?b():oa.unshift(b)})();if("function"===typeof a.onModuleParsed)a.onModuleParsed();a.Decoder.prototype.GetEncodedGeometryType=function(b){if(b.__class__&&b.__class__===a.DecoderBuffer)return a.Decoder.prototype.GetEncodedGeometryType_Deprecated(b);if(8>b.byteLength)return a.INVALID_GEOMETRY_TYPE;switch(b[7]){case 0:return a.POINT_CLOUD;case 1:return a.TRIANGULAR_MESH;default:return a.INVALID_GEOMETRY_TYPE}};return n.ready}}();
|
||||||
|
"object"===typeof exports&&"object"===typeof module?module.exports=DracoDecoderModule:"function"===typeof define&&define.amd?define([],function(){return DracoDecoderModule}):"object"===typeof exports&&(exports.DracoDecoderModule=DracoDecoderModule);
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.arrayIteratorImpl=function(h){var n=0;return function(){return n<h.length?{done:!1,value:h[n++]}:{done:!0}}};$jscomp.arrayIterator=function(h){return{next:$jscomp.arrayIteratorImpl(h)}};$jscomp.makeIterator=function(h){var n="undefined"!=typeof Symbol&&Symbol.iterator&&h[Symbol.iterator];return n?n.call(h):$jscomp.arrayIterator(h)};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;
|
||||||
|
$jscomp.ISOLATE_POLYFILLS=!1;$jscomp.FORCE_POLYFILL_PROMISE=!1;$jscomp.FORCE_POLYFILL_PROMISE_WHEN_NO_UNHANDLED_REJECTION=!1;$jscomp.getGlobal=function(h){h=["object"==typeof globalThis&&globalThis,h,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var n=0;n<h.length;++n){var k=h[n];if(k&&k.Math==Math)return k}throw Error("Cannot find global object");};$jscomp.global=$jscomp.getGlobal(this);
|
||||||
|
$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(h,n,k){if(h==Array.prototype||h==Object.prototype)return h;h[n]=k.value;return h};$jscomp.IS_SYMBOL_NATIVE="function"===typeof Symbol&&"symbol"===typeof Symbol("x");$jscomp.TRUST_ES6_POLYFILLS=!$jscomp.ISOLATE_POLYFILLS||$jscomp.IS_SYMBOL_NATIVE;$jscomp.polyfills={};$jscomp.propertyToPolyfillSymbol={};$jscomp.POLYFILL_PREFIX="$jscp$";
|
||||||
|
var $jscomp$lookupPolyfilledValue=function(h,n){var k=$jscomp.propertyToPolyfillSymbol[n];if(null==k)return h[n];k=h[k];return void 0!==k?k:h[n]};$jscomp.polyfill=function(h,n,k,p){n&&($jscomp.ISOLATE_POLYFILLS?$jscomp.polyfillIsolated(h,n,k,p):$jscomp.polyfillUnisolated(h,n,k,p))};
|
||||||
|
$jscomp.polyfillUnisolated=function(h,n,k,p){k=$jscomp.global;h=h.split(".");for(p=0;p<h.length-1;p++){var l=h[p];if(!(l in k))return;k=k[l]}h=h[h.length-1];p=k[h];n=n(p);n!=p&&null!=n&&$jscomp.defineProperty(k,h,{configurable:!0,writable:!0,value:n})};
|
||||||
|
$jscomp.polyfillIsolated=function(h,n,k,p){var l=h.split(".");h=1===l.length;p=l[0];p=!h&&p in $jscomp.polyfills?$jscomp.polyfills:$jscomp.global;for(var y=0;y<l.length-1;y++){var f=l[y];if(!(f in p))return;p=p[f]}l=l[l.length-1];k=$jscomp.IS_SYMBOL_NATIVE&&"es6"===k?p[l]:null;n=n(k);null!=n&&(h?$jscomp.defineProperty($jscomp.polyfills,l,{configurable:!0,writable:!0,value:n}):n!==k&&(void 0===$jscomp.propertyToPolyfillSymbol[l]&&(k=1E9*Math.random()>>>0,$jscomp.propertyToPolyfillSymbol[l]=$jscomp.IS_SYMBOL_NATIVE?
|
||||||
|
$jscomp.global.Symbol(l):$jscomp.POLYFILL_PREFIX+k+"$"+l),$jscomp.defineProperty(p,$jscomp.propertyToPolyfillSymbol[l],{configurable:!0,writable:!0,value:n})))};
|
||||||
|
$jscomp.polyfill("Promise",function(h){function n(){this.batch_=null}function k(f){return f instanceof l?f:new l(function(q,u){q(f)})}if(h&&(!($jscomp.FORCE_POLYFILL_PROMISE||$jscomp.FORCE_POLYFILL_PROMISE_WHEN_NO_UNHANDLED_REJECTION&&"undefined"===typeof $jscomp.global.PromiseRejectionEvent)||!$jscomp.global.Promise||-1===$jscomp.global.Promise.toString().indexOf("[native code]")))return h;n.prototype.asyncExecute=function(f){if(null==this.batch_){this.batch_=[];var q=this;this.asyncExecuteFunction(function(){q.executeBatch_()})}this.batch_.push(f)};
|
||||||
|
var p=$jscomp.global.setTimeout;n.prototype.asyncExecuteFunction=function(f){p(f,0)};n.prototype.executeBatch_=function(){for(;this.batch_&&this.batch_.length;){var f=this.batch_;this.batch_=[];for(var q=0;q<f.length;++q){var u=f[q];f[q]=null;try{u()}catch(A){this.asyncThrow_(A)}}}this.batch_=null};n.prototype.asyncThrow_=function(f){this.asyncExecuteFunction(function(){throw f;})};var l=function(f){this.state_=0;this.result_=void 0;this.onSettledCallbacks_=[];this.isRejectionHandled_=!1;var q=this.createResolveAndReject_();
|
||||||
|
try{f(q.resolve,q.reject)}catch(u){q.reject(u)}};l.prototype.createResolveAndReject_=function(){function f(A){return function(F){u||(u=!0,A.call(q,F))}}var q=this,u=!1;return{resolve:f(this.resolveTo_),reject:f(this.reject_)}};l.prototype.resolveTo_=function(f){if(f===this)this.reject_(new TypeError("A Promise cannot resolve to itself"));else if(f instanceof l)this.settleSameAsPromise_(f);else{a:switch(typeof f){case "object":var q=null!=f;break a;case "function":q=!0;break a;default:q=!1}q?this.resolveToNonPromiseObj_(f):
|
||||||
|
this.fulfill_(f)}};l.prototype.resolveToNonPromiseObj_=function(f){var q=void 0;try{q=f.then}catch(u){this.reject_(u);return}"function"==typeof q?this.settleSameAsThenable_(q,f):this.fulfill_(f)};l.prototype.reject_=function(f){this.settle_(2,f)};l.prototype.fulfill_=function(f){this.settle_(1,f)};l.prototype.settle_=function(f,q){if(0!=this.state_)throw Error("Cannot settle("+f+", "+q+"): Promise already settled in state"+this.state_);this.state_=f;this.result_=q;2===this.state_&&this.scheduleUnhandledRejectionCheck_();
|
||||||
|
this.executeOnSettledCallbacks_()};l.prototype.scheduleUnhandledRejectionCheck_=function(){var f=this;p(function(){if(f.notifyUnhandledRejection_()){var q=$jscomp.global.console;"undefined"!==typeof q&&q.error(f.result_)}},1)};l.prototype.notifyUnhandledRejection_=function(){if(this.isRejectionHandled_)return!1;var f=$jscomp.global.CustomEvent,q=$jscomp.global.Event,u=$jscomp.global.dispatchEvent;if("undefined"===typeof u)return!0;"function"===typeof f?f=new f("unhandledrejection",{cancelable:!0}):
|
||||||
|
"function"===typeof q?f=new q("unhandledrejection",{cancelable:!0}):(f=$jscomp.global.document.createEvent("CustomEvent"),f.initCustomEvent("unhandledrejection",!1,!0,f));f.promise=this;f.reason=this.result_;return u(f)};l.prototype.executeOnSettledCallbacks_=function(){if(null!=this.onSettledCallbacks_){for(var f=0;f<this.onSettledCallbacks_.length;++f)y.asyncExecute(this.onSettledCallbacks_[f]);this.onSettledCallbacks_=null}};var y=new n;l.prototype.settleSameAsPromise_=function(f){var q=this.createResolveAndReject_();
|
||||||
|
f.callWhenSettled_(q.resolve,q.reject)};l.prototype.settleSameAsThenable_=function(f,q){var u=this.createResolveAndReject_();try{f.call(q,u.resolve,u.reject)}catch(A){u.reject(A)}};l.prototype.then=function(f,q){function u(w,B){return"function"==typeof w?function(R){try{A(w(R))}catch(Z){F(Z)}}:B}var A,F,v=new l(function(w,B){A=w;F=B});this.callWhenSettled_(u(f,A),u(q,F));return v};l.prototype.catch=function(f){return this.then(void 0,f)};l.prototype.callWhenSettled_=function(f,q){function u(){switch(A.state_){case 1:f(A.result_);
|
||||||
|
break;case 2:q(A.result_);break;default:throw Error("Unexpected state: "+A.state_);}}var A=this;null==this.onSettledCallbacks_?y.asyncExecute(u):this.onSettledCallbacks_.push(u);this.isRejectionHandled_=!0};l.resolve=k;l.reject=function(f){return new l(function(q,u){u(f)})};l.race=function(f){return new l(function(q,u){for(var A=$jscomp.makeIterator(f),F=A.next();!F.done;F=A.next())k(F.value).callWhenSettled_(q,u)})};l.all=function(f){var q=$jscomp.makeIterator(f),u=q.next();return u.done?k([]):new l(function(A,
|
||||||
|
F){function v(R){return function(Z){w[R]=Z;B--;0==B&&A(w)}}var w=[],B=0;do w.push(void 0),B++,k(u.value).callWhenSettled_(v(w.length-1),F),u=q.next();while(!u.done)})};return l},"es6","es3");$jscomp.owns=function(h,n){return Object.prototype.hasOwnProperty.call(h,n)};$jscomp.assign=$jscomp.TRUST_ES6_POLYFILLS&&"function"==typeof Object.assign?Object.assign:function(h,n){for(var k=1;k<arguments.length;k++){var p=arguments[k];if(p)for(var l in p)$jscomp.owns(p,l)&&(h[l]=p[l])}return h};
|
||||||
|
$jscomp.polyfill("Object.assign",function(h){return h||$jscomp.assign},"es6","es3");$jscomp.checkStringArgs=function(h,n,k){if(null==h)throw new TypeError("The 'this' value for String.prototype."+k+" must not be null or undefined");if(n instanceof RegExp)throw new TypeError("First argument to String.prototype."+k+" must not be a regular expression");return h+""};
|
||||||
|
$jscomp.polyfill("String.prototype.startsWith",function(h){return h?h:function(n,k){var p=$jscomp.checkStringArgs(this,n,"startsWith");n+="";var l=p.length,y=n.length;k=Math.max(0,Math.min(k|0,p.length));for(var f=0;f<y&&k<l;)if(p[k++]!=n[f++])return!1;return f>=y}},"es6","es3");
|
||||||
|
$jscomp.polyfill("Array.prototype.copyWithin",function(h){function n(k){k=Number(k);return Infinity===k||-Infinity===k?k:k|0}return h?h:function(k,p,l){var y=this.length;k=n(k);p=n(p);l=void 0===l?y:n(l);k=0>k?Math.max(y+k,0):Math.min(k,y);p=0>p?Math.max(y+p,0):Math.min(p,y);l=0>l?Math.max(y+l,0):Math.min(l,y);if(k<p)for(;p<l;)p in this?this[k++]=this[p++]:(delete this[k++],p++);else for(l=Math.min(l,y+p-k),k+=l-p;l>p;)--l in this?this[--k]=this[l]:delete this[--k];return this}},"es6","es3");
|
||||||
|
$jscomp.typedArrayCopyWithin=function(h){return h?h:Array.prototype.copyWithin};$jscomp.polyfill("Int8Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint8Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint8ClampedArray.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Int16Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");
|
||||||
|
$jscomp.polyfill("Uint16Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Int32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Float32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Float64Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");
|
||||||
|
var DracoDecoderModule=function(){var h="undefined"!==typeof document&&document.currentScript?document.currentScript.src:void 0;"undefined"!==typeof __filename&&(h=h||__filename);return function(n){function k(e){return a.locateFile?a.locateFile(e,U):U+e}function p(e,b){if(e){var c=ia;var d=e+b;for(b=e;c[b]&&!(b>=d);)++b;if(16<b-e&&c.buffer&&ra)c=ra.decode(c.subarray(e,b));else{for(d="";e<b;){var g=c[e++];if(g&128){var t=c[e++]&63;if(192==(g&224))d+=String.fromCharCode((g&31)<<6|t);else{var aa=c[e++]&
|
||||||
|
63;g=224==(g&240)?(g&15)<<12|t<<6|aa:(g&7)<<18|t<<12|aa<<6|c[e++]&63;65536>g?d+=String.fromCharCode(g):(g-=65536,d+=String.fromCharCode(55296|g>>10,56320|g&1023))}}else d+=String.fromCharCode(g)}c=d}}else c="";return c}function l(){var e=ja.buffer;a.HEAP8=W=new Int8Array(e);a.HEAP16=new Int16Array(e);a.HEAP32=ca=new Int32Array(e);a.HEAPU8=ia=new Uint8Array(e);a.HEAPU16=new Uint16Array(e);a.HEAPU32=Y=new Uint32Array(e);a.HEAPF32=new Float32Array(e);a.HEAPF64=new Float64Array(e)}function y(e){if(a.onAbort)a.onAbort(e);
|
||||||
|
e="Aborted("+e+")";da(e);sa=!0;e=new WebAssembly.RuntimeError(e+". Build with -sASSERTIONS for more info.");ka(e);throw e;}function f(e){try{if(e==P&&ea)return new Uint8Array(ea);if(ma)return ma(e);throw"both async and sync fetching of the wasm failed";}catch(b){y(b)}}function q(){if(!ea&&(ta||fa)){if("function"==typeof fetch&&!P.startsWith("file://"))return fetch(P,{credentials:"same-origin"}).then(function(e){if(!e.ok)throw"failed to load wasm binary file at '"+P+"'";return e.arrayBuffer()}).catch(function(){return f(P)});
|
||||||
|
if(na)return new Promise(function(e,b){na(P,function(c){e(new Uint8Array(c))},b)})}return Promise.resolve().then(function(){return f(P)})}function u(e){for(;0<e.length;)e.shift()(a)}function A(e){this.excPtr=e;this.ptr=e-24;this.set_type=function(b){Y[this.ptr+4>>2]=b};this.get_type=function(){return Y[this.ptr+4>>2]};this.set_destructor=function(b){Y[this.ptr+8>>2]=b};this.get_destructor=function(){return Y[this.ptr+8>>2]};this.set_refcount=function(b){ca[this.ptr>>2]=b};this.set_caught=function(b){W[this.ptr+
|
||||||
|
12>>0]=b?1:0};this.get_caught=function(){return 0!=W[this.ptr+12>>0]};this.set_rethrown=function(b){W[this.ptr+13>>0]=b?1:0};this.get_rethrown=function(){return 0!=W[this.ptr+13>>0]};this.init=function(b,c){this.set_adjusted_ptr(0);this.set_type(b);this.set_destructor(c);this.set_refcount(0);this.set_caught(!1);this.set_rethrown(!1)};this.add_ref=function(){ca[this.ptr>>2]+=1};this.release_ref=function(){var b=ca[this.ptr>>2];ca[this.ptr>>2]=b-1;return 1===b};this.set_adjusted_ptr=function(b){Y[this.ptr+
|
||||||
|
16>>2]=b};this.get_adjusted_ptr=function(){return Y[this.ptr+16>>2]};this.get_exception_ptr=function(){if(ua(this.get_type()))return Y[this.excPtr>>2];var b=this.get_adjusted_ptr();return 0!==b?b:this.excPtr}}function F(){function e(){if(!la&&(la=!0,a.calledRun=!0,!sa)){va=!0;u(oa);wa(a);if(a.onRuntimeInitialized)a.onRuntimeInitialized();if(a.postRun)for("function"==typeof a.postRun&&(a.postRun=[a.postRun]);a.postRun.length;)xa.unshift(a.postRun.shift());u(xa)}}if(!(0<ba)){if(a.preRun)for("function"==
|
||||||
|
typeof a.preRun&&(a.preRun=[a.preRun]);a.preRun.length;)ya.unshift(a.preRun.shift());u(ya);0<ba||(a.setStatus?(a.setStatus("Running..."),setTimeout(function(){setTimeout(function(){a.setStatus("")},1);e()},1)):e())}}function v(){}function w(e){return(e||v).__cache__}function B(e,b){var c=w(b),d=c[e];if(d)return d;d=Object.create((b||v).prototype);d.ptr=e;return c[e]=d}function R(e){if("string"===typeof e){for(var b=0,c=0;c<e.length;++c){var d=e.charCodeAt(c);127>=d?b++:2047>=d?b+=2:55296<=d&&57343>=
|
||||||
|
d?(b+=4,++c):b+=3}b=Array(b+1);c=0;d=b.length;if(0<d){d=c+d-1;for(var g=0;g<e.length;++g){var t=e.charCodeAt(g);if(55296<=t&&57343>=t){var aa=e.charCodeAt(++g);t=65536+((t&1023)<<10)|aa&1023}if(127>=t){if(c>=d)break;b[c++]=t}else{if(2047>=t){if(c+1>=d)break;b[c++]=192|t>>6}else{if(65535>=t){if(c+2>=d)break;b[c++]=224|t>>12}else{if(c+3>=d)break;b[c++]=240|t>>18;b[c++]=128|t>>12&63}b[c++]=128|t>>6&63}b[c++]=128|t&63}}b[c]=0}e=r.alloc(b,W);r.copy(b,W,e);return e}return e}function Z(e){if("object"===
|
||||||
|
typeof e){var b=r.alloc(e,W);r.copy(e,W,b);return b}return e}function X(){throw"cannot construct a VoidPtr, no constructor in IDL";}function S(){this.ptr=za();w(S)[this.ptr]=this}function Q(){this.ptr=Aa();w(Q)[this.ptr]=this}function V(){this.ptr=Ba();w(V)[this.ptr]=this}function x(){this.ptr=Ca();w(x)[this.ptr]=this}function D(){this.ptr=Da();w(D)[this.ptr]=this}function G(){this.ptr=Ea();w(G)[this.ptr]=this}function H(){this.ptr=Fa();w(H)[this.ptr]=this}function E(){this.ptr=Ga();w(E)[this.ptr]=
|
||||||
|
this}function T(){this.ptr=Ha();w(T)[this.ptr]=this}function C(){throw"cannot construct a Status, no constructor in IDL";}function I(){this.ptr=Ia();w(I)[this.ptr]=this}function J(){this.ptr=Ja();w(J)[this.ptr]=this}function K(){this.ptr=Ka();w(K)[this.ptr]=this}function L(){this.ptr=La();w(L)[this.ptr]=this}function M(){this.ptr=Ma();w(M)[this.ptr]=this}function N(){this.ptr=Na();w(N)[this.ptr]=this}function O(){this.ptr=Oa();w(O)[this.ptr]=this}function z(){this.ptr=Pa();w(z)[this.ptr]=this}function m(){this.ptr=
|
||||||
|
Qa();w(m)[this.ptr]=this}n=void 0===n?{}:n;var a="undefined"!=typeof n?n:{},wa,ka;a.ready=new Promise(function(e,b){wa=e;ka=b});var Ra=!1,Sa=!1;a.onRuntimeInitialized=function(){Ra=!0;if(Sa&&"function"===typeof a.onModuleLoaded)a.onModuleLoaded(a)};a.onModuleParsed=function(){Sa=!0;if(Ra&&"function"===typeof a.onModuleLoaded)a.onModuleLoaded(a)};a.isVersionSupported=function(e){if("string"!==typeof e)return!1;e=e.split(".");return 2>e.length||3<e.length?!1:1==e[0]&&0<=e[1]&&5>=e[1]?!0:0!=e[0]||10<
|
||||||
|
e[1]?!1:!0};var Ta=Object.assign({},a),ta="object"==typeof window,fa="function"==typeof importScripts,Ua="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,U="";if(Ua){var Va=require("fs"),pa=require("path");U=fa?pa.dirname(U)+"/":__dirname+"/";var Wa=function(e,b){e=e.startsWith("file://")?new URL(e):pa.normalize(e);return Va.readFileSync(e,b?void 0:"utf8")};var ma=function(e){e=Wa(e,!0);e.buffer||(e=new Uint8Array(e));return e};var na=function(e,
|
||||||
|
b,c){e=e.startsWith("file://")?new URL(e):pa.normalize(e);Va.readFile(e,function(d,g){d?c(d):b(g.buffer)})};1<process.argv.length&&process.argv[1].replace(/\\/g,"/");process.argv.slice(2);a.inspect=function(){return"[Emscripten Module object]"}}else if(ta||fa)fa?U=self.location.href:"undefined"!=typeof document&&document.currentScript&&(U=document.currentScript.src),h&&(U=h),U=0!==U.indexOf("blob:")?U.substr(0,U.replace(/[?#].*/,"").lastIndexOf("/")+1):"",Wa=function(e){var b=new XMLHttpRequest;b.open("GET",
|
||||||
|
e,!1);b.send(null);return b.responseText},fa&&(ma=function(e){var b=new XMLHttpRequest;b.open("GET",e,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),na=function(e,b,c){var d=new XMLHttpRequest;d.open("GET",e,!0);d.responseType="arraybuffer";d.onload=function(){200==d.status||0==d.status&&d.response?b(d.response):c()};d.onerror=c;d.send(null)};a.print||console.log.bind(console);var da=a.printErr||console.warn.bind(console);Object.assign(a,Ta);Ta=null;var ea;a.wasmBinary&&
|
||||||
|
(ea=a.wasmBinary);"object"!=typeof WebAssembly&&y("no native wasm support detected");var ja,sa=!1,ra="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0,W,ia,ca,Y,ya=[],oa=[],xa=[],va=!1,ba=0,qa=null,ha=null;var P="draco_decoder_gltf.wasm";P.startsWith("data:application/octet-stream;base64,")||(P=k(P));var pd=0,qd={b:function(e,b,c){(new A(e)).init(b,c);pd++;throw e;},a:function(){y("")},d:function(e,b,c){ia.copyWithin(e,b,b+c)},c:function(e){var b=ia.length;e>>>=0;if(2147483648<e)return!1;
|
||||||
|
for(var c=1;4>=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,e+100663296);var g=Math;d=Math.max(e,d);g=g.min.call(g,2147483648,d+(65536-d%65536)%65536);a:{d=ja.buffer;try{ja.grow(g-d.byteLength+65535>>>16);l();var t=1;break a}catch(aa){}t=void 0}if(t)return!0}return!1}};(function(){function e(g,t){a.asm=g.exports;ja=a.asm.e;l();oa.unshift(a.asm.f);ba--;a.monitorRunDependencies&&a.monitorRunDependencies(ba);0==ba&&(null!==qa&&(clearInterval(qa),qa=null),ha&&(g=ha,ha=null,g()))}function b(g){e(g.instance)}
|
||||||
|
function c(g){return q().then(function(t){return WebAssembly.instantiate(t,d)}).then(function(t){return t}).then(g,function(t){da("failed to asynchronously prepare wasm: "+t);y(t)})}var d={a:qd};ba++;a.monitorRunDependencies&&a.monitorRunDependencies(ba);if(a.instantiateWasm)try{return a.instantiateWasm(d,e)}catch(g){da("Module.instantiateWasm callback failed with error: "+g),ka(g)}(function(){return ea||"function"!=typeof WebAssembly.instantiateStreaming||P.startsWith("data:application/octet-stream;base64,")||
|
||||||
|
P.startsWith("file://")||Ua||"function"!=typeof fetch?c(b):fetch(P,{credentials:"same-origin"}).then(function(g){return WebAssembly.instantiateStreaming(g,d).then(b,function(t){da("wasm streaming compile failed: "+t);da("falling back to ArrayBuffer instantiation");return c(b)})})})().catch(ka);return{}})();var Xa=a._emscripten_bind_VoidPtr___destroy___0=function(){return(Xa=a._emscripten_bind_VoidPtr___destroy___0=a.asm.h).apply(null,arguments)},za=a._emscripten_bind_DecoderBuffer_DecoderBuffer_0=
|
||||||
|
function(){return(za=a._emscripten_bind_DecoderBuffer_DecoderBuffer_0=a.asm.i).apply(null,arguments)},Ya=a._emscripten_bind_DecoderBuffer_Init_2=function(){return(Ya=a._emscripten_bind_DecoderBuffer_Init_2=a.asm.j).apply(null,arguments)},Za=a._emscripten_bind_DecoderBuffer___destroy___0=function(){return(Za=a._emscripten_bind_DecoderBuffer___destroy___0=a.asm.k).apply(null,arguments)},Aa=a._emscripten_bind_AttributeTransformData_AttributeTransformData_0=function(){return(Aa=a._emscripten_bind_AttributeTransformData_AttributeTransformData_0=
|
||||||
|
a.asm.l).apply(null,arguments)},$a=a._emscripten_bind_AttributeTransformData_transform_type_0=function(){return($a=a._emscripten_bind_AttributeTransformData_transform_type_0=a.asm.m).apply(null,arguments)},ab=a._emscripten_bind_AttributeTransformData___destroy___0=function(){return(ab=a._emscripten_bind_AttributeTransformData___destroy___0=a.asm.n).apply(null,arguments)},Ba=a._emscripten_bind_GeometryAttribute_GeometryAttribute_0=function(){return(Ba=a._emscripten_bind_GeometryAttribute_GeometryAttribute_0=
|
||||||
|
a.asm.o).apply(null,arguments)},bb=a._emscripten_bind_GeometryAttribute___destroy___0=function(){return(bb=a._emscripten_bind_GeometryAttribute___destroy___0=a.asm.p).apply(null,arguments)},Ca=a._emscripten_bind_PointAttribute_PointAttribute_0=function(){return(Ca=a._emscripten_bind_PointAttribute_PointAttribute_0=a.asm.q).apply(null,arguments)},cb=a._emscripten_bind_PointAttribute_size_0=function(){return(cb=a._emscripten_bind_PointAttribute_size_0=a.asm.r).apply(null,arguments)},db=a._emscripten_bind_PointAttribute_GetAttributeTransformData_0=
|
||||||
|
function(){return(db=a._emscripten_bind_PointAttribute_GetAttributeTransformData_0=a.asm.s).apply(null,arguments)},eb=a._emscripten_bind_PointAttribute_attribute_type_0=function(){return(eb=a._emscripten_bind_PointAttribute_attribute_type_0=a.asm.t).apply(null,arguments)},fb=a._emscripten_bind_PointAttribute_data_type_0=function(){return(fb=a._emscripten_bind_PointAttribute_data_type_0=a.asm.u).apply(null,arguments)},gb=a._emscripten_bind_PointAttribute_num_components_0=function(){return(gb=a._emscripten_bind_PointAttribute_num_components_0=
|
||||||
|
a.asm.v).apply(null,arguments)},hb=a._emscripten_bind_PointAttribute_normalized_0=function(){return(hb=a._emscripten_bind_PointAttribute_normalized_0=a.asm.w).apply(null,arguments)},ib=a._emscripten_bind_PointAttribute_byte_stride_0=function(){return(ib=a._emscripten_bind_PointAttribute_byte_stride_0=a.asm.x).apply(null,arguments)},jb=a._emscripten_bind_PointAttribute_byte_offset_0=function(){return(jb=a._emscripten_bind_PointAttribute_byte_offset_0=a.asm.y).apply(null,arguments)},kb=a._emscripten_bind_PointAttribute_unique_id_0=
|
||||||
|
function(){return(kb=a._emscripten_bind_PointAttribute_unique_id_0=a.asm.z).apply(null,arguments)},lb=a._emscripten_bind_PointAttribute___destroy___0=function(){return(lb=a._emscripten_bind_PointAttribute___destroy___0=a.asm.A).apply(null,arguments)},Da=a._emscripten_bind_AttributeQuantizationTransform_AttributeQuantizationTransform_0=function(){return(Da=a._emscripten_bind_AttributeQuantizationTransform_AttributeQuantizationTransform_0=a.asm.B).apply(null,arguments)},mb=a._emscripten_bind_AttributeQuantizationTransform_InitFromAttribute_1=
|
||||||
|
function(){return(mb=a._emscripten_bind_AttributeQuantizationTransform_InitFromAttribute_1=a.asm.C).apply(null,arguments)},nb=a._emscripten_bind_AttributeQuantizationTransform_quantization_bits_0=function(){return(nb=a._emscripten_bind_AttributeQuantizationTransform_quantization_bits_0=a.asm.D).apply(null,arguments)},ob=a._emscripten_bind_AttributeQuantizationTransform_min_value_1=function(){return(ob=a._emscripten_bind_AttributeQuantizationTransform_min_value_1=a.asm.E).apply(null,arguments)},pb=
|
||||||
|
a._emscripten_bind_AttributeQuantizationTransform_range_0=function(){return(pb=a._emscripten_bind_AttributeQuantizationTransform_range_0=a.asm.F).apply(null,arguments)},qb=a._emscripten_bind_AttributeQuantizationTransform___destroy___0=function(){return(qb=a._emscripten_bind_AttributeQuantizationTransform___destroy___0=a.asm.G).apply(null,arguments)},Ea=a._emscripten_bind_AttributeOctahedronTransform_AttributeOctahedronTransform_0=function(){return(Ea=a._emscripten_bind_AttributeOctahedronTransform_AttributeOctahedronTransform_0=
|
||||||
|
a.asm.H).apply(null,arguments)},rb=a._emscripten_bind_AttributeOctahedronTransform_InitFromAttribute_1=function(){return(rb=a._emscripten_bind_AttributeOctahedronTransform_InitFromAttribute_1=a.asm.I).apply(null,arguments)},sb=a._emscripten_bind_AttributeOctahedronTransform_quantization_bits_0=function(){return(sb=a._emscripten_bind_AttributeOctahedronTransform_quantization_bits_0=a.asm.J).apply(null,arguments)},tb=a._emscripten_bind_AttributeOctahedronTransform___destroy___0=function(){return(tb=
|
||||||
|
a._emscripten_bind_AttributeOctahedronTransform___destroy___0=a.asm.K).apply(null,arguments)},Fa=a._emscripten_bind_PointCloud_PointCloud_0=function(){return(Fa=a._emscripten_bind_PointCloud_PointCloud_0=a.asm.L).apply(null,arguments)},ub=a._emscripten_bind_PointCloud_num_attributes_0=function(){return(ub=a._emscripten_bind_PointCloud_num_attributes_0=a.asm.M).apply(null,arguments)},vb=a._emscripten_bind_PointCloud_num_points_0=function(){return(vb=a._emscripten_bind_PointCloud_num_points_0=a.asm.N).apply(null,
|
||||||
|
arguments)},wb=a._emscripten_bind_PointCloud___destroy___0=function(){return(wb=a._emscripten_bind_PointCloud___destroy___0=a.asm.O).apply(null,arguments)},Ga=a._emscripten_bind_Mesh_Mesh_0=function(){return(Ga=a._emscripten_bind_Mesh_Mesh_0=a.asm.P).apply(null,arguments)},xb=a._emscripten_bind_Mesh_num_faces_0=function(){return(xb=a._emscripten_bind_Mesh_num_faces_0=a.asm.Q).apply(null,arguments)},yb=a._emscripten_bind_Mesh_num_attributes_0=function(){return(yb=a._emscripten_bind_Mesh_num_attributes_0=
|
||||||
|
a.asm.R).apply(null,arguments)},zb=a._emscripten_bind_Mesh_num_points_0=function(){return(zb=a._emscripten_bind_Mesh_num_points_0=a.asm.S).apply(null,arguments)},Ab=a._emscripten_bind_Mesh___destroy___0=function(){return(Ab=a._emscripten_bind_Mesh___destroy___0=a.asm.T).apply(null,arguments)},Ha=a._emscripten_bind_Metadata_Metadata_0=function(){return(Ha=a._emscripten_bind_Metadata_Metadata_0=a.asm.U).apply(null,arguments)},Bb=a._emscripten_bind_Metadata___destroy___0=function(){return(Bb=a._emscripten_bind_Metadata___destroy___0=
|
||||||
|
a.asm.V).apply(null,arguments)},Cb=a._emscripten_bind_Status_code_0=function(){return(Cb=a._emscripten_bind_Status_code_0=a.asm.W).apply(null,arguments)},Db=a._emscripten_bind_Status_ok_0=function(){return(Db=a._emscripten_bind_Status_ok_0=a.asm.X).apply(null,arguments)},Eb=a._emscripten_bind_Status_error_msg_0=function(){return(Eb=a._emscripten_bind_Status_error_msg_0=a.asm.Y).apply(null,arguments)},Fb=a._emscripten_bind_Status___destroy___0=function(){return(Fb=a._emscripten_bind_Status___destroy___0=
|
||||||
|
a.asm.Z).apply(null,arguments)},Ia=a._emscripten_bind_DracoFloat32Array_DracoFloat32Array_0=function(){return(Ia=a._emscripten_bind_DracoFloat32Array_DracoFloat32Array_0=a.asm._).apply(null,arguments)},Gb=a._emscripten_bind_DracoFloat32Array_GetValue_1=function(){return(Gb=a._emscripten_bind_DracoFloat32Array_GetValue_1=a.asm.$).apply(null,arguments)},Hb=a._emscripten_bind_DracoFloat32Array_size_0=function(){return(Hb=a._emscripten_bind_DracoFloat32Array_size_0=a.asm.aa).apply(null,arguments)},Ib=
|
||||||
|
a._emscripten_bind_DracoFloat32Array___destroy___0=function(){return(Ib=a._emscripten_bind_DracoFloat32Array___destroy___0=a.asm.ba).apply(null,arguments)},Ja=a._emscripten_bind_DracoInt8Array_DracoInt8Array_0=function(){return(Ja=a._emscripten_bind_DracoInt8Array_DracoInt8Array_0=a.asm.ca).apply(null,arguments)},Jb=a._emscripten_bind_DracoInt8Array_GetValue_1=function(){return(Jb=a._emscripten_bind_DracoInt8Array_GetValue_1=a.asm.da).apply(null,arguments)},Kb=a._emscripten_bind_DracoInt8Array_size_0=
|
||||||
|
function(){return(Kb=a._emscripten_bind_DracoInt8Array_size_0=a.asm.ea).apply(null,arguments)},Lb=a._emscripten_bind_DracoInt8Array___destroy___0=function(){return(Lb=a._emscripten_bind_DracoInt8Array___destroy___0=a.asm.fa).apply(null,arguments)},Ka=a._emscripten_bind_DracoUInt8Array_DracoUInt8Array_0=function(){return(Ka=a._emscripten_bind_DracoUInt8Array_DracoUInt8Array_0=a.asm.ga).apply(null,arguments)},Mb=a._emscripten_bind_DracoUInt8Array_GetValue_1=function(){return(Mb=a._emscripten_bind_DracoUInt8Array_GetValue_1=
|
||||||
|
a.asm.ha).apply(null,arguments)},Nb=a._emscripten_bind_DracoUInt8Array_size_0=function(){return(Nb=a._emscripten_bind_DracoUInt8Array_size_0=a.asm.ia).apply(null,arguments)},Ob=a._emscripten_bind_DracoUInt8Array___destroy___0=function(){return(Ob=a._emscripten_bind_DracoUInt8Array___destroy___0=a.asm.ja).apply(null,arguments)},La=a._emscripten_bind_DracoInt16Array_DracoInt16Array_0=function(){return(La=a._emscripten_bind_DracoInt16Array_DracoInt16Array_0=a.asm.ka).apply(null,arguments)},Pb=a._emscripten_bind_DracoInt16Array_GetValue_1=
|
||||||
|
function(){return(Pb=a._emscripten_bind_DracoInt16Array_GetValue_1=a.asm.la).apply(null,arguments)},Qb=a._emscripten_bind_DracoInt16Array_size_0=function(){return(Qb=a._emscripten_bind_DracoInt16Array_size_0=a.asm.ma).apply(null,arguments)},Rb=a._emscripten_bind_DracoInt16Array___destroy___0=function(){return(Rb=a._emscripten_bind_DracoInt16Array___destroy___0=a.asm.na).apply(null,arguments)},Ma=a._emscripten_bind_DracoUInt16Array_DracoUInt16Array_0=function(){return(Ma=a._emscripten_bind_DracoUInt16Array_DracoUInt16Array_0=
|
||||||
|
a.asm.oa).apply(null,arguments)},Sb=a._emscripten_bind_DracoUInt16Array_GetValue_1=function(){return(Sb=a._emscripten_bind_DracoUInt16Array_GetValue_1=a.asm.pa).apply(null,arguments)},Tb=a._emscripten_bind_DracoUInt16Array_size_0=function(){return(Tb=a._emscripten_bind_DracoUInt16Array_size_0=a.asm.qa).apply(null,arguments)},Ub=a._emscripten_bind_DracoUInt16Array___destroy___0=function(){return(Ub=a._emscripten_bind_DracoUInt16Array___destroy___0=a.asm.ra).apply(null,arguments)},Na=a._emscripten_bind_DracoInt32Array_DracoInt32Array_0=
|
||||||
|
function(){return(Na=a._emscripten_bind_DracoInt32Array_DracoInt32Array_0=a.asm.sa).apply(null,arguments)},Vb=a._emscripten_bind_DracoInt32Array_GetValue_1=function(){return(Vb=a._emscripten_bind_DracoInt32Array_GetValue_1=a.asm.ta).apply(null,arguments)},Wb=a._emscripten_bind_DracoInt32Array_size_0=function(){return(Wb=a._emscripten_bind_DracoInt32Array_size_0=a.asm.ua).apply(null,arguments)},Xb=a._emscripten_bind_DracoInt32Array___destroy___0=function(){return(Xb=a._emscripten_bind_DracoInt32Array___destroy___0=
|
||||||
|
a.asm.va).apply(null,arguments)},Oa=a._emscripten_bind_DracoUInt32Array_DracoUInt32Array_0=function(){return(Oa=a._emscripten_bind_DracoUInt32Array_DracoUInt32Array_0=a.asm.wa).apply(null,arguments)},Yb=a._emscripten_bind_DracoUInt32Array_GetValue_1=function(){return(Yb=a._emscripten_bind_DracoUInt32Array_GetValue_1=a.asm.xa).apply(null,arguments)},Zb=a._emscripten_bind_DracoUInt32Array_size_0=function(){return(Zb=a._emscripten_bind_DracoUInt32Array_size_0=a.asm.ya).apply(null,arguments)},$b=a._emscripten_bind_DracoUInt32Array___destroy___0=
|
||||||
|
function(){return($b=a._emscripten_bind_DracoUInt32Array___destroy___0=a.asm.za).apply(null,arguments)},Pa=a._emscripten_bind_MetadataQuerier_MetadataQuerier_0=function(){return(Pa=a._emscripten_bind_MetadataQuerier_MetadataQuerier_0=a.asm.Aa).apply(null,arguments)},ac=a._emscripten_bind_MetadataQuerier_HasEntry_2=function(){return(ac=a._emscripten_bind_MetadataQuerier_HasEntry_2=a.asm.Ba).apply(null,arguments)},bc=a._emscripten_bind_MetadataQuerier_GetIntEntry_2=function(){return(bc=a._emscripten_bind_MetadataQuerier_GetIntEntry_2=
|
||||||
|
a.asm.Ca).apply(null,arguments)},cc=a._emscripten_bind_MetadataQuerier_GetIntEntryArray_3=function(){return(cc=a._emscripten_bind_MetadataQuerier_GetIntEntryArray_3=a.asm.Da).apply(null,arguments)},dc=a._emscripten_bind_MetadataQuerier_GetDoubleEntry_2=function(){return(dc=a._emscripten_bind_MetadataQuerier_GetDoubleEntry_2=a.asm.Ea).apply(null,arguments)},ec=a._emscripten_bind_MetadataQuerier_GetStringEntry_2=function(){return(ec=a._emscripten_bind_MetadataQuerier_GetStringEntry_2=a.asm.Fa).apply(null,
|
||||||
|
arguments)},fc=a._emscripten_bind_MetadataQuerier_NumEntries_1=function(){return(fc=a._emscripten_bind_MetadataQuerier_NumEntries_1=a.asm.Ga).apply(null,arguments)},gc=a._emscripten_bind_MetadataQuerier_GetEntryName_2=function(){return(gc=a._emscripten_bind_MetadataQuerier_GetEntryName_2=a.asm.Ha).apply(null,arguments)},hc=a._emscripten_bind_MetadataQuerier___destroy___0=function(){return(hc=a._emscripten_bind_MetadataQuerier___destroy___0=a.asm.Ia).apply(null,arguments)},Qa=a._emscripten_bind_Decoder_Decoder_0=
|
||||||
|
function(){return(Qa=a._emscripten_bind_Decoder_Decoder_0=a.asm.Ja).apply(null,arguments)},ic=a._emscripten_bind_Decoder_DecodeArrayToPointCloud_3=function(){return(ic=a._emscripten_bind_Decoder_DecodeArrayToPointCloud_3=a.asm.Ka).apply(null,arguments)},jc=a._emscripten_bind_Decoder_DecodeArrayToMesh_3=function(){return(jc=a._emscripten_bind_Decoder_DecodeArrayToMesh_3=a.asm.La).apply(null,arguments)},kc=a._emscripten_bind_Decoder_GetAttributeId_2=function(){return(kc=a._emscripten_bind_Decoder_GetAttributeId_2=
|
||||||
|
a.asm.Ma).apply(null,arguments)},lc=a._emscripten_bind_Decoder_GetAttributeIdByName_2=function(){return(lc=a._emscripten_bind_Decoder_GetAttributeIdByName_2=a.asm.Na).apply(null,arguments)},mc=a._emscripten_bind_Decoder_GetAttributeIdByMetadataEntry_3=function(){return(mc=a._emscripten_bind_Decoder_GetAttributeIdByMetadataEntry_3=a.asm.Oa).apply(null,arguments)},nc=a._emscripten_bind_Decoder_GetAttribute_2=function(){return(nc=a._emscripten_bind_Decoder_GetAttribute_2=a.asm.Pa).apply(null,arguments)},
|
||||||
|
oc=a._emscripten_bind_Decoder_GetAttributeByUniqueId_2=function(){return(oc=a._emscripten_bind_Decoder_GetAttributeByUniqueId_2=a.asm.Qa).apply(null,arguments)},pc=a._emscripten_bind_Decoder_GetMetadata_1=function(){return(pc=a._emscripten_bind_Decoder_GetMetadata_1=a.asm.Ra).apply(null,arguments)},qc=a._emscripten_bind_Decoder_GetAttributeMetadata_2=function(){return(qc=a._emscripten_bind_Decoder_GetAttributeMetadata_2=a.asm.Sa).apply(null,arguments)},rc=a._emscripten_bind_Decoder_GetFaceFromMesh_3=
|
||||||
|
function(){return(rc=a._emscripten_bind_Decoder_GetFaceFromMesh_3=a.asm.Ta).apply(null,arguments)},sc=a._emscripten_bind_Decoder_GetTriangleStripsFromMesh_2=function(){return(sc=a._emscripten_bind_Decoder_GetTriangleStripsFromMesh_2=a.asm.Ua).apply(null,arguments)},tc=a._emscripten_bind_Decoder_GetTrianglesUInt16Array_3=function(){return(tc=a._emscripten_bind_Decoder_GetTrianglesUInt16Array_3=a.asm.Va).apply(null,arguments)},uc=a._emscripten_bind_Decoder_GetTrianglesUInt32Array_3=function(){return(uc=
|
||||||
|
a._emscripten_bind_Decoder_GetTrianglesUInt32Array_3=a.asm.Wa).apply(null,arguments)},vc=a._emscripten_bind_Decoder_GetAttributeFloat_3=function(){return(vc=a._emscripten_bind_Decoder_GetAttributeFloat_3=a.asm.Xa).apply(null,arguments)},wc=a._emscripten_bind_Decoder_GetAttributeFloatForAllPoints_3=function(){return(wc=a._emscripten_bind_Decoder_GetAttributeFloatForAllPoints_3=a.asm.Ya).apply(null,arguments)},xc=a._emscripten_bind_Decoder_GetAttributeIntForAllPoints_3=function(){return(xc=a._emscripten_bind_Decoder_GetAttributeIntForAllPoints_3=
|
||||||
|
a.asm.Za).apply(null,arguments)},yc=a._emscripten_bind_Decoder_GetAttributeInt8ForAllPoints_3=function(){return(yc=a._emscripten_bind_Decoder_GetAttributeInt8ForAllPoints_3=a.asm._a).apply(null,arguments)},zc=a._emscripten_bind_Decoder_GetAttributeUInt8ForAllPoints_3=function(){return(zc=a._emscripten_bind_Decoder_GetAttributeUInt8ForAllPoints_3=a.asm.$a).apply(null,arguments)},Ac=a._emscripten_bind_Decoder_GetAttributeInt16ForAllPoints_3=function(){return(Ac=a._emscripten_bind_Decoder_GetAttributeInt16ForAllPoints_3=
|
||||||
|
a.asm.ab).apply(null,arguments)},Bc=a._emscripten_bind_Decoder_GetAttributeUInt16ForAllPoints_3=function(){return(Bc=a._emscripten_bind_Decoder_GetAttributeUInt16ForAllPoints_3=a.asm.bb).apply(null,arguments)},Cc=a._emscripten_bind_Decoder_GetAttributeInt32ForAllPoints_3=function(){return(Cc=a._emscripten_bind_Decoder_GetAttributeInt32ForAllPoints_3=a.asm.cb).apply(null,arguments)},Dc=a._emscripten_bind_Decoder_GetAttributeUInt32ForAllPoints_3=function(){return(Dc=a._emscripten_bind_Decoder_GetAttributeUInt32ForAllPoints_3=
|
||||||
|
a.asm.db).apply(null,arguments)},Ec=a._emscripten_bind_Decoder_GetAttributeDataArrayForAllPoints_5=function(){return(Ec=a._emscripten_bind_Decoder_GetAttributeDataArrayForAllPoints_5=a.asm.eb).apply(null,arguments)},Fc=a._emscripten_bind_Decoder_SkipAttributeTransform_1=function(){return(Fc=a._emscripten_bind_Decoder_SkipAttributeTransform_1=a.asm.fb).apply(null,arguments)},Gc=a._emscripten_bind_Decoder_GetEncodedGeometryType_Deprecated_1=function(){return(Gc=a._emscripten_bind_Decoder_GetEncodedGeometryType_Deprecated_1=
|
||||||
|
a.asm.gb).apply(null,arguments)},Hc=a._emscripten_bind_Decoder_DecodeBufferToPointCloud_2=function(){return(Hc=a._emscripten_bind_Decoder_DecodeBufferToPointCloud_2=a.asm.hb).apply(null,arguments)},Ic=a._emscripten_bind_Decoder_DecodeBufferToMesh_2=function(){return(Ic=a._emscripten_bind_Decoder_DecodeBufferToMesh_2=a.asm.ib).apply(null,arguments)},Jc=a._emscripten_bind_Decoder___destroy___0=function(){return(Jc=a._emscripten_bind_Decoder___destroy___0=a.asm.jb).apply(null,arguments)},Kc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_INVALID_TRANSFORM=
|
||||||
|
function(){return(Kc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_INVALID_TRANSFORM=a.asm.kb).apply(null,arguments)},Lc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_NO_TRANSFORM=function(){return(Lc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_NO_TRANSFORM=a.asm.lb).apply(null,arguments)},Mc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_QUANTIZATION_TRANSFORM=function(){return(Mc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_QUANTIZATION_TRANSFORM=
|
||||||
|
a.asm.mb).apply(null,arguments)},Nc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_OCTAHEDRON_TRANSFORM=function(){return(Nc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_OCTAHEDRON_TRANSFORM=a.asm.nb).apply(null,arguments)},Oc=a._emscripten_enum_draco_GeometryAttribute_Type_INVALID=function(){return(Oc=a._emscripten_enum_draco_GeometryAttribute_Type_INVALID=a.asm.ob).apply(null,arguments)},Pc=a._emscripten_enum_draco_GeometryAttribute_Type_POSITION=function(){return(Pc=a._emscripten_enum_draco_GeometryAttribute_Type_POSITION=
|
||||||
|
a.asm.pb).apply(null,arguments)},Qc=a._emscripten_enum_draco_GeometryAttribute_Type_NORMAL=function(){return(Qc=a._emscripten_enum_draco_GeometryAttribute_Type_NORMAL=a.asm.qb).apply(null,arguments)},Rc=a._emscripten_enum_draco_GeometryAttribute_Type_COLOR=function(){return(Rc=a._emscripten_enum_draco_GeometryAttribute_Type_COLOR=a.asm.rb).apply(null,arguments)},Sc=a._emscripten_enum_draco_GeometryAttribute_Type_TEX_COORD=function(){return(Sc=a._emscripten_enum_draco_GeometryAttribute_Type_TEX_COORD=
|
||||||
|
a.asm.sb).apply(null,arguments)},Tc=a._emscripten_enum_draco_GeometryAttribute_Type_GENERIC=function(){return(Tc=a._emscripten_enum_draco_GeometryAttribute_Type_GENERIC=a.asm.tb).apply(null,arguments)},Uc=a._emscripten_enum_draco_EncodedGeometryType_INVALID_GEOMETRY_TYPE=function(){return(Uc=a._emscripten_enum_draco_EncodedGeometryType_INVALID_GEOMETRY_TYPE=a.asm.ub).apply(null,arguments)},Vc=a._emscripten_enum_draco_EncodedGeometryType_POINT_CLOUD=function(){return(Vc=a._emscripten_enum_draco_EncodedGeometryType_POINT_CLOUD=
|
||||||
|
a.asm.vb).apply(null,arguments)},Wc=a._emscripten_enum_draco_EncodedGeometryType_TRIANGULAR_MESH=function(){return(Wc=a._emscripten_enum_draco_EncodedGeometryType_TRIANGULAR_MESH=a.asm.wb).apply(null,arguments)},Xc=a._emscripten_enum_draco_DataType_DT_INVALID=function(){return(Xc=a._emscripten_enum_draco_DataType_DT_INVALID=a.asm.xb).apply(null,arguments)},Yc=a._emscripten_enum_draco_DataType_DT_INT8=function(){return(Yc=a._emscripten_enum_draco_DataType_DT_INT8=a.asm.yb).apply(null,arguments)},Zc=
|
||||||
|
a._emscripten_enum_draco_DataType_DT_UINT8=function(){return(Zc=a._emscripten_enum_draco_DataType_DT_UINT8=a.asm.zb).apply(null,arguments)},$c=a._emscripten_enum_draco_DataType_DT_INT16=function(){return($c=a._emscripten_enum_draco_DataType_DT_INT16=a.asm.Ab).apply(null,arguments)},ad=a._emscripten_enum_draco_DataType_DT_UINT16=function(){return(ad=a._emscripten_enum_draco_DataType_DT_UINT16=a.asm.Bb).apply(null,arguments)},bd=a._emscripten_enum_draco_DataType_DT_INT32=function(){return(bd=a._emscripten_enum_draco_DataType_DT_INT32=
|
||||||
|
a.asm.Cb).apply(null,arguments)},cd=a._emscripten_enum_draco_DataType_DT_UINT32=function(){return(cd=a._emscripten_enum_draco_DataType_DT_UINT32=a.asm.Db).apply(null,arguments)},dd=a._emscripten_enum_draco_DataType_DT_INT64=function(){return(dd=a._emscripten_enum_draco_DataType_DT_INT64=a.asm.Eb).apply(null,arguments)},ed=a._emscripten_enum_draco_DataType_DT_UINT64=function(){return(ed=a._emscripten_enum_draco_DataType_DT_UINT64=a.asm.Fb).apply(null,arguments)},fd=a._emscripten_enum_draco_DataType_DT_FLOAT32=
|
||||||
|
function(){return(fd=a._emscripten_enum_draco_DataType_DT_FLOAT32=a.asm.Gb).apply(null,arguments)},gd=a._emscripten_enum_draco_DataType_DT_FLOAT64=function(){return(gd=a._emscripten_enum_draco_DataType_DT_FLOAT64=a.asm.Hb).apply(null,arguments)},hd=a._emscripten_enum_draco_DataType_DT_BOOL=function(){return(hd=a._emscripten_enum_draco_DataType_DT_BOOL=a.asm.Ib).apply(null,arguments)},id=a._emscripten_enum_draco_DataType_DT_TYPES_COUNT=function(){return(id=a._emscripten_enum_draco_DataType_DT_TYPES_COUNT=
|
||||||
|
a.asm.Jb).apply(null,arguments)},jd=a._emscripten_enum_draco_StatusCode_OK=function(){return(jd=a._emscripten_enum_draco_StatusCode_OK=a.asm.Kb).apply(null,arguments)},kd=a._emscripten_enum_draco_StatusCode_DRACO_ERROR=function(){return(kd=a._emscripten_enum_draco_StatusCode_DRACO_ERROR=a.asm.Lb).apply(null,arguments)},ld=a._emscripten_enum_draco_StatusCode_IO_ERROR=function(){return(ld=a._emscripten_enum_draco_StatusCode_IO_ERROR=a.asm.Mb).apply(null,arguments)},md=a._emscripten_enum_draco_StatusCode_INVALID_PARAMETER=
|
||||||
|
function(){return(md=a._emscripten_enum_draco_StatusCode_INVALID_PARAMETER=a.asm.Nb).apply(null,arguments)},nd=a._emscripten_enum_draco_StatusCode_UNSUPPORTED_VERSION=function(){return(nd=a._emscripten_enum_draco_StatusCode_UNSUPPORTED_VERSION=a.asm.Ob).apply(null,arguments)},od=a._emscripten_enum_draco_StatusCode_UNKNOWN_VERSION=function(){return(od=a._emscripten_enum_draco_StatusCode_UNKNOWN_VERSION=a.asm.Pb).apply(null,arguments)};a._malloc=function(){return(a._malloc=a.asm.Qb).apply(null,arguments)};
|
||||||
|
a._free=function(){return(a._free=a.asm.Rb).apply(null,arguments)};var ua=function(){return(ua=a.asm.Sb).apply(null,arguments)};a.___start_em_js=11660;a.___stop_em_js=11758;var la;ha=function b(){la||F();la||(ha=b)};if(a.preInit)for("function"==typeof a.preInit&&(a.preInit=[a.preInit]);0<a.preInit.length;)a.preInit.pop()();F();v.prototype=Object.create(v.prototype);v.prototype.constructor=v;v.prototype.__class__=v;v.__cache__={};a.WrapperObject=v;a.getCache=w;a.wrapPointer=B;a.castObject=function(b,
|
||||||
|
c){return B(b.ptr,c)};a.NULL=B(0);a.destroy=function(b){if(!b.__destroy__)throw"Error: Cannot destroy object. (Did you create it yourself?)";b.__destroy__();delete w(b.__class__)[b.ptr]};a.compare=function(b,c){return b.ptr===c.ptr};a.getPointer=function(b){return b.ptr};a.getClass=function(b){return b.__class__};var r={buffer:0,size:0,pos:0,temps:[],needed:0,prepare:function(){if(r.needed){for(var b=0;b<r.temps.length;b++)a._free(r.temps[b]);r.temps.length=0;a._free(r.buffer);r.buffer=0;r.size+=
|
||||||
|
r.needed;r.needed=0}r.buffer||(r.size+=128,r.buffer=a._malloc(r.size),r.buffer||y(void 0));r.pos=0},alloc:function(b,c){r.buffer||y(void 0);b=b.length*c.BYTES_PER_ELEMENT;b=b+7&-8;r.pos+b>=r.size?(0<b||y(void 0),r.needed+=b,c=a._malloc(b),r.temps.push(c)):(c=r.buffer+r.pos,r.pos+=b);return c},copy:function(b,c,d){d>>>=0;switch(c.BYTES_PER_ELEMENT){case 2:d>>>=1;break;case 4:d>>>=2;break;case 8:d>>>=3}for(var g=0;g<b.length;g++)c[d+g]=b[g]}};X.prototype=Object.create(v.prototype);X.prototype.constructor=
|
||||||
|
X;X.prototype.__class__=X;X.__cache__={};a.VoidPtr=X;X.prototype.__destroy__=X.prototype.__destroy__=function(){Xa(this.ptr)};S.prototype=Object.create(v.prototype);S.prototype.constructor=S;S.prototype.__class__=S;S.__cache__={};a.DecoderBuffer=S;S.prototype.Init=S.prototype.Init=function(b,c){var d=this.ptr;r.prepare();"object"==typeof b&&(b=Z(b));c&&"object"===typeof c&&(c=c.ptr);Ya(d,b,c)};S.prototype.__destroy__=S.prototype.__destroy__=function(){Za(this.ptr)};Q.prototype=Object.create(v.prototype);
|
||||||
|
Q.prototype.constructor=Q;Q.prototype.__class__=Q;Q.__cache__={};a.AttributeTransformData=Q;Q.prototype.transform_type=Q.prototype.transform_type=function(){return $a(this.ptr)};Q.prototype.__destroy__=Q.prototype.__destroy__=function(){ab(this.ptr)};V.prototype=Object.create(v.prototype);V.prototype.constructor=V;V.prototype.__class__=V;V.__cache__={};a.GeometryAttribute=V;V.prototype.__destroy__=V.prototype.__destroy__=function(){bb(this.ptr)};x.prototype=Object.create(v.prototype);x.prototype.constructor=
|
||||||
|
x;x.prototype.__class__=x;x.__cache__={};a.PointAttribute=x;x.prototype.size=x.prototype.size=function(){return cb(this.ptr)};x.prototype.GetAttributeTransformData=x.prototype.GetAttributeTransformData=function(){return B(db(this.ptr),Q)};x.prototype.attribute_type=x.prototype.attribute_type=function(){return eb(this.ptr)};x.prototype.data_type=x.prototype.data_type=function(){return fb(this.ptr)};x.prototype.num_components=x.prototype.num_components=function(){return gb(this.ptr)};x.prototype.normalized=
|
||||||
|
x.prototype.normalized=function(){return!!hb(this.ptr)};x.prototype.byte_stride=x.prototype.byte_stride=function(){return ib(this.ptr)};x.prototype.byte_offset=x.prototype.byte_offset=function(){return jb(this.ptr)};x.prototype.unique_id=x.prototype.unique_id=function(){return kb(this.ptr)};x.prototype.__destroy__=x.prototype.__destroy__=function(){lb(this.ptr)};D.prototype=Object.create(v.prototype);D.prototype.constructor=D;D.prototype.__class__=D;D.__cache__={};a.AttributeQuantizationTransform=
|
||||||
|
D;D.prototype.InitFromAttribute=D.prototype.InitFromAttribute=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return!!mb(c,b)};D.prototype.quantization_bits=D.prototype.quantization_bits=function(){return nb(this.ptr)};D.prototype.min_value=D.prototype.min_value=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return ob(c,b)};D.prototype.range=D.prototype.range=function(){return pb(this.ptr)};D.prototype.__destroy__=D.prototype.__destroy__=function(){qb(this.ptr)};G.prototype=
|
||||||
|
Object.create(v.prototype);G.prototype.constructor=G;G.prototype.__class__=G;G.__cache__={};a.AttributeOctahedronTransform=G;G.prototype.InitFromAttribute=G.prototype.InitFromAttribute=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return!!rb(c,b)};G.prototype.quantization_bits=G.prototype.quantization_bits=function(){return sb(this.ptr)};G.prototype.__destroy__=G.prototype.__destroy__=function(){tb(this.ptr)};H.prototype=Object.create(v.prototype);H.prototype.constructor=H;H.prototype.__class__=
|
||||||
|
H;H.__cache__={};a.PointCloud=H;H.prototype.num_attributes=H.prototype.num_attributes=function(){return ub(this.ptr)};H.prototype.num_points=H.prototype.num_points=function(){return vb(this.ptr)};H.prototype.__destroy__=H.prototype.__destroy__=function(){wb(this.ptr)};E.prototype=Object.create(v.prototype);E.prototype.constructor=E;E.prototype.__class__=E;E.__cache__={};a.Mesh=E;E.prototype.num_faces=E.prototype.num_faces=function(){return xb(this.ptr)};E.prototype.num_attributes=E.prototype.num_attributes=
|
||||||
|
function(){return yb(this.ptr)};E.prototype.num_points=E.prototype.num_points=function(){return zb(this.ptr)};E.prototype.__destroy__=E.prototype.__destroy__=function(){Ab(this.ptr)};T.prototype=Object.create(v.prototype);T.prototype.constructor=T;T.prototype.__class__=T;T.__cache__={};a.Metadata=T;T.prototype.__destroy__=T.prototype.__destroy__=function(){Bb(this.ptr)};C.prototype=Object.create(v.prototype);C.prototype.constructor=C;C.prototype.__class__=C;C.__cache__={};a.Status=C;C.prototype.code=
|
||||||
|
C.prototype.code=function(){return Cb(this.ptr)};C.prototype.ok=C.prototype.ok=function(){return!!Db(this.ptr)};C.prototype.error_msg=C.prototype.error_msg=function(){return p(Eb(this.ptr))};C.prototype.__destroy__=C.prototype.__destroy__=function(){Fb(this.ptr)};I.prototype=Object.create(v.prototype);I.prototype.constructor=I;I.prototype.__class__=I;I.__cache__={};a.DracoFloat32Array=I;I.prototype.GetValue=I.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Gb(c,
|
||||||
|
b)};I.prototype.size=I.prototype.size=function(){return Hb(this.ptr)};I.prototype.__destroy__=I.prototype.__destroy__=function(){Ib(this.ptr)};J.prototype=Object.create(v.prototype);J.prototype.constructor=J;J.prototype.__class__=J;J.__cache__={};a.DracoInt8Array=J;J.prototype.GetValue=J.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Jb(c,b)};J.prototype.size=J.prototype.size=function(){return Kb(this.ptr)};J.prototype.__destroy__=J.prototype.__destroy__=function(){Lb(this.ptr)};
|
||||||
|
K.prototype=Object.create(v.prototype);K.prototype.constructor=K;K.prototype.__class__=K;K.__cache__={};a.DracoUInt8Array=K;K.prototype.GetValue=K.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Mb(c,b)};K.prototype.size=K.prototype.size=function(){return Nb(this.ptr)};K.prototype.__destroy__=K.prototype.__destroy__=function(){Ob(this.ptr)};L.prototype=Object.create(v.prototype);L.prototype.constructor=L;L.prototype.__class__=L;L.__cache__={};a.DracoInt16Array=
|
||||||
|
L;L.prototype.GetValue=L.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Pb(c,b)};L.prototype.size=L.prototype.size=function(){return Qb(this.ptr)};L.prototype.__destroy__=L.prototype.__destroy__=function(){Rb(this.ptr)};M.prototype=Object.create(v.prototype);M.prototype.constructor=M;M.prototype.__class__=M;M.__cache__={};a.DracoUInt16Array=M;M.prototype.GetValue=M.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Sb(c,b)};
|
||||||
|
M.prototype.size=M.prototype.size=function(){return Tb(this.ptr)};M.prototype.__destroy__=M.prototype.__destroy__=function(){Ub(this.ptr)};N.prototype=Object.create(v.prototype);N.prototype.constructor=N;N.prototype.__class__=N;N.__cache__={};a.DracoInt32Array=N;N.prototype.GetValue=N.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Vb(c,b)};N.prototype.size=N.prototype.size=function(){return Wb(this.ptr)};N.prototype.__destroy__=N.prototype.__destroy__=function(){Xb(this.ptr)};
|
||||||
|
O.prototype=Object.create(v.prototype);O.prototype.constructor=O;O.prototype.__class__=O;O.__cache__={};a.DracoUInt32Array=O;O.prototype.GetValue=O.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Yb(c,b)};O.prototype.size=O.prototype.size=function(){return Zb(this.ptr)};O.prototype.__destroy__=O.prototype.__destroy__=function(){$b(this.ptr)};z.prototype=Object.create(v.prototype);z.prototype.constructor=z;z.prototype.__class__=z;z.__cache__={};a.MetadataQuerier=
|
||||||
|
z;z.prototype.HasEntry=z.prototype.HasEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return!!ac(d,b,c)};z.prototype.GetIntEntry=z.prototype.GetIntEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return bc(d,b,c)};z.prototype.GetIntEntryArray=z.prototype.GetIntEntryArray=function(b,c,d){var g=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===
|
||||||
|
typeof c?c.ptr:R(c);d&&"object"===typeof d&&(d=d.ptr);cc(g,b,c,d)};z.prototype.GetDoubleEntry=z.prototype.GetDoubleEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return dc(d,b,c)};z.prototype.GetStringEntry=z.prototype.GetStringEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return p(ec(d,b,c))};z.prototype.NumEntries=z.prototype.NumEntries=function(b){var c=this.ptr;
|
||||||
|
b&&"object"===typeof b&&(b=b.ptr);return fc(c,b)};z.prototype.GetEntryName=z.prototype.GetEntryName=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return p(gc(d,b,c))};z.prototype.__destroy__=z.prototype.__destroy__=function(){hc(this.ptr)};m.prototype=Object.create(v.prototype);m.prototype.constructor=m;m.prototype.__class__=m;m.__cache__={};a.Decoder=m;m.prototype.DecodeArrayToPointCloud=m.prototype.DecodeArrayToPointCloud=function(b,c,d){var g=
|
||||||
|
this.ptr;r.prepare();"object"==typeof b&&(b=Z(b));c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return B(ic(g,b,c,d),C)};m.prototype.DecodeArrayToMesh=m.prototype.DecodeArrayToMesh=function(b,c,d){var g=this.ptr;r.prepare();"object"==typeof b&&(b=Z(b));c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return B(jc(g,b,c,d),C)};m.prototype.GetAttributeId=m.prototype.GetAttributeId=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&
|
||||||
|
(c=c.ptr);return kc(d,b,c)};m.prototype.GetAttributeIdByName=m.prototype.GetAttributeIdByName=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return lc(d,b,c)};m.prototype.GetAttributeIdByMetadataEntry=m.prototype.GetAttributeIdByMetadataEntry=function(b,c,d){var g=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);d=d&&"object"===typeof d?d.ptr:R(d);return mc(g,b,c,d)};m.prototype.GetAttribute=
|
||||||
|
m.prototype.GetAttribute=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return B(nc(d,b,c),x)};m.prototype.GetAttributeByUniqueId=m.prototype.GetAttributeByUniqueId=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return B(oc(d,b,c),x)};m.prototype.GetMetadata=m.prototype.GetMetadata=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return B(pc(c,b),T)};m.prototype.GetAttributeMetadata=m.prototype.GetAttributeMetadata=
|
||||||
|
function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return B(qc(d,b,c),T)};m.prototype.GetFaceFromMesh=m.prototype.GetFaceFromMesh=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!rc(g,b,c,d)};m.prototype.GetTriangleStripsFromMesh=m.prototype.GetTriangleStripsFromMesh=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);
|
||||||
|
return sc(d,b,c)};m.prototype.GetTrianglesUInt16Array=m.prototype.GetTrianglesUInt16Array=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!tc(g,b,c,d)};m.prototype.GetTrianglesUInt32Array=m.prototype.GetTrianglesUInt32Array=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!uc(g,b,c,d)};m.prototype.GetAttributeFloat=m.prototype.GetAttributeFloat=
|
||||||
|
function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!vc(g,b,c,d)};m.prototype.GetAttributeFloatForAllPoints=m.prototype.GetAttributeFloatForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!wc(g,b,c,d)};m.prototype.GetAttributeIntForAllPoints=m.prototype.GetAttributeIntForAllPoints=function(b,c,d){var g=this.ptr;
|
||||||
|
b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!xc(g,b,c,d)};m.prototype.GetAttributeInt8ForAllPoints=m.prototype.GetAttributeInt8ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!yc(g,b,c,d)};m.prototype.GetAttributeUInt8ForAllPoints=m.prototype.GetAttributeUInt8ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=
|
||||||
|
b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!zc(g,b,c,d)};m.prototype.GetAttributeInt16ForAllPoints=m.prototype.GetAttributeInt16ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Ac(g,b,c,d)};m.prototype.GetAttributeUInt16ForAllPoints=m.prototype.GetAttributeUInt16ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&
|
||||||
|
(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Bc(g,b,c,d)};m.prototype.GetAttributeInt32ForAllPoints=m.prototype.GetAttributeInt32ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Cc(g,b,c,d)};m.prototype.GetAttributeUInt32ForAllPoints=m.prototype.GetAttributeUInt32ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===
|
||||||
|
typeof d&&(d=d.ptr);return!!Dc(g,b,c,d)};m.prototype.GetAttributeDataArrayForAllPoints=m.prototype.GetAttributeDataArrayForAllPoints=function(b,c,d,g,t){var aa=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);g&&"object"===typeof g&&(g=g.ptr);t&&"object"===typeof t&&(t=t.ptr);return!!Ec(aa,b,c,d,g,t)};m.prototype.SkipAttributeTransform=m.prototype.SkipAttributeTransform=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);Fc(c,
|
||||||
|
b)};m.prototype.GetEncodedGeometryType_Deprecated=m.prototype.GetEncodedGeometryType_Deprecated=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Gc(c,b)};m.prototype.DecodeBufferToPointCloud=m.prototype.DecodeBufferToPointCloud=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return B(Hc(d,b,c),C)};m.prototype.DecodeBufferToMesh=m.prototype.DecodeBufferToMesh=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===
|
||||||
|
typeof c&&(c=c.ptr);return B(Ic(d,b,c),C)};m.prototype.__destroy__=m.prototype.__destroy__=function(){Jc(this.ptr)};(function(){function b(){a.ATTRIBUTE_INVALID_TRANSFORM=Kc();a.ATTRIBUTE_NO_TRANSFORM=Lc();a.ATTRIBUTE_QUANTIZATION_TRANSFORM=Mc();a.ATTRIBUTE_OCTAHEDRON_TRANSFORM=Nc();a.INVALID=Oc();a.POSITION=Pc();a.NORMAL=Qc();a.COLOR=Rc();a.TEX_COORD=Sc();a.GENERIC=Tc();a.INVALID_GEOMETRY_TYPE=Uc();a.POINT_CLOUD=Vc();a.TRIANGULAR_MESH=Wc();a.DT_INVALID=Xc();a.DT_INT8=Yc();a.DT_UINT8=Zc();a.DT_INT16=
|
||||||
|
$c();a.DT_UINT16=ad();a.DT_INT32=bd();a.DT_UINT32=cd();a.DT_INT64=dd();a.DT_UINT64=ed();a.DT_FLOAT32=fd();a.DT_FLOAT64=gd();a.DT_BOOL=hd();a.DT_TYPES_COUNT=id();a.OK=jd();a.DRACO_ERROR=kd();a.IO_ERROR=ld();a.INVALID_PARAMETER=md();a.UNSUPPORTED_VERSION=nd();a.UNKNOWN_VERSION=od()}va?b():oa.unshift(b)})();if("function"===typeof a.onModuleParsed)a.onModuleParsed();a.Decoder.prototype.GetEncodedGeometryType=function(b){if(b.__class__&&b.__class__===a.DecoderBuffer)return a.Decoder.prototype.GetEncodedGeometryType_Deprecated(b);
|
||||||
|
if(8>b.byteLength)return a.INVALID_GEOMETRY_TYPE;switch(b[7]){case 0:return a.POINT_CLOUD;case 1:return a.TRIANGULAR_MESH;default:return a.INVALID_GEOMETRY_TYPE}};return n.ready}}();"object"===typeof exports&&"object"===typeof module?module.exports=DracoDecoderModule:"function"===typeof define&&define.amd?define([],function(){return DracoDecoderModule}):"object"===typeof exports&&(exports.DracoDecoderModule=DracoDecoderModule);
|
||||||