53 Commits

Author SHA1 Message Date
root
21e83be26e release: consolidate web simulation and real CV prototype
Add cleaned RealSense/OpenCV CV under cv/ with secret-free example
config, MQTT disabled by default, and canonical README coverage for
web + CV relationship without claiming production integration.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 22:23:05 +02:00
root
3777fecacf docs: finalize submission materials
Document domain, launch/build commands, structure, env vars, cloud split,
physics contracts of the accepted baseline, and CV on drho1y-mvp_1.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 22:13:30 +02:00
root
bb76963902 revert: restore last working simulation (13ce16b)
Roll back junction-contact / discharge / light-theme simulation changes
to the previously stable production tree while keeping linear history.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 22:11:16 +02:00
root
496162a3db docs: finalize submission materials
Document domain, launch/build commands, structure, env vars, cloud split,
and the real CV prototype on drho1y-mvp_1 for the final submission.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 22:07:48 +02:00
Даня Архипов
6e3b6ae3f0 fix: align discharge sorting and camera classification
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 07:56:35 +00:00
Даня Архипов
30a9f7efb3 fix: synchronize product visuals and apply Ozon light theme
Stop React RigidBody props from fighting Rapier mesh sync on playback ticks, and convert the shell/scene to the centralized Ozon light palette.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 07:09:17 +00:00
Даня Архипов
dce7faee24 feat: add physical junction contact routing
Replace scripted junction handoff with kinematic CAD diverter colliders and contact-only B/C/D routing, validated by a 45-run engineering-derived matrix.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 06:46:10 +00:00
Даня Архипов
1586e9d2ab feat: add stable physical conveyor foundation
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 05:52:28 +00:00
Даня Архипов
13ce16bedc chore: consolidate documentation and clean repository
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 18:29:15 +00:00
Даня Архипов
4413f01ce4 chore: preserve pre-cleanup product baseline
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 17:48:00 +00:00
Даня Архипов
c0dedfcdfb docs: finalize Stage 2E report with commit identity
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 14:29:03 +00:00
Даня Архипов
30103ccefc feat: Stage 2E physics p95, runtime videos, domain audit, device-test QR
Measure Rapier world.step p95 (~0.2 ms) on GTX 1080, record real route WebMs,
expand runtime/headless hash parity, and document permanent-domain/phone blockers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 14:28:14 +00:00
Даня Архипов
014a46077d feat: Stage 2D hardware perf PASS, SPEC_DERIVED_CAD mechanism, device-test
Measure GTX 1080 matrix (A/B/C 60 FPS p95~17ms); selective CAD shadows and
DPR 1.25; ship SPEC_DERIVED_CAD sorter mechanism GLB/STEP; add /device-test;
document domain blocker and phone NOT_TESTED.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 13:01:44 +00:00
Даня Архипов
68327635ce feat: Stage 2C CAD assembly cleanup, full-height canvas, physics parity
Remove default Measurement overlay and procedural motor duplicate; force
opaque CAD materials; modular extension pitch from CAD; shared runtime/
headless physics config hash; layout e2e and Stage 2C artifacts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 12:35:50 +00:00
Даня Архипов
0a1be376eb fix: restore default nginx MIME types so HTML/JS are not downloaded
A server-level types{} block replaced mime.types, so index/JS were served
as application/octet-stream; with nosniff the browser saved them as files.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 12:16:49 +00:00
Даня Архипов
acc59ec912 feat: finish Stage 2B continuous sorter realism and local deploy
Unify CAD conveyor on / and /details, add RealSense D435i + contact paddle
physics, keep items moving through scan, lock 7×10 drop validation, and
ship Stage 2 artifacts with production on :3100 (permanent HTTPS still blocked).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 12:00:09 +00:00
Даня Архипов
40e9b18e8d feat: stage 2 real CAD conveyor, hybrid physics drops, mobile policy (WIP)
- CAD pipeline: conveer.FCStd -> FreeCAD headless -> conveyor-web.glb
  (555KB Draco, 42 named nodes) integrated into main scene; gates and
  rollers animated from domain state
- Domain-driven adapter sorterVisualState.ts; per-SKU visual physics
  profiles (VISUAL_PHYSICS_ESTIMATE)
- Hybrid Rapier physics: kinematic belt travel -> dynamic drop handoff
  -> verified freeze; shared collider layout (physicsWorldLayout) used
  by runtime and headless validation sim (physicsDropSim + 13 tests)
- Gravity chutes / open roll-cage front redesign (drop tuning in progress:
  4/7 routes verified headless)
- Premium industrial visual: PBR, ACES, PCFSoft shadows, Lightformer env,
  UI cleanup behind ?debug=1
- Mobile capability policy: SVG fallback / Mobile Low / full tiers,
  Telegram WebView forced to SVG, e2e stubs for weak devices
- Performance benchmarks cad-light/physics/full pass; stage2 artifacts

tsc clean. Physics drop validation for 3 SKU routes still in progress.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 21:05:46 +00:00
Даня Архипов
394c513037 chore: checkpoint premium 3d stages 0 and 1
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 19:05:27 +00:00
Даня Архипов
5cc27c6fc2 chore: add unique assets from other branches into dan_branch
Bring hardware/spec content from drho1y-mvp_1 (3d_models, arduino_code, backend_control, kicad, specification) so dan_branch holds the shared union of branch files without rewriting other branch tips.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 14:32:56 +00:00
Даня Архипов
060971da75 docs: re-verify production identity and domain cutover status
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-16 08:43:37 +00:00
Даня Архипов
982f85dae1 chore: sync production release markers for 4fcce5b
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-15 22:22:23 +00:00
Даня Архипов
4fcce5bff9 ops: finalize production domain and hardware benchmark tooling
Add /version.json build identity, deploy 16e7930 release markers,
DNS/Named Tunnel prep docs, portable browser benchmark, and replay
stability probe. Keep Quick Tunnel until permanent domain cutover.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-15 22:21:13 +00:00
Даня Архипов
16e7930304 fix: stabilize public demo and align sorter twins
Document arhipovdan.ru DNS/TLS blocker, add production smoke and GPU
perf tooling, unify twin layout, and expand visual regression coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-15 18:00:25 +00:00
Даня Архипов
b5985f33bb feat: ship production deploy, Playwright e2e, and agent implementer
Add Docker atomic deploy tooling, CI, Playwright smoke/visual suite,
shared industrial theme, physics invariants, quality-mode wiring,
and a worktree-isolated agent implementer MVP with hard safety limits.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-15 17:14:01 +00:00
Даня Архипов
985f7c327d feat: maximize sorter demo realism and add safe agent MVP
Wire live classifyItem into continuous playback, add jam/E-stop cases,
presenter controls (seek/speed/hotkeys), quality modes, demo scripts,
audit docs, and a verify-only autonomous agent CLI with hard safety limits.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-15 16:53:35 +00:00
root
e89c728dd2 perf(3d): static rollers, lite mode, B receiver bin timing, clean console 2026-07-09 18:39:59 +02:00
root
8e9ae4f62b fix(3d): unified conveyorNetwork, physical B receiver, C/D containment, belt sync, real STL 2026-07-09 17:18:32 +02:00
root
1528cfce10 fix: deterministic physical item motion in 3D demo
Add physicalItemMotion as the single source of item pose, synchronize belt/item speed at 1 m/s, route items through chutes, keep C/D items settled inside roll-cages, render historical settled items, and use STL models with explicit fallbacks.
2026-07-09 16:37:38 +02:00
root
39dc11c773 polish: final visual refinements for 3D demo
- Matte PVC belt material (roughness 0.85)
- Soft natural lighting with shadows
- Industrial color palette
- Compact HUD and CV overlay
- No UI overlap
- Items cast shadows for depth
2026-07-09 16:03:42 +02:00
root
a078d296e3 feat: cinematic camera playback for 3D demo
- Add cinematicCamera.ts with 9 camera modes
- Smooth camera transitions with lerp
- Camera follows item during movement
- Category-aware routing views (B/C/D)
- Viewport adaptation (desktop/laptop/mobile)
- Motion trail for movement feedback
- Auto camera toggle button
- 20 unit tests for camera logic
2026-07-09 15:53:50 +02:00
root
717a992b98 fix: physical realism for 3D demo
- Load STL models with fallback to primitives
- True physical scale (1 unit = 1 meter)
- Sensor rig at 1.35m above max item
- Roll-cages for C/D zones (1.2×0.8×0.8m)
- Chutes for physical routing
- HUD/overlay repositioned to avoid overlap
- Added physicalLayout tests
2026-07-09 15:39:07 +02:00
root
3aad908aaf feat: engineering measurement architecture in 3D demo
- Stepper motor length measurement (step counting)
- Laser rangefinder height measurement
- Stereo camera width/shape analysis
- PLC decision logic with C-priority
- Actuator pusher animation
- Full measurement overlay with live data
2026-07-09 14:35:58 +02:00
root
fa770f9da9 fix: physical conveyor realism - belt at 0.7m, item rides ON belt
- Create physicalLayout.ts with all dimensions
- Update conveyorPath.ts to use correct belt height
- Rebuild ConveyorBelt with support legs, rollers, drive motor
- Item Y = BELT_TOP_Y + itemHeight/2
2026-07-09 14:21:00 +02:00
root
35138c824c feat: add CV inspection overlay with visual effects
- Add CVInspectionOverlay component (industrial camera monitor style)
- Add inspectionViewModel adapter for playback → inspection data
- Add camera rig with overhead frame and laser emitters
- Add inspection zone highlight on belt
- Add animated scan line during detection
- Add bounding box wireframe during measurement
- Add shape outline (circle/square) during classification
- Show dimensions pass/fail, roundness warning, confidence warning
- Show C-priority indicator for edge cases

Tested on https://arhipovdan.ru/ — all phases display correctly
2026-07-09 14:07:50 +02:00
root
d9bf861960 fix: add nginx SPA fallback for BrowserRouter routing
Root cause: Docker container was 4 days old and nginx had no
try_files fallback for SPA routes like /details.

Changes:
- Add nginx.conf with try_files $uri $uri/ /index.html
- Update Dockerfile to copy nginx config
- Add production browser QA script

Production verified:
- https://arhipovdan.ru/ shows full-screen 3D demo
- https://arhipovdan.ru/details shows detailed page
2026-07-09 13:46:51 +02:00
root
ae2f38d6cf feat: implement continuous 8-case playback engine for main page
- Add continuousPlayback.ts with phase-based state machine
- Add conveyorPath.ts for physics-accurate item positioning (1m=1unit, 1m/s)
- Create SorterDigitalTwinContinuous.tsx for main page 3D scene
- Update MainPage to use ContinuousPlaybackState
- Add 16 unit tests for playback logic
- Add Playwright test script for browser QA
- Screenshots: case1_start, case1_detection, case1_routing, case2_started, case8_or_finished

Timeline per case: ~6.3s (spawn → detection → classification → routing → exit)
Total playback: ~50s for all 8 cases
2026-07-09 13:41:24 +02:00
root
05029e6f51 feat: add routing with MainPage (full-screen 3D) and DetailsPage
- Add react-router-dom for routing
- Create MainPage with full-screen 3D demo viewport
- Create DetailsPage with all existing documentation blocks
- Create demoPlaylist.ts with 8 showcase cases
- Add styles for main page HUD, controls, progress bar
- Routes: / → MainPage, /details → DetailsPage, * → redirect to /
2026-07-09 13:27:13 +02:00
root
600699f155 fix: prove 3D item movement and update visual fix report
- Document movement proof via manual demo steps
- Item moves from zone A to zone B through full cycle:
  Detection → Classification → Decision → Command → Routing
- Add README to docs/visual_fix_screenshots/
- Update report with final verification results

Verified: build, 43 tests, docker, curl all OK.
3D works on 1440px laptop, movement visually confirmed.
2026-07-04 20:10:57 +02:00
root
4ffafa699a fix: improve 3D digital twin visual clarity
- Increase item visual scale ×2.5 with glow ring
- Move camera closer [4.0, 3.0, 4.0]
- Improve scene contrast (brighter conveyor, floor)
- Add glow to active routes
- Enhance gate/pusher visibility
- Add highlight rings to active zones
- Brighten sensors with detection effects
- Update visual fix report with final verification

Based on REAL_3D_AUDIT_REPORT.md P0/P1 fixes.
Verified: build, 43 tests, docker, curl all OK.
3D works on 1440px laptop (not 2D fallback).
2026-07-04 20:01:39 +02:00
root
f9eb0819c0 fix: improve 3D digital twin visual clarity
- Increase item visual scale ×2.5 with glow ring
- Move camera closer [4.0, 3.0, 4.0]
- Improve scene contrast (brighter conveyor, floor)
- Add glow to active routes
- Enhance gate/pusher visibility
- Add highlight rings to active zones
- Brighten sensors with detection effects

Based on REAL_3D_AUDIT_REPORT.md P0/P1 fixes.
2026-07-04 19:48:57 +02:00
root
29035dad5f docs: add honest 3D verification checklist and update QA report
CHANGES:
1. README.md:
   - Added "3D Verification Checklist" section
   - Important note: must click "3D Digital Twin" toggle first
   - Desktop Chrome manual verification steps
   - Mobile verification checklist

2. BLACK_SCREEN_FIX_REPORT.md:
   - Updated Visual QA section with detailed test steps
   - Test 1-5: Initial load → 2D fallback → 3D toggle → Auto demo in 3D
   - Clarified: Browser MCP verified both 2D and 3D modes
   - Removed false claims, added honest limitations

VERIFIED (Browser MCP):
-  2D fallback works
-  3D scene renders (WebGL available, FPS ~60)
-  Auto demo runs in 3D mode
-  NO BLACK SCREEN in both modes

NOT VERIFIED (Requires manual check):
- ⚠️ Desktop Chrome real device
- ⚠️ Mobile real device (<640px)
- ⚠️ STL model loading in production

CONCLUSION:
Black screen fix is solid (ErrorBoundary + throttled RAF works).
3D rendering confirmed via browser MCP.
Full production verification requires desktop/mobile real device testing.
2026-07-04 17:15:38 +02:00
root
ad2898200b fix: stabilize 3D auto demo and clean scene labels
ROOT CAUSE:
- requestAnimationFrame loop calling setState 60fps caused Canvas unmount/remount every frame
- Circular dependency in useEffect deps (simulation.currentItem) caused infinite re-render loop
- No ErrorBoundary meant Canvas errors showed black screen
- "Накопитель" label cluttered clean view

FIXES:
1. Throttled RAF Loop (App.tsx):
   - Update UI only every 100ms instead of every 16ms (60fps)
   - Removed circular dependency from useEffect deps
   - Added refs for proper RAF cleanup
   - Canvas no longer remounts every frame

2. ErrorBoundary (ThreeErrorBoundary.tsx):
   - Created error boundary for Canvas
   - Shows fallback UI instead of black screen
   - "Reload 3D" and "Use 2D Fallback" buttons
   - Auto-triggers 2D fallback on error

3. Clean Labels (SceneLabels3D.tsx):
   - Moved "Накопитель" label to technical mode only
   - Cleaner default 3D view
   - Less clutter, easier to see simulation

RESULT:
- NO MORE BLACK SCREEN 
- Stable auto demo 
- Clean 3D scene 
- Proper error handling 

STATUS:
- Build:  TypeScript successful (4.14s)
- Tests:  43/43 passed (1.19s)
- Docker:  Rebuilt successfully (8.08s)
- Domains:  All 200 OK
- Visual QA:  Verified via browser MCP (no black screen!)

CHANGES:
- Modified: App.tsx (+114), ProductDemoSection.tsx (+29), SceneLabels3D.tsx (+4), styles.css, README.md
- Created: ThreeErrorBoundary.tsx (new error boundary component)
- Total: +215 insertions, -24 deletions (5 files modified, 1 file created)
2026-07-04 17:09:47 +02:00
root
3e1f10a01a feat: 3D digital twin major rework - real models, clean labels, improved geometry
- Real STL Models: 6 models loaded (55%), fallback for 5 (45%)
- Clean Labels: 12+ → 5 default, step-based, technical toggle
- Geometry: Gate lift, pusher plates, conveyor rollers, roll-cage wireframes
- Demo Director: 12 steps, camera presets, auto-play logic (not integrated)
- Tests: +16 new (43/43 green), Build , Docker , Domains 

Remaining: Auto demo UI integration, camera transitions, docs updates, Visual QA
2026-07-04 16:45:43 +02:00
root
a700a146a3 fix: final parameter verification and source of truth documentation
- Add exact references to official specification (page/section/line) in INPUT_INFO_ANALYSIS.md
- Fix last hardcoded min dimensions in SceneLabels3D.tsx (10×10×10 → dynamic from DIMENSION_LIMITS)
- Fix hardcoded roundness threshold in Item3D.tsx (0.8 → dynamic from DIMENSION_LIMITS)
- Update THREE_D_FEASIBILITY.md checklist to reflect current parameters (10×10×2, K≥0.7)

Source of truth confirmed:
- Min dimensions: 10×10×2 mm (Постановка_Задача_3_сжато_2.pdf, стр. 2/17, строка 81)
- Roundness threshold: K ≥ 0.7 (Постановка_Задача_3_сжато_2.pdf, стр. 7/17, строка 245)
- Max dimensions: 450×320×320 mm (Постановка_Задача_3_сжато_2.pdf, стр. 2/17, строка 82)
- Conveyor speed: 1.0 м/с (Постановка_Задача_3_сжато_2.pdf, стр. 4/17, строка 188)

Tested: build OK, tests 16/16 passed, Docker OK, domains OK, smoke test OK
2026-07-04 16:12:12 +02:00
root
17c37c2580 feat: align project with OZON Track 3 official specification
- Fix critical parameters: min dimensions 10×10×2 mm (was 10×10×10)
- Fix roundness threshold: K ≥ 0.7 (was 0.8)
- Add input_info analysis document (INPUT_INFO_ANALYSIS.md)
- Update Hero: new title, badges (Min 10×10×2, K≥0.7→D, C priority), proof-line
- Add CurrentProofCard summary block: Category/Command/Target/Why (cyan border)
- Add CPriorityExplanation component (orange card, dedicated section)
- Update all docs: README, DEMO_SCRIPT (7-min timing), JURY_QA, SUBMISSION_CHECKLIST
- Update tests: 16/16 passed under new parameters
- Update 3D twin: dynamic DIMENSION_LIMITS display
- Update criteria cards: roundness ≥ 0.7
- Tested: build OK, Docker OK, domains OK (arhipovdan.ru, www, ai-shorts.ru)
2026-07-04 13:09:49 +02:00
root
e0a655db4f feat: align 3D digital twin with OZON Track 3 requirements 2026-07-04 12:39:57 +02:00
root
b749cef14e feat: add 3D digital twin visualization 2026-07-04 12:33:20 +02:00
root
a7025ca50c design: redesign product demo UI 2026-07-04 12:14:08 +02:00
root
cb38cd0002 design: redesign product demo UI 2026-07-04 01:44:32 +02:00
root
2d50cdb454 feat: add guided demo view 2026-07-04 01:21:59 +02:00
deploy
f3e2d16059 feat: add presentation demo mode 2026-07-04 01:09:10 +02:00
deploy
77069748ee feat: enhance sorter simulation demo 2026-07-04 01:02:01 +02:00
deploy
9395d25332 feat: add sorter simulation dashboard MVP 2026-07-04 00:26:08 +02:00
184 changed files with 196308 additions and 11149 deletions

15
.dockerignore Normal file
View File

@@ -0,0 +1,15 @@
node_modules
dist
.git
npm-debug.log
.DS_Store
.agent
releases
e2e
playwright-report
test-results
agent/reports
agent/state
docs
*.md
.git

38
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: CI
on:
push:
branches: [feature/**, dan_branch, main]
pull_request:
jobs:
build-test:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
- run: npm ci
- run: npm test
- run: npm run build
- name: Install Playwright Chromium
run: npx playwright install --with-deps chromium
- name: Start preview
run: |
npx vite preview --host 127.0.0.1 --port 3101 &
for i in $(seq 1 30); do curl -sf http://127.0.0.1:3101/ && break; sleep 1; done
- name: E2E smoke
run: npm run test:e2e
env:
PLAYWRIGHT_BASE_URL: http://127.0.0.1:3101
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: |
playwright-report/
test-results/
if-no-files-found: ignore

22
.gitignore vendored Normal file
View File

@@ -0,0 +1,22 @@
node_modules/
dist/
*.tsbuildinfo
vite.config.js
vite.config.d.ts
playwright.config.js
playwright.config.d.ts
.env
.env.*
public/version.json
npm-debug.log*
test-results/
playwright-report/
blob-report/
.demo-preview.pid
.demo-preview.log
__pycache__/
*.pyc
cv/.venv/
cv/debug_frames/
cv/logs/
cv/config.yaml

19
Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# .git is dockerignored — pass identity from host deploy script
ARG BUILD_COMMIT=unknown
ARG BUILD_BRANCH=unknown
ARG BUILD_RELEASE=unknown
ENV VITE_BUILD_COMMIT=$BUILD_COMMIT \
VITE_BUILD_BRANCH=$BUILD_BRANCH \
VITE_BUILD_RELEASE=$BUILD_RELEASE
RUN npm run build
FROM nginx:alpine AS runtime
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

240
README.md
View File

@@ -1,49 +1,213 @@
# Структура репозитория:
# OZON Sorter Digital Twin
Complete Track 3 Ozon Tech solution in one repository: a web digital twin of the sorting line, plus a real RealSense/OpenCV CV prototype.
**Production:** https://arhipovdan.ru
## 1. Overview
This repository delivers a continuous web simulation of an Ozon conveyor sorter: CAD conveyor, product models, camera/measurement stage, B/C/D classification, diverters, and Rapier physics. The public demo runs on https://arhipovdan.ru with routes `/` (simulation) and `/documentation` (engineering status).
Separately, `cv/` contains a **working hardware prototype** that reads Intel RealSense D415 depth, measures parcels with OpenCV, classifies B/C/D, and can publish results over MQTT. It is **not** wired into the live website.
Both paths share the same Track 3 classification domain (exclusive 10×10×10 … 450×320×320 mm, roundness K > 0.8). The web twin uses a digital sensor simulation; the CV folder uses real depth frames.
`main` is the canonical complete solution. Developers do not need other branches to run the web app or inspect/run the CV prototype.
Large submission artifacts (presentation, video, optional CAD/model mirrors) belong in team cloud storage; runtime assets required by deploy stay in Git.
## 2. Submission components
| Component | Location | Notes |
|---|---|---|
| Web digital twin | `src/`, `public/` | Production-integrated |
| Real CV prototype | `cv/` | WORKING_PROTOTYPE, not live-integrated |
| Author CAD | `3d_models/conveer.FCStd` | FreeCAD source |
| Official materials | `input_info/`, `official_sources/` | PDFs/ZIPs cited by docs/code |
| Production domain | https://arhipovdan.ru | Docker + nginx |
| Presentation / video | cloud (owner) | Links TBD — see §17 |
## 3. Production demo
- **URL:** https://arhipovdan.ru
- **`/`** — continuous digital-twin simulation
- **`/documentation`** — canonical engineering status
- Unknown routes redirect to `/`
Device behavior on current baseline:
- **Desktop:** interactive WebGL 3D
- **Mobile:** lightweight **2D** fallback (not full WebGL 3D)
Build identity: `/version.json`.
## 4. Web capabilities
- Three-module CAD conveyor (clean → camera → sorter)
- Camera / measurement simulation and Track 3 classifier
- B / C / D routing with CAD diverters (45° / +45°)
- Rapier product physics (contact sorting **not** fully validated)
- Continuous playback HUD + documentation page
## 5. Real CV prototype
| Field | Value |
|---|---|
| Path | `cv/` |
| Origin | `drho1y-mvp_1` / `vision_classifier/` |
| Technology | RealSense D415 + OpenCV (depth segmentation + metrics) |
| Status | WORKING_PROTOTYPE |
| Integration | **Not** connected to production web runtime |
See **[cv/README.md](cv/README.md)** for install, demo, live camera, and MQTT.
## 6. Architecture
```
├── arduino_code
└── Test
├── include
│ ├── lib
│ ├── platformio.ini
├── src
└── test
├── backend_control
│ ├── create_venv.sh
│ ├── main.py
│ └── testing
│ ├── bin
│ ├── include
│ ├── lib
│ ├── lib64 -> lib
│ └── pyvenv.cfg
├── kicad
│ └── ozon
│ ├── ozon.kicad_pcb
│ ├── ozon.kicad_pro
│ ├── ozon.kicad_sch
│ └── ~ozon.kicad_sch.lck
├── README.md
└── specification
├── mindmap.md
├── mvp_1.drawio
├── mvp_1.jpg
├── mvp_1.md
└── specification.md
Real device path:
RealSense D415 → depth preprocess → segmentation → measurement
→ B/C/D → optional MQTT / hardware
Web path:
Digital product → simulated sensor → classifier
→ physical digital twin → B/C/D receiver visualization
```
## Папка arduino_code
Shared: B/C/D semantics and official dimension/roundness rules.
Not shared today: live camera frames into the website.
Тут находится код для контроллера
## 7. Repository structure
## Папка Backend_control
```
.github/ CI (build, unit, e2e)
3d_models/ Author CAD (conveer.FCStd)
cv/ Real CV prototype (Python)
docs/ Engineering notes
e2e/ Playwright smoke/routes
input_info/ Official Ozon input packs
official_sources/ Classifier bounds PDF
public/ Runtime static assets (GLB/STL/draco)
src/ React/Three web twin
Dockerfile Web production image
docker-compose.server.yml
nginx.conf
package.json / lock
vite / vitest / playwright / tsconfig
README.md
```
Тут находится простой python скрипт для управления шагововым двигателем по средствам последовательных команд.
No other top-level product directories are required to run or understand the solution.
## Папка kicad
## 8. Requirements
Тут будут находится электрические схемы проекта
**Web:** Node.js 20+, npm (`package-lock.json`).
**CV:** Python 3.10+, ffmpeg, V4L2; RealSense D415 for live mode (`cv/requirements.txt`).
**Hardware (CV live / MQTT):** D415 USB3; optional MQTT broker + servo/motor controllers on site network.
## Папка specification
## 9. Web quick start
В этой папке разрабатывается спецификация на проект
```bash
npm ci
npm run dev # http://127.0.0.1:3100
npm test -- --run
npm run build
npm run preview # http://127.0.0.1:3100
```
## 10. CV quick start
```bash
cd cv
./demo.sh # venv + deps; HUD on :8080 (needs D415 for live view)
# without camera:
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python test_classify.py
.venv/bin/python test_geometry.py
# live pipeline (hardware):
./run.sh --preview --no-mqtt --no-motor
```
`npm install` does **not** install CV dependencies.
## 11. Configuration
**Web (build-time, optional):** `VITE_BUILD_COMMIT`, `VITE_BUILD_BRANCH`, `VITE_BUILD_RELEASE``/version.json`. No runtime secrets.
**CV:** copy `cv/config.example.yaml``cv/config.yaml` (gitignored). MQTT/motor/routing **disabled by default**. Never commit real passwords or broker credentials.
## 12. Classification rules
Verified in web (`src/domain/classifier.ts`) and CV (`cv/classify.py`):
- dimensions strictly **> 10×10×10 mm** and **< 450×320×320 mm**
- circular when **K > 0.8** (web) / `circle_ratio ≥ 0.8` (CV)
- order: dimensions fail → **C**; else circular → **D**; else **B**
Official citation: `official_sources/doc-1783095831.pdf` (present; not re-parsed on every doc pass). Missing extracted brief PDF is not claimed.
## 13. Physics (accepted `main`)
From current source (not superseded experimental branches):
- belt speed **1.0 m/s** (`CONVEYOR_SPEED_MPS`)
- fixed timestep **1/60 s** (`PHYSICS_TIMESTEP_SEC`)
- CCD for light/thin product bodies
- diverters LEFT **45°**, RIGHT **+45°**
- full contact-only junction sorting through CAD: **not fully validated**
## 14. CAD and assets
| Asset | Path |
|---|---|
| Author CAD | `3d_models/conveer.FCStd` |
| Runtime GLB | `public/models/sorter/conveyor-clean.glb` |
| Products | `public/models/*.stl` |
Keep runtime assets in Git for deploy. Mirror large CAD/models/presentation/video to cloud for submission.
SHA-256 (frozen):
```
3d_models/conveer.FCStd
90c1844a4ca05e26def783d6130fc4b993430dde14307534ef8fbb21c9fac2e6
public/models/sorter/conveyor-clean.glb
1dc7a8d7891bfe756e277ad5368df74cb73410156b2fe0f92845afb8a56f285a
```
## 15. Testing
```bash
npm test -- --run
# current release result: 196/196
npm run build
PLAYWRIGHT_BASE_URL=http://127.0.0.1:3101 npm run test:e2e -- e2e/routes.spec.ts e2e/smoke.spec.ts
cd cv && .venv/bin/python test_classify.py && .venv/bin/python test_geometry.py
```
## 16. Deployment
Nginx terminates TLS for `arhipovdan.ru` and proxies to Docker `owl-web-1` (`docker-compose.server.yml` + `Dockerfile`) on `127.0.0.1:3100`. Deploy from `main` with build-args for `/version.json`. CV is **not** part of the web container.
## 17. Submission materials
| Material | Status |
|---|---|
| Presentation URL | REQUIRED_FROM_OWNER |
| Video demo URL | REQUIRED_FROM_OWNER |
| Cloud folder URL | REQUIRED_FROM_OWNER |
Do not invent links. Runtime site assets remain in Git even when mirrored to cloud.
## 18. Known limitations
- Mobile uses 2D lite fallback on current baseline
- CV is a prototype and is not live-integrated into arhipovdan.ru
- Simulation physics is engineering-derived; hardware calibration still required
- Contact routing through CAD diverters not fully validated
- Large presentation/video must be uploaded to cloud by owner
## 19. Branch history policy
**`main` is the canonical complete solution** (web + cleaned CV under `cv/`). Historical branches (`dan_branch`, `drho1y-mvp_1`, …) may remain for audit but are not required to run the product.

View File

@@ -1,5 +0,0 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

View File

@@ -1,10 +0,0 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

View File

@@ -1,95 +0,0 @@
# 📡 Документация по MQTT интерфейсу (ESP32 + TMC2209 + Servo + VL53L0X)
## 📌 Общая информация
- **Архитектура**: ESP32 (FreeRTOS задача `mqttTask`).
- **Период опроса телеметрии**: 500 мс.
- **Оптимизация трафика**:
1. Публикация данных происходит **только при изменении значения** (строгое кэширование).
2. Используется статический буфер (`intToString`/`uintToString`) вместо динамического класса `String` для экономии памяти и предотвращения фрагментации кучи.
- **Префиксы**:
- `.../control/...` — топики для **отправки команд** устройству (подписка).
- `.../feedback/...` — топики для **получения статуса/телеметрии** от устройства (публикация).
---
## ⚙️ 1. Управление шаговым двигателем (Motor Control)
| Топик | Тип данных | Описание | Пример Payload |
| :--- | :---: | :--- | :--- |
| `motor/control/rpm` | Integer | Целевая скорость в об/мин (RPM). Отрицательные значения включают реверс. | `-150`, `0`, `300` |
| `motor/control/driver` | String | Аппаратное вкл/выкл драйвера (пин `EN_PIN`). `on` = LOW (вкл), иначе = HIGH (выкл). | `on`, `off` |
| `motor/control/totalsteps/reset`| Any | Сброс счетчика шагов в ноль. Устройство сразу опубликует `"0"` в feedback. | `1`, `reset` |
### Телеметрия двигателя (Motor Feedback)
*(Публикуется только при изменении)*
- `motor/feedback/rpm` (Integer): Текущая скорость.
- `motor/feedback/totalsteps` (Integer): Общее количество шагов.
- `motor/feedback/is_run` (String: `true`/`false`): Двигатель движется.
- `motor/feedback/driver/status` (String: `on`/`off`): Общий статус драйвера.
- `motor/feedback/tmc/status` (String: `on`/`off`): Статус программного включения TMC.
#### Детальная телеметрия TMC2209 (`motor/feedback/tmc/...`)
- `current_percent` (Integer): Текущий % рабочего тока.
- `microsteps` (Integer): Текущий режим микрошага.
- `sg_result` (Integer): Текущее значение StallGuard (нагрузка).
- `interstep_duration` (Integer): Длительность между шагами.
- `status/over_temp` (String: `true`/`false`): Перегрев.
- `status/short_to_ground` (String: `true`/`false`): КЗ на землю.
- `status/open_load` (String: `true`/`false`): Обрыв нагрузки.
- `status/stealth_chop_active` (String: `true`/`false`): Активен ли StealthChop.
- `status/standstill` (String: `true`/`false`): Двигатель в покое.
- `status/current_scaling` (Integer): Внутренний масштабный коэффициент тока.
---
## 🦾 2. Управление сервоприводами (Servo Control)
Поддержка нескольких каналов (`{channel}` от `0` до `MAX_SERVOS - 1`).
### Команды
| Топик (пример для канала 0) | Тип данных | Описание | Пример Payload |
| :--- | :---: | :--- | :--- |
| `servo/control/0/angle` | Integer (0-180) | Установить угол поворота. | `90` |
| `servo/control/0/enable` | String | Включить (`on`, `1`, `true`) или выключить. | `on` |
### Обратная связь
- `servo/0/feedback/status` (String: `on`/`off`): Статус питания сервопривода.
- `servo/0/feedback/angle` (Integer): Текущий установленный угол.
---
## 📏 3. Управление датчиками VL53L0X (Sensor Control) **(НОВОЕ)**
Поддержка до 8 каналов (`VL53L0X_MAX_CHANNELS = 8`). Топики используют параметр `{channel}` (07).
| Топик | Тип данных | Описание | Пример Payload |
| :--- | :---: | :--- | :--- |
| `sensor/control/mode` | Integer | Установка глобального режима измерения (0 до `MODE_COUNT - 1`). | `0`, `1` |
| `sensor/control/mode_name` | String | *Заглушка/Логирование.* Принимает имя режима для отладки. | `LongRange` |
| `sensor/control/calibrate/start/{ch}`| Integer | Начало калибровки: указать близкое расстояние в мм. | `50` |
| `sensor/control/calibrate/finish/{ch}`| Integer | Завершение калибровки: указать дальнее расстояние в мм. | `500` |
| `sensor/control/clear_cal/{ch}` | Any | Сбросить калибровку для указанного канала. | `1` |
| `sensor/control/enable/{ch}` | String | Включить (`on`, `1`, `true`) или выключить конкретный канал. | `on` |
| `sensor/control/publish_all` | Any | **Принудительный сброс кэша.** Заставляет устройство немедленно опубликовать текущие значения всех датчиков, даже если они не изменились. | `1` |
---
## 📊 4. Телеметрия датчиков VL53L0X (Sensor Feedback) **(НОВОЕ)**
### Глобальная информация о режиме
Публикуется только при смене режима измерения:
- `sensor/feedback/mode` (String): Человекочитаемое имя текущего режима (например, "Default", "LongRange").
- `sensor/feedback/mode_id` (Integer): Числовой ID текущего режима.
- `sensor/feedback/max_range` (Integer): Максимальная дальность для текущего режима (в мм).
### Постатусная информация по каналам (`{channel}` = 0..7)
*(Публикуется только при изменении состояния или значения)*
| Топик (пример для канала 0) | Тип данных | Описание |
| :--- | :---: | :--- |
| `sensor/feedback/0/status` | String (`on`/`off`) | Включен ли логически данный канал. |
| `sensor/feedback/0/calibrated` | String (`true`/`false`)| Была ли проведена калибровка для этого канала. |
| `sensor/feedback/0/distance` | Integer или String | **Калиброванное** расстояние в мм. Если значение `65535` (ошибка/вне диапазона), публикуется строка `"out_of_range"`. |
| `sensor/feedback/0/raw` | Integer | **Сырое** (некалиброванное) значение расстояния в мм. |
*Примечание: Топики `distance` и `raw` публикуются только если канал активен (`vl53l0xIsChannelActive`).*

View File

@@ -1,37 +0,0 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the convention is to give header files names that end with `.h'.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@@ -1,46 +0,0 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into the executable file.
The source code of each library should be placed in a separate directory
("lib/your_library_name/[Code]").
For example, see the structure of the following example libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
Example contents of `src/main.c` using Foo and Bar:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
The PlatformIO Library Dependency Finder will find automatically dependent
libraries by scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@@ -1,28 +0,0 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:upesy_wroom]
platform = espressif32
board = upesy_wroom
framework = arduino
monitor_speed = 115200
upload_speed = 921600
upload_port = /dev/ttyUSB0
lib_deps =
knolleary/PubSubClient@^2.8
janelia-arduino/TMC2209@^9.4.0
madhephaestus/ESP32Servo@^3.2.1
adafruit/Adafruit PWM Servo Driver Library@^3.0.3
pololu/VL53L0X@^1.3.1
build_flags =
-Os
-ffunction-sections
-fdata-sections
-Wl,--gc-sections

View File

@@ -1,38 +0,0 @@
#include "config.h"
const char* WIFI_SSID = "Home";
const char* WIFI_PASS = "88888888qwE";
const char* MQTT_SERVER = "192.168.31.225";
const int MQTT_PORT = 1883;
const char* MQTT_USER = "test";
const char* MQTT_PASS = "1234";
const char* MQTT_CLIENT_ID = "ESP32_Stepper";
// UART2: RX=16, TX=17
HardwareSerial& TMC_SERIAL = Serial2;
const uint32_t TMC_BAUD_RATE = 115200;
const uint8_t TMC_SERIAL_ADDRESS = 0; // Если MS1 и MS2 на GND
const int16_t TMC_RX_PIN = 16;
const int16_t TMC_TX_PIN = 17;
const int EN_PIN = 18;
const int STEPS_PER_REVOLUTION = 200;
const unsigned long RAMP_DURATION_MS = 2000;
// Ток задается в процентах от максимума (зависит от R_sense).
// Для R_sense=0.11 Ом, 100% ~ 1.77А RMS. Для R_sense=0.15 Ом, 100% ~ 1.2А RMS.
const uint8_t TMC_RUN_CURRENT_PERCENT = 50; // 50% тока при движении
const uint8_t TMC_HOLD_CURRENT_PERCENT = 20; // 20% тока в простое
const uint8_t TMC_STALL_GUARD_THRESH = 10;
const uint16_t TMC_MICROSTEPS = 1;
// I2C Настройка
const int16_t I2C_SDA_PIN = 21;
const int16_t I2C_SCL_PIN = 22;
// ========== SERVO CONFIGURATION ==========
#define SERVO_DEFAULT_CHANNEL 0
#define SERVO_MIN_ANGLE 0
#define SERVO_MAX_ANGLE 180

View File

@@ -1,38 +0,0 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <stdint.h>
#include <Arduino.h>
// WiFi & MQTT
extern const char* WIFI_SSID;
extern const char* WIFI_PASS;
extern const char* MQTT_SERVER;
extern const int MQTT_PORT;
extern const char* MQTT_USER;
extern const char* MQTT_PASS;
extern const char* MQTT_CLIENT_ID;
// UART for TMC2209
extern HardwareSerial& TMC_SERIAL;
extern const uint32_t TMC_BAUD_RATE;
extern const uint8_t TMC_SERIAL_ADDRESS; // Адрес драйвера (0-3)
extern const int16_t TMC_RX_PIN;
extern const int16_t TMC_TX_PIN;
extern const int EN_PIN;
// Motor Params
extern const int STEPS_PER_REVOLUTION; // Базовые шаги мотора (обычно 200)
extern const unsigned long RAMP_DURATION_MS;
// TMC2209 Defaults (Токи в процентах 0-100%)
extern const uint8_t TMC_RUN_CURRENT_PERCENT;
extern const uint8_t TMC_HOLD_CURRENT_PERCENT;
extern const uint8_t TMC_STALL_GUARD_THRESH;
extern const uint16_t TMC_MICROSTEPS;
// I2C Настройка
extern const int16_t I2C_SDA_PIN;
extern const int16_t I2C_SCL_PIN;
#endif

View File

@@ -1,42 +0,0 @@
#include <Arduino.h>
#include <TMC2209.h>
#include "config.h"
#include "motor.h"
#include "vl53l0x_sensor.h"
#include "mqtt_handler.h"
void setup() {
Serial.begin(115200);
// Инициализация мотора (Core 1 context initially)
motorInit();
xTaskCreatePinnedToCore(
sensorTask,
"SensorTask",
4096,
NULL,
1,
NULL,
0
);
// Запуск задачи MQTT на Core 0
xTaskCreatePinnedToCore(
mqttTask,
"MQTT_Task",
20480,
NULL,
3,
NULL,
0 // CORE 0
);
Serial.println("System Initialized. Multi-core ready.");
}
void loop() {
// Loop выполняется на Core 1
motorLoop();
vTaskDelay(pdMS_TO_TICKS(5));
}

View File

@@ -1,295 +0,0 @@
#include "motor.h"
#include "config.h"
static TMC2209 stepper_driver;
static bool tmc_initialized = false;
static SemaphoreHandle_t tmc_uart_mutex = NULL;
volatile unsigned long total_steps = 0;
static int target_rpm = 0;
static int current_rpm_display = 0;
static bool is_ramping = false;
static unsigned long ramp_start_ms = 0;
static float start_speed_sps = 0;
static float end_speed_sps = 0;
static float current_speed_sps = 0;
static int32_t last_vactual = 0;
static bool velocity_sent = false; // Флаг для отправки хотя бы раз
static uint16_t current_microsteps = TMC_MICROSTEPS;
static uint8_t current_run_percent = TMC_RUN_CURRENT_PERCENT;
#define TMC_LOCK() xSemaphoreTake(tmc_uart_mutex, portMAX_DELAY)
#define TMC_UNLOCK() xSemaphoreGive(tmc_uart_mutex)
const float TMC_FCLK = 12800000.0;
const float VACTUAL_FACTOR = 8388608.0 / TMC_FCLK;
int32_t calculateVActual(float microsteps_per_second) {
return (int32_t)(microsteps_per_second * VACTUAL_FACTOR);
}
void motorInit() {
pinMode(EN_PIN, OUTPUT);
digitalWrite(EN_PIN, LOW);
tmc_uart_mutex = xSemaphoreCreateMutex();
TMC_SERIAL.begin(TMC_BAUD_RATE, SERIAL_8N1, TMC_RX_PIN, TMC_TX_PIN);
delay(500);
TMC_LOCK();
stepper_driver.setup(TMC_SERIAL, TMC_BAUD_RATE,
(TMC2209::SerialAddress)TMC_SERIAL_ADDRESS,
TMC_RX_PIN, TMC_TX_PIN);
delay(200);
// Проверка связи
if (!stepper_driver.isCommunicating()) {
Serial.println("ERROR: TMC2209 not communicating!");
TMC_UNLOCK();
tmc_initialized = false;
return;
}
Serial.println("TMC2209 communicating OK");
// Базовая настройка
stepper_driver.setMicrostepsPerStep(TMC_MICROSTEPS);
stepper_driver.setRunCurrent(TMC_RUN_CURRENT_PERCENT);
stepper_driver.setHoldCurrent(TMC_HOLD_CURRENT_PERCENT);
stepper_driver.setHoldDelay(7);
stepper_driver.setStallGuardThreshold(TMC_STALL_GUARD_THRESH);
stepper_driver.enableAutomaticCurrentScaling();
stepper_driver.enableAutomaticGradientAdaptation();
// КРИТИЧНО: Отключаем StealthChop для работы moveAtVelocity()!
stepper_driver.disableStealthChop();
delay(10);
// Включаем CoolStep для энергосбережения
stepper_driver.enableCoolStep();
// Программное включение драйвера
stepper_driver.enable();
delay(100);
tmc_initialized = stepper_driver.isSetupAndCommunicating();
if (tmc_initialized) {
Serial.println("TMC2209 initialized successfully");
TMC2209::Settings settings = stepper_driver.getSettings();
Serial.printf("Run: %d%%, Hold: %d%%, Microsteps: %d, StealthChop: %s\n",
settings.irun_percent, settings.ihold_percent,
settings.microsteps_per_step, settings.stealth_chop_enabled ? "ON" : "OFF");
} else {
Serial.println("ERROR: TMC2209 setup failed!");
}
TMC_UNLOCK();
current_microsteps = TMC_MICROSTEPS;
current_run_percent = TMC_RUN_CURRENT_PERCENT;
}
void motorLoop() {
if (is_ramping) {
unsigned long now = millis();
unsigned long elapsed = now - ramp_start_ms;
if (elapsed >= RAMP_DURATION_MS) {
is_ramping = false;
current_speed_sps = end_speed_sps;
} else {
float progress = (float)elapsed / RAMP_DURATION_MS;
current_speed_sps = start_speed_sps + (end_speed_sps - start_speed_sps) * progress;
}
int32_t vactual = calculateVActual(current_speed_sps);
// Отправляем если: скорость изменилась ИЛИ это первая отправка в рампе
if (abs(vactual - last_vactual) >= 0 || !velocity_sent) {
TMC_LOCK();
stepper_driver.moveAtVelocity(vactual);
TMC_UNLOCK();
last_vactual = vactual;
velocity_sent = true;
Serial.printf("VACTUAL: %d (SPS: %.1f, RPM: %d)\n",
vactual, current_speed_sps, current_rpm_display);
}
unsigned long steps_per_rev = (unsigned long)STEPS_PER_REVOLUTION * current_microsteps;
current_rpm_display = ((unsigned long)abs(current_speed_sps) * 60) / steps_per_rev;
} else if (!velocity_sent && current_speed_sps == 0) {
// Если мотор стоит и скорость не отправлялась - отправляем 0
TMC_LOCK();
stepper_driver.moveAtVelocity(0);
TMC_UNLOCK();
velocity_sent = true;
}
if (current_speed_sps != 0) {
total_steps += (unsigned long)(abs(current_speed_sps) * 0.005);
}
vTaskDelay(pdMS_TO_TICKS(5));
}
void setTargetRPM(int rpm) {
target_rpm = rpm;
if (rpm != 0 && current_rpm_display < 5) resetSteps();
unsigned long steps_per_rev = (unsigned long)STEPS_PER_REVOLUTION * current_microsteps;
start_speed_sps = current_speed_sps;
end_speed_sps = (rpm != 0) ? ((float)rpm * steps_per_rev) / 60.0f : 0;
ramp_start_ms = millis();
is_ramping = true;
velocity_sent = false; // Сбрасываем флаг для новой отправки
Serial.printf("Target RPM: %d -> SPS: %.1f\n", rpm, end_speed_sps);
}
void resetSteps() {
total_steps = 0;
}
unsigned long getMotorSteps() { return total_steps; }
int getCurrentRPM() { return current_rpm_display; }
bool isMotorRunning() { return (abs(current_speed_sps) > 1); }
void tmcSetCurrentPercent(uint8_t run_percent, uint8_t hold_percent) {
if (!tmc_initialized) return;
TMC_LOCK();
stepper_driver.setAllCurrentValues(run_percent, hold_percent, 7);
TMC_UNLOCK();
current_run_percent = run_percent;
}
void tmcSetMicrosteps(uint16_t ms) {
if (!tmc_initialized) return;
TMC_LOCK();
stepper_driver.setMicrostepsPerStep(ms);
TMC_UNLOCK();
current_microsteps = ms;
if (target_rpm != 0) setTargetRPM(target_rpm);
}
void tmcSetStallGuard(uint8_t threshold) {
if (!tmc_initialized) return;
TMC_LOCK();
stepper_driver.setStallGuardThreshold(threshold);
TMC_UNLOCK();
}
void tmcSoftwareEnable(bool enable) {
if (!tmc_initialized) return;
TMC_LOCK();
if (enable) {
stepper_driver.enable();
// После enable нужно заново отправить скорость
velocity_sent = false;
} else {
stepper_driver.moveAtVelocity(0);
stepper_driver.disable();
last_vactual = 0;
current_speed_sps = 0;
is_ramping = false;
}
TMC_UNLOCK();
}
void tmcSetStealthChop(bool enable) {
if (!tmc_initialized) return;
TMC_LOCK();
if (enable) {
stepper_driver.enableStealthChop();
// В StealthChop VACTUAL не работает, останавливаем мотор
stepper_driver.moveAtVelocity(0);
last_vactual = 0;
current_speed_sps = 0;
} else {
stepper_driver.disableStealthChop();
velocity_sent = false;
}
TMC_UNLOCK();
}
void tmcSetCoolStep(bool enable) {
if (!tmc_initialized) return;
TMC_LOCK();
enable ? stepper_driver.enableCoolStep() : stepper_driver.disableCoolStep();
TMC_UNLOCK();
}
bool tmcIsInitialized() { return tmc_initialized; }
bool tmcIsCommunicating() {
if (!tmc_initialized) return false;
TMC_LOCK();
bool res = stepper_driver.isCommunicating();
TMC_UNLOCK();
return res;
}
// Функция проверки состояния EN_PIN
bool checkDriverStatus() {
bool en_state = (digitalRead(EN_PIN) == LOW);
return en_state;
}
// Функция проверки программного состояния TMC
bool checkTmcSoftwareEnable() {
TMC2209::Settings s = tmcGetSettings();
bool tmc_state = s.software_enabled;
return tmc_state;
}
uint16_t tmcGetMicrostepsSetting() { return current_microsteps; }
uint8_t tmcGetRunCurrentPercent() { return current_run_percent; }
TMC2209::Status tmcGetStatus() {
TMC2209::Status s = {};
if (!tmc_initialized) return s;
TMC_LOCK();
s = stepper_driver.getStatus();
TMC_UNLOCK();
return s;
}
TMC2209::Settings tmcGetSettings() {
TMC2209::Settings s = {};
if (!tmc_initialized) return s;
TMC_LOCK();
s = stepper_driver.getSettings();
TMC_UNLOCK();
return s;
}
uint16_t tmcGetStallGuardResult() {
if (!tmc_initialized) return 0;
TMC_LOCK();
uint16_t res = stepper_driver.getStallGuardResult();
TMC_UNLOCK();
return res;
}
uint32_t tmcGetInterstepDuration() {
if (!tmc_initialized) return 0;
TMC_LOCK();
uint32_t res = stepper_driver.getInterstepDuration();
TMC_UNLOCK();
return res;
}
uint16_t tmcGetMicrostepCounter() {
if (!tmc_initialized) return 0;
TMC_LOCK();
uint16_t res = stepper_driver.getMicrostepCounter();
TMC_UNLOCK();
return res;
}

View File

@@ -1,42 +0,0 @@
#ifndef MOTOR_H
#define MOTOR_H
#include <Arduino.h>
#include <TMC2209.h>
void motorInit();
void motorLoop();
// Управление движением (через UART VACTUAL)
void setTargetRPM(int rpm); // Поддерживает отрицательные значения для реверса!
void resetSteps();
// Геттеры состояния движения
unsigned long getMotorSteps(); // Считается программно
int getCurrentRPM();
bool isMotorRunning();
// Управление TMC2209 через UART
void tmcSetCurrentPercent(uint8_t run_percent, uint8_t hold_percent);
void tmcSetMicrosteps(uint16_t ms);
void tmcSetStallGuard(uint8_t threshold);
void tmcSoftwareEnable(bool enable); // Вкл/Выкл драйвер программно
void tmcSetStealthChop(bool enable);
void tmcSetCoolStep(bool enable);
// Расширенная телеметрия
bool tmcIsInitialized();
bool tmcIsCommunicating();
uint16_t tmcGetMicrostepsSetting();
uint8_t tmcGetRunCurrentPercent();
// Структуры статусов для MQTT
TMC2209::Status tmcGetStatus();
TMC2209::Settings tmcGetSettings();
uint16_t tmcGetStallGuardResult();
uint32_t tmcGetInterstepDuration();
uint16_t tmcGetMicrostepCounter();
bool checkDriverStatus();
bool checkTmcSoftwareEnable();
#endif

View File

@@ -1,506 +0,0 @@
#include "mqtt_handler.h"
#include "config.h"
#include "motor.h"
#include "servo_control.h"
#include "vl53l0x_sensor.h"
static WiFiClient espClient;
static PubSubClient client(espClient);
// Кэш телеметрии
static unsigned long last_feedback_time = 0;
static int last_pub_rpm = -1;
static unsigned long last_pub_steps = -1;
static int last_pub_is_run = -1;
// TMC Кэш
static uint16_t last_pub_sg = 65535;
static uint32_t last_pub_interstep = 0;
static uint8_t last_pub_current_pct = 255;
static uint16_t last_pub_microsteps = 0;
// Статусы (битовые флаги)
static int last_pub_over_temp = -1;
static int last_pub_short_gnd = -1;
static int last_pub_open_load = -1;
static int last_pub_stealth_active = -1;
static int last_pub_standstill = -1;
static int last_pub_driver_status = -1;
static int last_pub_tmc_software_enable = -1;
static uint8_t last_pub_current_scaling = 255;
// ============================================
// КЭШИРОВАНИЕ ДЛЯ VL53L0X
// ============================================
#define VL53L0X_MAX_CHANNELS 8
static uint16_t last_pub_vl53_distance[VL53L0X_MAX_CHANNELS] = {65535};
static uint16_t last_pub_vl53_raw[VL53L0X_MAX_CHANNELS] = {65535};
static int last_pub_vl53_status[VL53L0X_MAX_CHANNELS] = {-1};
static int last_pub_vl53_calibrated[VL53L0X_MAX_CHANNELS] = {-1};
static MeasurementMode last_pub_vl53_mode = MODE_COUNT;
// ============================================
// БУФЕРЫ ДЛЯ ПРЕОБРАЗОВАНИЯ
// ============================================
static char int_buffer[16];
static char uint_buffer[16];
static const char* intToString(int value) {
snprintf(int_buffer, sizeof(int_buffer), "%d", value);
return int_buffer;
}
static const char* uintToString(unsigned long value) {
snprintf(uint_buffer, sizeof(uint_buffer), "%lu", value);
return uint_buffer;
}
// ============================================
// WIFI И MQTT ПОДКЛЮЧЕНИЕ
// ============================================
static void setup_wifi() {
Serial.print("Connecting to WiFi");
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
vTaskDelay(pdMS_TO_TICKS(500));
Serial.print(".");
}
Serial.println("\nWiFi Connected");
}
static void resetAllCaches() {
// Motor
last_pub_rpm = -1;
last_pub_steps = (unsigned long)-1;
last_pub_is_run = -1;
last_pub_sg = 65535;
last_pub_interstep = 0;
last_pub_current_pct = 255;
last_pub_microsteps = 0;
last_pub_over_temp = -1;
last_pub_short_gnd = -1;
last_pub_open_load = -1;
last_pub_stealth_active = -1;
last_pub_standstill = -1;
last_pub_current_scaling = 255;
last_pub_driver_status = -1;
last_pub_tmc_software_enable = -1;
// VL53L0X
for (int i = 0; i < VL53L0X_MAX_CHANNELS; i++) {
last_pub_vl53_distance[i] = 65535;
last_pub_vl53_raw[i] = 65535;
last_pub_vl53_status[i] = -1;
last_pub_vl53_calibrated[i] = -1;
}
last_pub_vl53_mode = MODE_COUNT;
}
static void subscribeToAllTopics() {
// Motor
client.subscribe("motor/control/rpm");
client.subscribe("motor/control/driver");
client.subscribe("motor/control/totalsteps/reset");
client.subscribe("motor/control/tmc/current_percent");
client.subscribe("motor/control/tmc/microsteps");
client.subscribe("motor/control/tmc/stallguard");
client.subscribe("motor/control/tmc/enable");
client.subscribe("motor/control/tmc/stealthchop");
client.subscribe("motor/control/tmc/coolstep");
// Servo
client.subscribe("servo/control/+/#");
// VL53L0X
client.subscribe("sensor/control/mode");
client.subscribe("sensor/control/mode_name");
client.subscribe("sensor/control/calibrate/start/+");
client.subscribe("sensor/control/calibrate/finish/+");
client.subscribe("sensor/control/clear_cal/+");
client.subscribe("sensor/control/enable/+");
client.subscribe("sensor/control/publish_all");
}
static void reconnect() {
while (!client.connected()) {
if (client.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASS)) {
resetAllCaches();
subscribeToAllTopics();
Serial.println("MQTT Connected and subscribed");
} else {
Serial.printf("MQTT connection failed, rc=%d, retrying...\n", client.state());
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
}
// ============================================
// ОБРАБОТКА ВХОДЯЩИХ MQTT КОМАНД
// ============================================
static void callback(char* topic, byte* payload, unsigned int length) {
char msg[length + 1];
memcpy(msg, payload, length);
msg[length] = '\0';
if (strcmp(topic, "motor/control/rpm") == 0) {
setTargetRPM(atoi(msg)); // Поддерживает отрицательные для реверса!
}
else if (strcmp(topic, "motor/control/driver") == 0) {
// TMC2209: LOW = Enabled, HIGH = Disabled
bool enable = (strcmp(msg, "on") == 0);
digitalWrite(EN_PIN, enable ? LOW : HIGH);
}
else if (strcmp(topic, "motor/control/totalsteps/reset") == 0) {
resetSteps();
if (client.connected()) client.publish("motor/feedback/totalsteps", "0");
last_pub_steps = 0;
}
// --- TMC Control ---
else if (strcmp(topic, "motor/control/tmc/current_percent") == 0) {
uint8_t pct = atoi(msg);
if (pct <= 100) tmcSetCurrentPercent(pct, pct / 2); // Hold = 50% от Run
}
else if (strcmp(topic, "motor/control/tmc/microsteps") == 0) {
uint16_t ms = atoi(msg);
tmcSetMicrosteps(ms);
}
else if (strcmp(topic, "motor/control/tmc/stallguard") == 0) {
tmcSetStallGuard(atoi(msg));
}
else if (strcmp(topic, "motor/control/tmc/enable") == 0) {
tmcSoftwareEnable(strcmp(msg, "on") == 0);
}
else if (strcmp(topic, "motor/control/tmc/stealthchop") == 0) {
tmcSetStealthChop(strcmp(msg, "on") == 0);
}
else if (strcmp(topic, "motor/control/tmc/coolstep") == 0) {
tmcSetCoolStep(strcmp(msg, "on") == 0);
}
else if (strncmp(topic, "servo/control", 12) == 0) {
handleServoMQTTCommand(topic, msg);
}
// === VL53L0X ===
else if (strcmp(topic, "sensor/control/mode") == 0) {
int mode = atoi(msg);
if (mode >= 0 && mode < MODE_COUNT) {
vl53l0xSetMode((MeasurementMode)mode);
}
}
else if (strcmp(topic, "sensor/control/mode_name") == 0) {
// Маппинг имени режима на ID (если нужно)
// Пока просто логируем
Serial.printf("Mode name request: %s\n", msg);
}
else if (strncmp(topic, "sensor/control/calibrate/start/", 31) == 0) {
int channel = atoi(topic + 31);
int near_mm = atoi(msg);
if (channel >= 0 && channel < VL53L0X_MAX_CHANNELS && near_mm > 0) {
vl53l0xStartCalibration(channel, near_mm);
}
}
else if (strncmp(topic, "sensor/control/calibrate/finish/", 32) == 0) {
int channel = atoi(topic + 32);
int far_mm = atoi(msg);
if (channel >= 0 && channel < VL53L0X_MAX_CHANNELS && far_mm > 0) {
vl53l0xFinishCalibration(channel, far_mm);
}
}
else if (strncmp(topic, "sensor/control/clear_cal/", 25) == 0) {
int channel = atoi(topic + 25);
if (channel >= 0 && channel < VL53L0X_MAX_CHANNELS) {
vl53l0xClearCalibration(channel);
}
}
else if (strncmp(topic, "sensor/control/enable/", 22) == 0) {
int channel = atoi(topic + 22);
bool enable = (strcmp(msg, "on") == 0 || strcmp(msg, "1") == 0 || strcmp(msg, "true") == 0);
if (channel >= 0 && channel < VL53L0X_MAX_CHANNELS) {
vl53l0xEnableChannel(channel, enable);
}
}
else if (strcmp(topic, "sensor/control/publish_all") == 0) {
// Сбрасываем кэш для принудительной публикации
for (int i = 0; i < VL53L0X_MAX_CHANNELS; i++) {
last_pub_vl53_distance[i] = 65535;
last_pub_vl53_raw[i] = 65535;
}
}
}
// ============================================
// ПУБЛИКАЦИЯ MOTOR TELEMETRY
// ============================================
static void publishMotorTelemetry() {
// RPM
int rpm = getCurrentRPM();
if (rpm != last_pub_rpm) {
client.publish("motor/feedback/rpm", intToString(rpm));
last_pub_rpm = rpm;
}
// Steps
unsigned long steps = getMotorSteps();
if (steps != last_pub_steps) {
client.publish("motor/feedback/totalsteps", uintToString(steps));
last_pub_steps = steps;
}
// Is running
int run = isMotorRunning() ? 1 : 0;
if (run != last_pub_is_run) {
client.publish("motor/feedback/is_run", run ? "true" : "false");
last_pub_is_run = run;
}
// TMC
if (tmcIsInitialized()) {
uint8_t pct = tmcGetRunCurrentPercent();
if (pct != last_pub_current_pct) {
client.publish("motor/feedback/tmc/current_percent", intToString(pct));
last_pub_current_pct = pct;
}
uint16_t ms = tmcGetMicrostepsSetting();
if (ms != last_pub_microsteps) {
client.publish("motor/feedback/tmc/microsteps", intToString(ms));
last_pub_microsteps = ms;
}
uint16_t sg = tmcGetStallGuardResult();
if (sg != last_pub_sg) {
client.publish("motor/feedback/tmc/sg_result", intToString(sg));
last_pub_sg = sg;
}
uint32_t interstep = tmcGetInterstepDuration();
if (interstep != last_pub_interstep) {
client.publish("motor/feedback/tmc/interstep_duration", uintToString(interstep));
last_pub_interstep = interstep;
}
TMC2209::Status status = tmcGetStatus();
int ot = (status.over_temperature_warning || status.over_temperature_shutdown) ? 1 : 0;
if (ot != last_pub_over_temp) {
client.publish("motor/feedback/tmc/status/over_temp", ot ? "true" : "false");
last_pub_over_temp = ot;
}
int sgnd = (status.short_to_ground_a || status.short_to_ground_b) ? 1 : 0;
if (sgnd != last_pub_short_gnd) {
client.publish("motor/feedback/tmc/status/short_to_ground", sgnd ? "true" : "false");
last_pub_short_gnd = sgnd;
}
int ol = (status.open_load_a || status.open_load_b) ? 1 : 0;
if (ol != last_pub_open_load) {
client.publish("motor/feedback/tmc/status/open_load", ol ? "true" : "false");
last_pub_open_load = ol;
}
int sa = status.stealth_chop_mode ? 1 : 0;
if (sa != last_pub_stealth_active) {
client.publish("motor/feedback/tmc/status/stealth_chop_active", sa ? "true" : "false");
last_pub_stealth_active = sa;
}
int ss = status.standstill ? 1 : 0;
if (ss != last_pub_standstill) {
client.publish("motor/feedback/tmc/status/standstill", ss ? "true" : "false");
last_pub_standstill = ss;
}
if (status.current_scaling != last_pub_current_scaling) {
client.publish("motor/feedback/tmc/status/current_scaling", intToString(status.current_scaling));
last_pub_current_scaling = status.current_scaling;
}
int cds = checkDriverStatus() ? 1 : 0;
if (cds != last_pub_driver_status) {
client.publish("motor/feedback/driver/status", cds ? "on" : "off");
last_pub_driver_status = cds;
}
int tse = checkTmcSoftwareEnable() ? 1 : 0;
if (tse != last_pub_tmc_software_enable) {
client.publish("motor/feedback/tmc/status", tse ? "on" : "off");
last_pub_tmc_software_enable = tse;
}
}
}
// ============================================
// ПУБЛИКАЦИЯ VL53L0X TELEMETRY
// ============================================
static void publishVL53L0XTelemetry() {
// Публикация режима
MeasurementMode current_mode = vl53l0xGetMode();
if (current_mode != last_pub_vl53_mode) {
const ModeProfile* profile = vl53l0xGetModeProfile();
client.publish("sensor/feedback/mode", profile->name);
client.publish("sensor/feedback/mode_id", intToString(current_mode));
client.publish("sensor/feedback/max_range", intToString(profile->max_range_mm));
last_pub_vl53_mode = current_mode;
}
// Публикация данных с каждого канала
for (uint8_t ch = 0; ch < VL53L0X_MAX_CHANNELS; ch++) {
if (!vl53l0xIsChannelPresent(ch)) continue;
char topic[64];
// Статус канала
int status = vl53l0xIsChannelEnabled(ch) ? 1 : 0;
if (status != last_pub_vl53_status[ch]) {
snprintf(topic, sizeof(topic), "sensor/feedback/%d/status", ch);
client.publish(topic, status ? "on" : "off");
last_pub_vl53_status[ch] = status;
}
// Статус калибровки
int calibrated = vl53l0xIsCalibrated(ch) ? 1 : 0;
if (calibrated != last_pub_vl53_calibrated[ch]) {
snprintf(topic, sizeof(topic), "sensor/feedback/%d/calibrated", ch);
client.publish(topic, calibrated ? "true" : "false");
last_pub_vl53_calibrated[ch] = calibrated;
}
// Только если канал активен, публикуем расстояния
if (vl53l0xIsChannelActive(ch)) {
// Калиброванное расстояние
uint16_t distance = vl53l0xReadDistance(ch);
if (distance != last_pub_vl53_distance[ch]) {
snprintf(topic, sizeof(topic), "sensor/feedback/%d/distance", ch);
if (distance != 65535) {
client.publish(topic, intToString(distance));
} else {
client.publish(topic, "out_of_range");
}
last_pub_vl53_distance[ch] = distance;
}
// Сырое значение
uint16_t raw = vl53l0xReadRawDistance(ch);
if (raw != last_pub_vl53_raw[ch]) {
snprintf(topic, sizeof(topic), "sensor/feedback/%d/raw", ch);
if (raw != 65535) {
client.publish(topic, intToString(raw));
}
last_pub_vl53_raw[ch] = raw;
}
}
}
}
/////
void checkAndPublishServoStatus(uint8_t channel) {
if (channel >= MAX_SERVOS) return;
bool current_state = servo_enabled[channel];
if (current_state != last_published_status[channel]) {
last_published_status[channel] = current_state;
char topic[64];
snprintf(topic, sizeof(topic), "servo/%d/feedback/status", channel);
String status = current_state ? "on" : "off";
client.publish(topic, status.c_str());
Serial.printf("Published servo %d status: %s\n", channel, status.c_str());
}
}
void checkAndPublishServoAngle(uint8_t channel) {
if (channel >= MAX_SERVOS) return;
uint8_t current_angle = current_angles[channel];
if (current_angle != last_published_angles[channel]) {
last_published_angles[channel] = current_angle;
char topic[64];
snprintf(topic, sizeof(topic), "servo/%d/feedback/angle", channel);
char payload[8];
snprintf(payload, sizeof(payload), "%d", current_angle);
client.publish(topic, payload);
Serial.printf("Published servo %d angle: %d\n", channel, current_angle);
}
}
void handleServoMQTTCommand(const char* topic, const char* payload) {
// Парсим топик: servo/control/{channel}/{command}
int channel = -1;
char command[32] = {0};
if (sscanf(topic, "servo/control/%2d/%8s", &channel, command) != 2) {
Serial.printf("Invalid servo topic: %s\n", topic);
return;
}
if (channel < 0 || channel >= MAX_SERVOS) {
Serial.printf("Invalid servo channel: %d\n", channel);
return;
}
Serial.printf("Servo %d command: %s = %s\n", channel, command, payload);
if (strcmp(command, "angle") == 0) {
int angle = atoi(payload);
if (angle >= 0 && angle <= 180) {
setServoAngle(channel, (uint8_t)angle);
checkAndPublishServoAngle(channel);
checkAndPublishServoStatus(channel);
}
}
else if (strcmp(command, "enable") == 0) {
bool enable = (strcmp(payload, "on") == 0 || strcmp(payload, "1") == 0 || strcmp(payload, "true") == 0);
if (enable) {
enableServo(channel);
} else {
disableServo(channel);
}
checkAndPublishServoStatus(channel);
}
}
/////
// ============================================
// ГЛАВНЫЙ ЦИКЛ ПУБЛИКАЦИИ
// ============================================
static void publishTelemetry() {
unsigned long now = millis();
if (now - last_feedback_time >= 500) {
publishMotorTelemetry();
publishVL53L0XTelemetry();
last_feedback_time = now;
}
}
void mqttTask(void *parameter) {
static unsigned long last_stack_check = 0;
setup_wifi();
client.setServer(MQTT_SERVER, MQTT_PORT);
client.setCallback(callback);
servoInit();
for (;;) {
if (!client.connected()) reconnect();
client.loop();
publishTelemetry();
vTaskDelay(pdMS_TO_TICKS(10));
}
}

View File

@@ -1,11 +0,0 @@
#ifndef MQTT_HANDLER_H
#define MQTT_HANDLER_H
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
void mqttTask(void *parameter);
void handleServoMQTTCommand(const char* topic, const char* payload);
#endif

View File

@@ -1,98 +0,0 @@
#include "servo_control.h"
#include "mqtt_handler.h"
#include "config.h"
static Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
static bool servo_initialized = false;
void servoInit() {
Serial.println("Initializing PCA9685 servo driver...");
// Инициализация I2C на пинах 21 (SDA) и 22 (SCL)
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
pwm.begin();
pwm.setOscillatorFrequency(27000000);
pwm.setPWMFreq(50); // 50 Hz для сервоприводов
delay(10);
servo_initialized = true;
Serial.println("PCA9685 initialized successfully");
// Инициализируем все каналы как выключенные
for (int i = 0; i < MAX_SERVOS; i++) {
current_angles[i] = 90; // Начальное положение - середина
servo_enabled[i] = false;
disableServo(i);
}
}
// Преобразование угла (0-180) в длину импульса
uint16_t angleToPulse(uint8_t angle) {
if (angle > 180) angle = 180;
return map(angle, 0, 180, SERVO_MIN_PULSE, SERVO_MAX_PULSE);
}
// Преобразование длины импульса в угол
uint8_t pulseToAngle(uint16_t pulse) {
if (pulse < SERVO_MIN_PULSE) return 0;
if (pulse > SERVO_MAX_PULSE) return 180;
return map(pulse, SERVO_MIN_PULSE, SERVO_MAX_PULSE, 0, 180);
}
void setServoAngle(uint8_t channel, uint8_t angle) {
if (!servo_initialized || channel >= MAX_SERVOS) return;
if (angle > 180) angle = 180;
current_angles[channel] = angle;
servo_enabled[channel] = true;
uint16_t pulse = angleToPulse(angle);
setServoPulse(channel, pulse);
Serial.printf("Servo %d: angle=%d, pulse=%d\n", channel, angle, pulse);
}
void setServoPulse(uint8_t channel, uint16_t pulse) {
if (!servo_initialized || channel >= MAX_SERVOS) return;
// Преобразование микросекунд в тики PCA9685
// PCA9685 имеет 4096 тиков на период при 50Hz = 20000 мкс
// 1 мкс = 4096 / 20000 = 0.2048 тика
double pulselength = 4096.0 / 20000.0; // тиков на микросекунду
uint16_t ticks = pulse * pulselength;
pwm.setPWM(channel, 0, ticks);
}
void enableServo(uint8_t channel) {
if (!servo_initialized || channel >= MAX_SERVOS) return;
servo_enabled[channel] = true;
setServoAngle(channel, current_angles[channel]);
Serial.printf("Servo %d: ENABLED\n", channel);
}
void disableServo(uint8_t channel) {
if (!servo_initialized || channel >= MAX_SERVOS) return;
servo_enabled[channel] = false;
pwm.setPWM(channel, 0, 0); // Отключаем сигнал
Serial.printf("Servo %d: DISABLED\n", channel);
}
uint8_t getServoAngle(uint8_t channel) {
if (channel >= MAX_SERVOS) return 0;
return current_angles[channel];
}
bool isServoEnabled(uint8_t channel) {
if (channel >= MAX_SERVOS) return false;
return servo_enabled[channel];
}

View File

@@ -1,32 +0,0 @@
#ifndef SERVO_H
#define SERVO_H
#include <Arduino.h>
#include <Adafruit_PWMServoDriver.h>
// Хранение текущего состояния сервоприводов
#define MAX_SERVOS 16
static uint8_t current_angles[MAX_SERVOS] = {0};
static bool servo_enabled[MAX_SERVOS] = {false};
static uint8_t last_published_angles[MAX_SERVOS] = {255};
static bool last_published_status[MAX_SERVOS] = {false};
// Минимальная и максимальная длина импульса для сервопривода (в микросекундах)
static const uint16_t SERVO_MIN_PULSE = 600;
static const uint16_t SERVO_MAX_PULSE = 2400;
// Инициализация сервопривода
void servoInit();
// Управление сервоприводом
void setServoAngle(uint8_t channel, uint8_t angle);
void setServoPulse(uint8_t channel, uint16_t pulse);
void enableServo(uint8_t channel);
void disableServo(uint8_t channel);
// Получение состояния
uint8_t getServoAngle(uint8_t channel);
bool isServoEnabled(uint8_t channel);
#endif // SERVO_H

View File

@@ -1,477 +0,0 @@
#include "vl53l0x_sensor.h"
#include "config.h"
// ============================================
// КОНСТАНТЫ
// ============================================
#define TCA9548A_ADDRESS 0x70
#define TCA9548A_CHANNELS 8
#define MIN_DISTANCE_MM 30
#define OUT_OF_RANGE_VALUE 65535
#define FILTER_SIZE 5
// ============================================
// ПРОФИЛИ РЕЖИМОВ
// ============================================
static const ModeProfile MODES[MODE_COUNT] = {
{
"HIGH_ACCURACY",
200000, 14, 10, 0.5f, 500, 2
},
{
"PRECISION",
66000, 14, 10, 0.3f, 1000, 5
},
{
"DEFAULT",
33000, 14, 10, 0.25f, 1200, 15
},
{
"LONG_RANGE",
33000, 18, 14, 0.1f, 2000, 40
},
{
"ULTRA_LONG",
100000, 18, 14, 0.05f, 2500, 80
}
};
// ============================================
// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
// ============================================
static VL53L0X sensor;
static Preferences preferences;
static MeasurementMode current_mode = MODE_DEFAULT;
static bool sensor_present[TCA9548A_CHANNELS] = {false};
static bool channel_enabled[TCA9548A_CHANNELS] = {false};
static CalibrationData calibration[TCA9548A_CHANNELS];
static uint16_t filter_buffer[TCA9548A_CHANNELS][FILTER_SIZE];
static uint8_t filter_index[TCA9548A_CHANNELS] = {0};
static bool calibration_in_progress = false;
static uint8_t calibration_channel = 0;
static uint16_t calibration_near_raw = 0;
static uint16_t calibration_near_known = 0;
// ============================================
// TCA9548A МУЛЬТИПЛЕКСОР
// ============================================
static void TCA9548A_Select(uint8_t channel) {
Wire.beginTransmission(TCA9548A_ADDRESS);
Wire.write((channel < TCA9548A_CHANNELS) ? (1 << channel) : 0x00);
Wire.endTransmission();
delay(3);
}
static void TCA9548A_DisableAll() {
Wire.beginTransmission(TCA9548A_ADDRESS);
Wire.write(0x00);
Wire.endTransmission();
}
static bool checkTCA9548A() {
Wire.beginTransmission(TCA9548A_ADDRESS);
return (Wire.endTransmission() == 0);
}
// ============================================
// NVS (СОХРАНЕНИЕ В FLASH)
// ============================================
static void loadCalibration() {
preferences.begin("vl53_cal", true);
for (uint8_t ch = 0; ch < TCA9548A_CHANNELS; ch++) {
char key[16];
snprintf(key, sizeof(key), "ch%d", ch);
size_t len = preferences.getBytesLength(key);
if (len == sizeof(CalibrationData)) {
preferences.getBytes(key, &calibration[ch], sizeof(CalibrationData));
} else {
calibration[ch].valid = false;
calibration[ch].scale = 1.0f;
calibration[ch].offset = 0.0f;
}
}
int saved_mode = preferences.getInt("mode", MODE_DEFAULT);
if (saved_mode >= 0 && saved_mode < MODE_COUNT) {
current_mode = (MeasurementMode)saved_mode;
}
preferences.end();
}
static void saveCalibration(uint8_t channel) {
preferences.begin("vl53_cal", false);
char key[16];
snprintf(key, sizeof(key), "ch%d", channel);
preferences.putBytes(key, &calibration[channel], sizeof(CalibrationData));
preferences.end();
}
static void saveMode() {
preferences.begin("vl53_cal", false);
preferences.putInt("mode", (int)current_mode);
preferences.end();
}
// ============================================
// ПРИМЕНЕНИЕ РЕЖИМА
// ============================================
static bool applyModeProfile() {
const ModeProfile& profile = MODES[current_mode];
sensor.setTimeout(500);
if (!sensor.init()) {
return false;
}
sensor.setAddress(0x29);
sensor.setSignalRateLimit(profile.signal_rate_limit);
sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, profile.vcsel_prerange);
sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, profile.vcsel_final);
sensor.setMeasurementTimingBudget(profile.timing_budget_us);
return true;
}
// ============================================
// КАЛИБРОВКА И ФИЛЬТРАЦИЯ
// ============================================
static uint16_t applyCalibration(uint8_t channel, uint16_t raw_mm) {
if (!calibration[channel].valid || raw_mm == OUT_OF_RANGE_VALUE) {
return raw_mm;
}
float calibrated = (float)raw_mm * calibration[channel].scale + calibration[channel].offset;
const ModeProfile& profile = MODES[current_mode];
if (calibrated < MIN_DISTANCE_MM) calibrated = MIN_DISTANCE_MM;
if (calibrated > profile.max_range_mm) return OUT_OF_RANGE_VALUE;
return (uint16_t)calibrated;
}
static uint16_t applyFilter(uint8_t channel, uint16_t new_value) {
if (new_value == OUT_OF_RANGE_VALUE) return OUT_OF_RANGE_VALUE;
filter_buffer[channel][filter_index[channel]] = new_value;
filter_index[channel] = (filter_index[channel] + 1) % FILTER_SIZE;
uint32_t sum = 0;
uint8_t count = 0;
for (uint8_t i = 0; i < FILTER_SIZE; i++) {
if (filter_buffer[channel][i] != 0) {
sum += filter_buffer[channel][i];
count++;
}
}
return (count > 0) ? (uint16_t)(sum / count) : new_value;
}
// ============================================
// ПУБЛИЧНЫЕ ФУНКЦИИ
// ============================================
void vl53l0xInit() {
Serial.println("Initializing VL53L0X sensors...");
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
Wire.setClock(400000);
delay(100);
if (!checkTCA9548A()) {
Serial.println("ERROR: TCA9548A not found!");
return;
}
Serial.println("✓ TCA9548A found");
TCA9548A_DisableAll();
loadCalibration();
Serial.printf("✓ Loaded mode: %s\n", MODES[current_mode].name);
Serial.println("\nScanning VL53L0X channels:");
for (uint8_t channel = 0; channel < TCA9548A_CHANNELS; channel++) {
TCA9548A_Select(channel);
delay(20);
if (applyModeProfile()) {
sensor_present[channel] = true;
channel_enabled[channel] = true;
Serial.printf(" ✓ Channel %d: VL53L0X found", channel);
if (calibration[channel].valid) Serial.print(" [CALIBRATED]");
Serial.println();
memset(filter_buffer[channel], 0, sizeof(filter_buffer[channel]));
filter_index[channel] = 0;
} else {
sensor_present[channel] = false;
channel_enabled[channel] = false;
Serial.printf(" ✗ Channel %d: No device\n", channel);
}
TCA9548A_DisableAll();
delay(5);
}
Serial.printf("\n✓ VL53L0X initialized: %d sensors found\n\n",
vl53l0xGetChannelCount());
}
void vl53l0xLoop() {
// Внутренняя логика датчиков (если нужна)
// Сейчас вся публикация в mqtt_handle.cpp
}
bool vl53l0xSetMode(MeasurementMode mode) {
if (mode < 0 || mode >= MODE_COUNT) {
Serial.printf("ERROR: Invalid mode %d\n", mode);
return false;
}
current_mode = mode;
saveMode();
const ModeProfile& profile = MODES[current_mode];
Serial.printf("✓ Mode changed to: %s (max %d mm)\n",
profile.name, profile.max_range_mm);
return true;
}
MeasurementMode vl53l0xGetMode() {
return current_mode;
}
const ModeProfile* vl53l0xGetModeProfile() {
return &MODES[current_mode];
}
uint16_t vl53l0xReadDistance(uint8_t channel) {
if (channel >= TCA9548A_CHANNELS || !sensor_present[channel] || !channel_enabled[channel]) {
return OUT_OF_RANGE_VALUE;
}
TCA9548A_Select(channel);
if (!applyModeProfile()) {
TCA9548A_DisableAll();
return OUT_OF_RANGE_VALUE;
}
uint16_t distance = sensor.readRangeSingleMillimeters();
bool timeout = sensor.timeoutOccurred();
TCA9548A_DisableAll();
if (distance == 65535 || timeout) return OUT_OF_RANGE_VALUE;
const ModeProfile& profile = MODES[current_mode];
if (distance < MIN_DISTANCE_MM || distance > profile.max_range_mm) {
return OUT_OF_RANGE_VALUE;
}
uint16_t filtered = applyFilter(channel, distance);
uint16_t calibrated = applyCalibration(channel, filtered);
return calibrated;
}
uint16_t vl53l0xReadRawDistance(uint8_t channel) {
if (channel >= TCA9548A_CHANNELS || !sensor_present[channel] || !channel_enabled[channel]) {
return OUT_OF_RANGE_VALUE;
}
TCA9548A_Select(channel);
if (!applyModeProfile()) {
TCA9548A_DisableAll();
return OUT_OF_RANGE_VALUE;
}
uint16_t distance = sensor.readRangeSingleMillimeters();
bool timeout = sensor.timeoutOccurred();
TCA9548A_DisableAll();
if (distance == 65535 || timeout) return OUT_OF_RANGE_VALUE;
return distance;
}
bool vl53l0xIsChannelActive(uint8_t channel) {
return (channel < TCA9548A_CHANNELS && sensor_present[channel] && channel_enabled[channel]);
}
int vl53l0xGetChannelCount() {
int count = 0;
for (uint8_t i = 0; i < TCA9548A_CHANNELS; i++) {
if (sensor_present[i]) count++;
}
return count;
}
bool vl53l0xStartCalibration(uint8_t channel, uint16_t near_known_mm) {
if (channel >= TCA9548A_CHANNELS || !sensor_present[channel]) {
Serial.printf("ERROR: Channel %d not available\n", channel);
return false;
}
Serial.printf("Starting calibration for channel %d (near point: %d mm)\n",
channel, near_known_mm);
uint32_t sum = 0;
uint8_t valid = 0;
for (uint8_t i = 0; i < 20; i++) {
uint16_t d = vl53l0xReadRawDistance(channel);
if (d != OUT_OF_RANGE_VALUE) {
sum += d;
valid++;
}
delay(50);
}
if (valid == 0) {
Serial.println("ERROR: Failed to read near point");
return false;
}
calibration_near_raw = (uint16_t)(sum / valid);
calibration_near_known = near_known_mm;
calibration_channel = channel;
calibration_in_progress = true;
Serial.printf("✓ Near point captured: raw=%d mm, actual=%d mm\n",
calibration_near_raw, calibration_near_known);
Serial.println("Now place object at FAR point and call vl53l0xFinishCalibration()");
return true;
}
bool vl53l0xFinishCalibration(uint8_t channel, uint16_t far_known_mm) {
if (!calibration_in_progress || channel != calibration_channel) {
Serial.println("ERROR: Calibration not in progress or wrong channel");
return false;
}
Serial.printf("Finishing calibration for channel %d (far point: %d mm)\n",
channel, far_known_mm);
uint32_t sum = 0;
uint8_t valid = 0;
for (uint8_t i = 0; i < 20; i++) {
uint16_t d = vl53l0xReadRawDistance(channel);
if (d != OUT_OF_RANGE_VALUE) {
sum += d;
valid++;
}
delay(50);
}
if (valid == 0) {
Serial.println("ERROR: Failed to read far point");
calibration_in_progress = false;
return false;
}
uint16_t far_raw = (uint16_t)(sum / valid);
Serial.printf("✓ Far point captured: raw=%d mm, actual=%d mm\n",
far_raw, far_known_mm);
if (far_raw == calibration_near_raw) {
Serial.println("ERROR: Raw values are identical");
calibration_in_progress = false;
return false;
}
float scale = (float)(far_known_mm - calibration_near_known) /
(float)(far_raw - calibration_near_raw);
float offset = (float)calibration_near_known -
(float)calibration_near_raw * scale;
calibration[channel].valid = true;
calibration[channel].near_raw = calibration_near_raw;
calibration[channel].near_known = calibration_near_known;
calibration[channel].far_raw = far_raw;
calibration[channel].far_known = far_known_mm;
calibration[channel].scale = scale;
calibration[channel].offset = offset;
saveCalibration(channel);
Serial.printf("✓ Calibration complete: scale=%.5f, offset=%.2f\n", scale, offset);
calibration_in_progress = false;
return true;
}
void vl53l0xClearCalibration(uint8_t channel) {
if (channel >= TCA9548A_CHANNELS) return;
calibration[channel].valid = false;
calibration[channel].scale = 1.0f;
calibration[channel].offset = 0.0f;
saveCalibration(channel);
Serial.printf("✓ Channel %d calibration cleared\n", channel);
}
bool vl53l0xIsCalibrated(uint8_t channel) {
return (channel < TCA9548A_CHANNELS && calibration[channel].valid);
}
CalibrationData vl53l0xGetCalibration(uint8_t channel) {
if (channel >= TCA9548A_CHANNELS) {
CalibrationData empty = {false, 0, 0, 0, 0, 1.0f, 0.0f};
return empty;
}
return calibration[channel];
}
void vl53l0xEnableChannel(uint8_t channel, bool enable) {
if (channel >= TCA9548A_CHANNELS) return;
if (!sensor_present[channel]) {
Serial.printf("ERROR: Channel %d not present\n", channel);
return;
}
channel_enabled[channel] = enable;
Serial.printf("✓ Channel %d %s\n", channel, enable ? "enabled" : "disabled");
}
bool vl53l0xIsChannelEnabled(uint8_t channel) {
return (channel < TCA9548A_CHANNELS && channel_enabled[channel]);
}
bool vl53l0xIsChannelPresent(uint8_t channel) {
return (channel < TCA9548A_CHANNELS && sensor_present[channel]);
}
void sensorTask(void *parameter) {
vl53l0xInit();
for (;;) {
vl53l0xLoop();
vTaskDelay(pdMS_TO_TICKS(50));
}
}

View File

@@ -1,68 +0,0 @@
#ifndef VL53L0X_SENSOR_H
#define VL53L0X_SENSOR_H
#include <Arduino.h>
#include <VL53L0X.h>
#include <Wire.h>
#include <Preferences.h>
// Режимы измерения
enum MeasurementMode {
MODE_HIGH_ACCURACY = 0,
MODE_PRECISION = 1,
MODE_DEFAULT = 2,
MODE_LONG_RANGE = 3,
MODE_ULTRA_LONG = 4,
MODE_COUNT = 5
};
struct ModeProfile {
const char* name;
uint32_t timing_budget_us;
uint8_t vcsel_prerange;
uint8_t vcsel_final;
float signal_rate_limit;
uint16_t max_range_mm;
uint8_t accuracy_mm;
};
struct CalibrationData {
bool valid;
uint16_t near_raw;
uint16_t near_known;
uint16_t far_raw;
uint16_t far_known;
float scale;
float offset;
};
// Инициализация и цикл
void vl53l0xInit();
void vl53l0xLoop();
// Управление режимами
bool vl53l0xSetMode(MeasurementMode mode);
MeasurementMode vl53l0xGetMode();
const ModeProfile* vl53l0xGetModeProfile();
// Чтение данных
uint16_t vl53l0xReadDistance(uint8_t channel);
uint16_t vl53l0xReadRawDistance(uint8_t channel);
bool vl53l0xIsChannelActive(uint8_t channel);
int vl53l0xGetChannelCount();
// Калибровка
bool vl53l0xStartCalibration(uint8_t channel, uint16_t near_known_mm);
bool vl53l0xFinishCalibration(uint8_t channel, uint16_t far_known_mm);
void vl53l0xClearCalibration(uint8_t channel);
bool vl53l0xIsCalibrated(uint8_t channel);
CalibrationData vl53l0xGetCalibration(uint8_t channel);
// Управление каналами
void vl53l0xEnableChannel(uint8_t channel, bool enable);
bool vl53l0xIsChannelEnabled(uint8_t channel);
bool vl53l0xIsChannelPresent(uint8_t channel);
void sensorTask(void *parameter);
#endif // VL53L0X_SENSOR_H

View File

@@ -1,11 +0,0 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html

View File

@@ -1 +0,0 @@
/testing

View File

@@ -1,61 +0,0 @@
# Интерфейс управления
![](img/gui_interface.png)
## Область Управления
`Целевой RPM` - Задается уставка скорости в диапозоне от -1000 до 1000 оборотов в минуту
`Ток` - Уставка тока в диапозоне 0 - 100%
`StallGuard` -
`Микрошаг` - Уставка микрошага
`Сброс счетчика шагов` - Сброс счетчика шагов
## Область Режимы
`Аппаратное вкл. (Driver EN)` - Аппаратное Включение/Выключение драйвера tmc2209
`Программное вкл. (TMC Chip)` - Программное Включение/Выключение драйвера tmc2209
`StealthChop (Тихий)` - Включение/Выключение тихого режима работы шагового двигателя.
`CoolStep (Энергосбер.)` - Включение/Выключение энергосберегающего режима работы шагового двигателя.
## Область Телеметрия и Статусы
`Текущий RPM (Факт)`
`Всего шагов`
`Вращение`
`SG Result` - Для отслеживания нагрузки на вал или момента срыва шагов. Резкое падение значения sg_result при движении обычно означает столкновение или заклинивание механизма.
`Interstep (ns)`
`Current Scaling`
## Область Ошибки и Флаги
`Перегрев`
`КЗ на землю`
`Обрыв нагрузки`
`StealthChop активен`
`Остановка (Standstill)`
![](img/servo_interface.png)
## Область Управление Серво
Тут можно задать угол сервопривода и отключить сервопривод
## Облать Статус и телеметрия
Вывод информации о сервоприводе включен или выключен. Так же выведен текущий угол.

View File

@@ -1,6 +0,0 @@
#!/bin/bash
python -m venv testing
source testing/bin/activate
pip install -r requirements.txt

View File

@@ -1,460 +0,0 @@
import customtkinter as ctk
import paho.mqtt.client as mqtt
# ================= НАСТРОЙКИ =================
MQTT_BROKER = "192.168.0.200"
MQTT_PORT = 1883
MQTT_USER = "test"
MQTT_PASSWORD = "1234"
MAX_SERVOS = 4
MAX_SENSORS = 8 # Количество каналов VL53L0X
# Цвета
COLOR_OK = "#28a745"
COLOR_ERR = "#dc3545"
COLOR_OFF = "#555555"
COLOR_ACTIVE = "#00d2ff"
COLOR_PENDING = "#ffaa00"
COLOR_WARN = "#ff9900" # Для out_of_range
class MotorSCADA:
def __init__(self):
ctk.set_appearance_mode("Dark")
ctk.set_default_color_theme("blue")
self.root = ctk.CTk()
self.root.title("🚀 Motor, Servo & Sensor SCADA")
self.root.geometry("1200x900")
self.root.minsize(1100, 800)
# Флаги ожидания
self.driver_pending = False
self.tmc_pending = False
self.servo_pending = {i: False for i in range(MAX_SERVOS)}
self.sensor_pending = {i: False for i in range(MAX_SENSORS)}
self.setup_gui()
self.setup_mqtt()
def setup_gui(self):
# --- Шапка ---
header = ctk.CTkFrame(self.root, height=60)
header.pack(fill="x", padx=20, pady=(20, 10))
header.pack_propagate(False)
ctk.CTkLabel(header, text="Motor, Servo & Sensor SCADA", font=ctk.CTkFont(size=24, weight="bold")).pack(side="left", padx=20)
self.lbl_status = ctk.CTkLabel(header, text="● Отключено", text_color=COLOR_ERR, font=ctk.CTkFont(size=16, weight="bold"))
self.lbl_status.pack(side="right", padx=20)
# --- Вкладки ---
self.tabview = ctk.CTkTabview(self.root)
self.tabview.pack(fill="both", expand=True, padx=20, pady=10)
self.tab_motor = self.tabview.add("Шаговый двигатель (TMC2209)")
self.tab_servo = self.tabview.add(f"Сервоприводы (0-{MAX_SERVOS-1})")
self.tab_sensor = self.tabview.add(f"Датчики VL53L0X (0-{MAX_SENSORS-1})")
self.create_motor_tab(self.tab_motor)
self.create_servo_tab(self.tab_servo)
self.create_sensor_tab(self.tab_sensor)
# ================= ВКЛАДКА ШАГОВОГО ДВИГАТЕЛЯ =================
# (Код для мотора остался без изменений, чтобы не раздувать ответ,
# но в реальном файле он должен быть здесь полностью)
def create_motor_tab(self, parent):
grid = ctk.CTkFrame(parent, fg_color="transparent")
grid.pack(fill="both", expand=True)
grid.grid_columnconfigure((0, 1, 2), weight=1, uniform="col")
grid.grid_rowconfigure(0, weight=1)
self.create_control_frame(grid)
self.create_modes_frame(grid)
self.create_telemetry_frame(grid)
def create_control_frame(self, parent):
frame = ctk.CTkFrame(parent)
frame.grid(row=0, column=0, sticky="nsew", padx=(0, 10))
ctk.CTkLabel(frame, text="⚙️ Управление", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=(10, 20))
ctk.CTkLabel(frame, text="Целевой RPM:").pack(anchor="w", padx=20)
rpm_frame = ctk.CTkFrame(frame, fg_color="transparent")
rpm_frame.pack(fill="x", padx=20, pady=5)
self.sld_rpm = ctk.CTkSlider(rpm_frame, from_=-1000, to=1000, command=self.on_rpm_slider_change)
self.sld_rpm.pack(side="left", fill="x", expand=True, padx=(0, 10))
self.ent_rpm = ctk.CTkEntry(rpm_frame, width=80, justify="right")
self.ent_rpm.insert(0, "0"); self.ent_rpm.pack(side="left", padx=(0, 10))
self.ent_rpm.bind("<Return>", self.on_rpm_entry_apply); self.ent_rpm.bind("<FocusOut>", self.on_rpm_entry_apply)
self.lbl_rpm_val = ctk.CTkLabel(rpm_frame, text="0", width=50); self.lbl_rpm_val.pack(side="right")
ctk.CTkLabel(frame, text="Ток (%):").pack(anchor="w", padx=20, pady=(15,0))
cur_frame = ctk.CTkFrame(frame, fg_color="transparent")
cur_frame.pack(fill="x", padx=20, pady=5)
self.sld_current = ctk.CTkSlider(cur_frame, from_=0, to=100, command=self.on_current_change)
self.sld_current.pack(side="left", fill="x", expand=True)
self.lbl_cur_val = ctk.CTkLabel(cur_frame, text="50", width=50); self.lbl_cur_val.pack(side="right", padx=(10, 0))
ctk.CTkLabel(frame, text="StallGuard (0-255):").pack(anchor="w", padx=20, pady=(15,0))
self.ent_sg = ctk.CTkEntry(frame, width=100); self.ent_sg.insert(0, "0")
self.ent_sg.pack(anchor="w", padx=20, pady=5)
ctk.CTkButton(frame, text="Применить SG", width=150, command=self.on_sg_apply).pack(pady=5)
ctk.CTkLabel(frame, text="Микрошаги:").pack(anchor="w", padx=20, pady=(15,0))
self.opt_msteps = ctk.CTkOptionMenu(frame, values=["1", "2", "4", "8", "16", "32", "64", "128", "256"], command=self.on_msteps_change)
self.opt_msteps.set("16"); self.opt_msteps.pack(anchor="w", padx=20, pady=5)
ctk.CTkButton(frame, text="Сбросить счетчик шагов", fg_color="#dc3545", hover_color="#b02a37", command=self.on_reset_steps).pack(pady=20)
def create_modes_frame(self, parent):
frame = ctk.CTkFrame(parent)
frame.grid(row=0, column=1, sticky="nsew", padx=10)
ctk.CTkLabel(frame, text="🔌 Режимы и Включение", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=(10, 20))
self.sw_driver, self.led_driver_fb = self.create_switch_with_feedback(frame, "Аппаратное вкл. (Driver EN)", self.on_driver_change)
self.sw_tmc_enable, self.led_tmc_fb = self.create_switch_with_feedback(frame, "Программное вкл. (TMC Chip)", self.on_tmc_enable_change)
ctk.CTkFrame(frame, height=2, fg_color="#4a4a6a").pack(fill="x", padx=20, pady=15)
self.sw_stealth = self.create_switch(frame, "StealthChop (Тихий)", self.on_stealth_change)
self.sw_cool = self.create_switch(frame, "CoolStep (Энергосбер.)", self.on_cool_change)
def create_telemetry_frame(self, parent):
frame = ctk.CTkFrame(parent)
frame.grid(row=0, column=2, sticky="nsew", padx=(10, 0))
ctk.CTkLabel(frame, text="📊 Телеметрия и Статусы", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=(10, 10))
tel_frame = ctk.CTkFrame(frame); tel_frame.pack(fill="x", padx=10, pady=5)
self.lbl_fb_rpm = self.create_telemetry_row(tel_frame, "Текущий RPM:")
self.lbl_fb_steps = self.create_telemetry_row(tel_frame, "Всего шагов:")
self.lbl_fb_run = self.create_telemetry_row(tel_frame, "Вращение:")
self.lbl_fb_sg = self.create_telemetry_row(tel_frame, "SG Result:")
self.lbl_fb_interstep = self.create_telemetry_row(tel_frame, "Interstep:")
self.lbl_fb_cscale = self.create_telemetry_row(tel_frame, "Current Scaling:")
ctk.CTkLabel(frame, text="🚨 Ошибки и Флаги", font=ctk.CTkFont(size=16, weight="bold")).pack(pady=(15, 5))
stat_frame = ctk.CTkFrame(frame); stat_frame.pack(fill="x", padx=10, pady=5)
self.led_over_temp = self.create_led_row(stat_frame, "Перегрев:")
self.led_short_gnd = self.create_led_row(stat_frame, "КЗ на землю:")
self.led_open_load = self.create_led_row(stat_frame, "Обрыв нагрузки:")
self.led_stealth_act = self.create_led_row(stat_frame, "StealthChop активен:")
self.led_standstill = self.create_led_row(stat_frame, "Остановка:")
# ================= ВКЛАДКА СЕРВОПРИВОДОВ =================
def create_servo_tab(self, parent):
grid = ctk.CTkFrame(parent, fg_color="transparent")
grid.pack(fill="both", expand=True, padx=10, pady=10)
grid.grid_columnconfigure((0, 1), weight=1, uniform="col")
grid.grid_rowconfigure((0, 1), weight=1, uniform="row")
self.servo_ui = {}
for i in range(MAX_SERVOS):
row, col = divmod(i, 2)
frame = ctk.CTkFrame(grid)
frame.grid(row=row, column=col, sticky="nsew", padx=10, pady=10)
self.servo_ui[i] = self.create_servo_card(frame, i)
def create_servo_card(self, parent, channel):
ui = {}
ctk.CTkLabel(parent, text=f"🦾 Сервопривод #{channel}", font=ctk.CTkFont(size=16, weight="bold")).pack(pady=(10, 10))
ctk.CTkLabel(parent, text="Угол (0-180°):").pack(anchor="w", padx=20)
ang_frame = ctk.CTkFrame(parent, fg_color="transparent"); ang_frame.pack(fill="x", padx=20, pady=5)
sld = ctk.CTkSlider(ang_frame, from_=0, to=180, command=lambda val, ch=channel: self.on_servo_ang_slider(ch, val))
sld.pack(side="left", fill="x", expand=True, padx=(0, 10)); sld.set(90)
ent = ctk.CTkEntry(ang_frame, width=60, justify="right"); ent.insert(0, "90"); ent.pack(side="left", padx=(0, 10))
ent.bind("<Return>", lambda event, ch=channel: self.on_servo_ang_entry(ch, event))
ent.bind("<FocusOut>", lambda event, ch=channel: self.on_servo_ang_entry(ch, event))
lbl_val = ctk.CTkLabel(ang_frame, text="90", width=40); lbl_val.pack(side="right")
ui['slider_ang'], ui['entry_ang'], ui['lbl_ang_val'] = sld, ent, lbl_val
sw, led_fb = self.create_switch_with_feedback(parent, "Включить серво", lambda ch=channel: self.on_servo_enable_change(ch))
ui['switch_en'], ui['led_fb'] = sw, led_fb
tel_frame = ctk.CTkFrame(parent); tel_frame.pack(fill="x", padx=10, pady=15)
ui['lbl_fb_ang'] = self.create_telemetry_row(tel_frame, "Текущий угол:")
stat_frame = ctk.CTkFrame(parent); stat_frame.pack(fill="x", padx=10, pady=5)
ui['led_status'] = self.create_led_row(stat_frame, "Статус:")
return ui
# ================= ВКЛАДКА ДАТЧИКОВ VL53L0X =================
def create_sensor_tab(self, parent):
main_frame = ctk.CTkFrame(parent, fg_color="transparent")
main_frame.pack(fill="both", expand=True, padx=10, pady=10)
# --- Глобальное управление ---
global_frame = ctk.CTkFrame(main_frame)
global_frame.pack(fill="x", padx=10, pady=(0, 10))
ctk.CTkLabel(global_frame, text="📏 Глобальные настройки VL53L0X", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=(10, 5))
info_frame = ctk.CTkFrame(global_frame, fg_color="transparent")
info_frame.pack(fill="x", padx=20, pady=10)
self.lbl_sensor_mode = self.create_telemetry_row(info_frame, "Режим:")
self.lbl_sensor_mode_id = self.create_telemetry_row(info_frame, "ID режима:")
self.lbl_sensor_max_range = self.create_telemetry_row(info_frame, "Макс. дальность (мм):")
ctrl_frame = ctk.CTkFrame(global_frame, fg_color="transparent")
ctrl_frame.pack(fill="x", padx=20, pady=(0, 10))
ctk.CTkLabel(ctrl_frame, text="Выбрать режим:").pack(side="left", padx=(0, 10))
# Предполагаем, что режимов от 0 до 4 (Default, HighAccuracy, LongRange, HighSpeed)
self.opt_sensor_mode = ctk.CTkOptionMenu(ctrl_frame, values=["0", "1", "2", "3", "4"], width=100, command=self.on_sensor_mode_change)
self.opt_sensor_mode.set("0"); self.opt_sensor_mode.pack(side="left", padx=(0, 20))
ctk.CTkButton(ctrl_frame, text="🔄 Принудительно обновить все", fg_color="#007bff", hover_color="#0056b3", command=self.on_sensor_publish_all).pack(side="right")
# --- Сетка каналов (Scrollable) ---
scroll_frame = ctk.CTkScrollableFrame(main_frame)
scroll_frame.pack(fill="both", expand=True, padx=10, pady=10)
scroll_frame.grid_columnconfigure((0, 1, 2, 3), weight=1, uniform="col")
self.sensor_ui = {}
for i in range(MAX_SENSORS):
row, col = divmod(i, 4)
frame = ctk.CTkFrame(scroll_frame)
frame.grid(row=row, column=col, sticky="nsew", padx=5, pady=5)
self.sensor_ui[i] = self.create_sensor_card(frame, i)
def create_sensor_card(self, parent, channel):
ui = {}
ctk.CTkLabel(parent, text=f"📡 Канал #{channel}", font=ctk.CTkFont(size=14, weight="bold")).pack(pady=(5, 5))
# Включение и калибровка
sw, led_fb = self.create_switch_with_feedback(parent, "Включить", lambda ch=channel: self.on_sensor_enable_change(ch))
ui['switch_en'], ui['led_fb'] = sw, led_fb
cal_stat_frame = ctk.CTkFrame(parent, fg_color="transparent")
cal_stat_frame.pack(fill="x", padx=10, pady=5)
ctk.CTkLabel(cal_stat_frame, text="Калибровка:").pack(side="left")
ui['led_calibrated'] = ctk.CTkLabel(cal_stat_frame, text="", font=ctk.CTkFont(size=16), text_color=COLOR_OFF)
ui['led_calibrated'].pack(side="right")
# Поля калибровки
cal_ctrl_frame = ctk.CTkFrame(parent, fg_color="transparent")
cal_ctrl_frame.pack(fill="x", padx=10, pady=5)
ctk.CTkLabel(cal_ctrl_frame, text="Ближняя (мм):").pack(anchor="w")
ent_near = ctk.CTkEntry(cal_ctrl_frame, width=60, justify="right"); ent_near.insert(0, "50"); ent_near.pack(side="left", padx=(0, 5))
btn_start = ctk.CTkButton(cal_ctrl_frame, text="Старт", width=60, height=28, command=lambda ch=channel, e=ent_near: self.on_sensor_cal_start(ch, e))
btn_start.pack(side="right")
ctk.CTkLabel(cal_ctrl_frame, text="Дальняя (мм):").pack(anchor="w", pady=(5,0))
ent_far = ctk.CTkEntry(cal_ctrl_frame, width=60, justify="right"); ent_far.insert(0, "500"); ent_far.pack(side="left", padx=(0, 5), pady=(5,0))
btn_finish = ctk.CTkButton(cal_ctrl_frame, text="Финиш", width=60, height=28, command=lambda ch=channel, e=ent_far: self.on_sensor_cal_finish(ch, e))
btn_finish.pack(side="right")
ctk.CTkButton(parent, text="Сбросить калибровку", fg_color="#6c757d", hover_color="#5a6268", height=28, command=lambda ch=channel: self.on_sensor_clear_cal(ch)).pack(pady=5)
ui['ent_near'], ui['ent_far'] = ent_near, ent_far
# Телеметрия
tel_frame = ctk.CTkFrame(parent)
tel_frame.pack(fill="x", padx=5, pady=5)
ui['lbl_dist'] = self.create_telemetry_row(tel_frame, "Дист. (мм):")
ui['lbl_raw'] = self.create_telemetry_row(tel_frame, "Сырое (мм):")
return ui
# ================= ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ GUI =================
def create_switch(self, parent, text, command):
frame = ctk.CTkFrame(parent, fg_color="transparent"); frame.pack(fill="x", padx=10, pady=5)
ctk.CTkLabel(frame, text=text).pack(side="left")
switch = ctk.CTkSwitch(frame, text="", command=command); switch.pack(side="right")
return switch
def create_switch_with_feedback(self, parent, text, command):
frame = ctk.CTkFrame(parent, fg_color="transparent"); frame.pack(fill="x", padx=10, pady=5)
ctk.CTkLabel(frame, text=text).pack(side="left")
feedback_led = ctk.CTkLabel(frame, text="", font=ctk.CTkFont(size=18), text_color=COLOR_OFF)
feedback_led.pack(side="right", padx=(10, 0))
switch = ctk.CTkSwitch(frame, text="", command=command); switch.pack(side="right", padx=(10, 0))
return switch, feedback_led
def create_telemetry_row(self, parent, text):
frame = ctk.CTkFrame(parent, fg_color="transparent"); frame.pack(fill="x", pady=2)
ctk.CTkLabel(frame, text=text, anchor="w", font=ctk.CTkFont(size=12)).pack(side="left")
val_lbl = ctk.CTkLabel(frame, text="-", font=ctk.CTkFont(weight="bold", size=12), text_color=COLOR_ACTIVE, anchor="e")
val_lbl.pack(side="right")
return val_lbl
def create_led_row(self, parent, text):
frame = ctk.CTkFrame(parent, fg_color="transparent"); frame.pack(fill="x", pady=2)
ctk.CTkLabel(frame, text=text, anchor="w", font=ctk.CTkFont(size=12)).pack(side="left")
led_lbl = ctk.CTkLabel(frame, text="", font=ctk.CTkFont(size=16), text_color=COLOR_OFF)
led_lbl.pack(side="right")
return led_lbl
# ================= MQTT =================
def setup_mqtt(self):
self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="python_scada")
self.client.username_pw_set(MQTT_USER, MQTT_PASSWORD)
self.client.on_connect = self.on_mqtt_connect
self.client.on_disconnect = self.on_mqtt_disconnect
self.client.on_message = self.on_mqtt_message
try:
self.client.connect(MQTT_BROKER, MQTT_PORT, 60)
self.client.loop_start()
except Exception as e:
print(f"Ошибка подключения: {e}")
self.update_status(False)
def on_mqtt_connect(self, client, userdata, flags, reason_code, properties):
if reason_code == 0:
self.root.after(0, self.update_status, True)
client.subscribe("motor/feedback/#")
client.subscribe("servo/+/feedback/#")
client.subscribe("sensor/feedback/#") # Подписка на датчики
else:
self.root.after(0, self.update_status, False)
def on_mqtt_disconnect(self, client, userdata, flags, reason_code, properties):
self.root.after(0, self.update_status, False)
def on_mqtt_message(self, client, userdata, msg):
self.root.after(0, self.process_feedback, msg.topic, msg.payload.decode('utf-8'))
def process_feedback(self, topic, val):
parts = topic.split('/')
# --- MOTOR ---
if parts[0] == 'motor' and parts[1] == 'feedback':
if topic == "motor/feedback/rpm": self.lbl_fb_rpm.configure(text=val)
elif topic == "motor/feedback/totalsteps": self.lbl_fb_steps.configure(text=val)
elif topic == "motor/feedback/is_run":
is_run = val == "true"
self.lbl_fb_run.configure(text="Да" if is_run else "Нет", text_color=COLOR_OK if is_run else COLOR_ERR)
elif topic == "motor/feedback/tmc/current_percent": self.lbl_cur_val.configure(text=val); self.sld_current.set(int(val))
elif topic == "motor/feedback/tmc/microsteps": self.opt_msteps.set(val)
elif topic == "motor/feedback/tmc/sg_result": self.lbl_fb_sg.configure(text=val)
elif topic == "motor/feedback/tmc/interstep_duration": self.lbl_fb_interstep.configure(text=val)
elif topic == "motor/feedback/tmc/status/current_scaling": self.lbl_fb_cscale.configure(text=val)
elif topic == "motor/feedback/tmc/status/over_temp": self.update_led(self.led_over_temp, val, True)
elif topic == "motor/feedback/tmc/status/short_to_ground": self.update_led(self.led_short_gnd, val, True)
elif topic == "motor/feedback/tmc/status/open_load": self.update_led(self.led_open_load, val, True)
elif topic == "motor/feedback/tmc/status/stealth_chop_active": self.update_led(self.led_stealth_act, val, False)
elif topic == "motor/feedback/tmc/status/standstill": self.update_led(self.led_standstill, val, False)
elif topic == "motor/feedback/driver/status":
is_on = val == "on"; self.driver_pending = False
if self.sw_driver.get() != is_on: self.sw_driver.select() if is_on else self.sw_driver.deselect()
self.led_driver_fb.configure(text_color=COLOR_OK if is_on else COLOR_OFF)
elif topic == "motor/feedback/tmc/status":
is_on = val == "on"; self.tmc_pending = False
if self.sw_tmc_enable.get() != is_on: self.sw_tmc_enable.select() if is_on else self.sw_tmc_enable.deselect()
self.led_tmc_fb.configure(text_color=COLOR_OK if is_on else COLOR_OFF)
# --- SERVO ---
elif parts[0] == 'servo' and len(parts) == 4 and parts[2] == 'feedback':
try:
ch = int(parts[1]); param = parts[3]
if ch in self.servo_ui:
ui = self.servo_ui[ch]
if param == 'angle': ui['lbl_fb_ang'].configure(text=val)
elif param == 'status':
is_on = val == "on"; self.servo_pending[ch] = False
if ui['switch_en'].get() != is_on: ui['switch_en'].select() if is_on else ui['switch_en'].deselect()
ui['led_fb'].configure(text_color=COLOR_OK if is_on else COLOR_OFF)
self.update_led(ui['led_status'], val, False)
except ValueError: pass
# --- SENSOR ---
elif parts[0] == 'sensor' and parts[1] == 'feedback':
if len(parts) == 3: # Глобальные sensor/feedback/mode...
param = parts[2]
if param == 'mode': self.lbl_sensor_mode.configure(text=val)
elif param == 'mode_id': self.lbl_sensor_mode_id.configure(text=val); self.opt_sensor_mode.set(val)
elif param == 'max_range': self.lbl_sensor_max_range.configure(text=val)
elif len(parts) == 4: # Канальные sensor/feedback/{ch}/...
try:
ch = int(parts[2]); param = parts[3]
if ch in self.sensor_ui:
ui = self.sensor_ui[ch]
if param == 'status':
is_on = val == "on"; self.sensor_pending[ch] = False
if ui['switch_en'].get() != is_on: ui['switch_en'].select() if is_on else ui['switch_en'].deselect()
ui['led_fb'].configure(text_color=COLOR_OK if is_on else COLOR_OFF)
elif param == 'calibrated':
is_cal = val == "true"
ui['led_calibrated'].configure(text_color=COLOR_OK if is_cal else COLOR_OFF)
elif param == 'distance':
if val == "out_of_range":
ui['lbl_dist'].configure(text="Вне диапазона", text_color=COLOR_WARN)
else:
ui['lbl_dist'].configure(text=val, text_color=COLOR_ACTIVE)
elif param == 'raw':
ui['lbl_raw'].configure(text=val, text_color=COLOR_ACTIVE)
except ValueError: pass
# ================= ОБРАБОТЧИКИ СОБЫТИЙ =================
def publish(self, topic, payload):
if self.client.is_connected(): self.client.publish(topic, str(payload), qos=1)
def update_status(self, is_online):
self.lbl_status.configure(text="● Подключено" if is_online else "● Отключено", text_color=COLOR_OK if is_online else COLOR_ERR)
def update_led(self, label, val, is_error):
is_true = val in ["true", "1", "on"]
label.configure(text_color=COLOR_ERR if (is_true and is_error) else (COLOR_OK if is_true else COLOR_OFF))
# --- Motor Handlers ---
def on_rpm_slider_change(self, value):
int_val = int(value); self.ent_rpm.delete(0, ctk.END); self.ent_rpm.insert(0, str(int_val))
self.lbl_rpm_val.configure(text=str(int_val)); self.publish("motor/control/rpm", int_val)
def on_rpm_entry_apply(self, event=None):
try:
int_val = max(-1000, min(1000, int(self.ent_rpm.get())))
self.sld_rpm.set(int_val); self.lbl_rpm_val.configure(text=str(int_val)); self.publish("motor/control/rpm", int_val)
except ValueError: self.ent_rpm.delete(0, ctk.END); self.ent_rpm.insert(0, str(int(self.sld_rpm.get())))
def on_current_change(self, value):
int_val = int(value); self.lbl_cur_val.configure(text=str(int_val)); self.publish("motor/control/tmc/current_percent", int_val)
def on_sg_apply(self):
val = self.ent_sg.get()
if val.isdigit() and 0 <= int(val) <= 255: self.publish("motor/control/tmc/stallguard", int(val))
def on_msteps_change(self, choice): self.publish("motor/control/tmc/microsteps", int(choice))
def on_reset_steps(self): self.publish("motor/control/totalsteps/reset", "1")
def on_driver_change(self):
is_on = self.sw_driver.get(); self.driver_pending = True; self.led_driver_fb.configure(text_color=COLOR_PENDING)
self.publish("motor/control/driver", "on" if is_on else "off")
def on_tmc_enable_change(self):
is_on = self.sw_tmc_enable.get(); self.tmc_pending = True; self.led_tmc_fb.configure(text_color=COLOR_PENDING)
self.publish("motor/control/tmc/enable", "on" if is_on else "off")
def on_stealth_change(self): self.publish("motor/control/tmc/stealthchop", "on" if self.sw_stealth.get() else "off")
def on_cool_change(self): self.publish("motor/control/tmc/coolstep", "on" if self.sw_cool.get() else "off")
# --- Servo Handlers ---
def on_servo_ang_slider(self, channel, value):
int_val = int(value); ui = self.servo_ui[channel]
ui['entry_ang'].delete(0, ctk.END); ui['entry_ang'].insert(0, str(int_val))
ui['lbl_ang_val'].configure(text=str(int_val)); self.publish(f"servo/control/{channel}/angle", int_val)
def on_servo_ang_entry(self, channel, event=None):
ui = self.servo_ui[channel]
try:
int_val = max(0, min(180, int(ui['entry_ang'].get())))
ui['slider_ang'].set(int_val); ui['lbl_ang_val'].configure(text=str(int_val)); self.publish(f"servo/control/{channel}/angle", int_val)
except ValueError: ui['entry_ang'].delete(0, ctk.END); ui['entry_ang'].insert(0, str(int(ui['slider_ang'].get())))
def on_servo_enable_change(self, channel):
ui = self.servo_ui[channel]; is_on = ui['switch_en'].get()
self.servo_pending[channel] = True; ui['led_fb'].configure(text_color=COLOR_PENDING)
self.publish(f"servo/control/{channel}/enable", "on" if is_on else "off")
# --- Sensor Handlers ---
def on_sensor_mode_change(self, choice):
self.publish("sensor/control/mode", int(choice))
def on_sensor_publish_all(self):
self.publish("sensor/control/publish_all", "1")
def on_sensor_enable_change(self, channel):
ui = self.sensor_ui[channel]; is_on = ui['switch_en'].get()
self.sensor_pending[channel] = True; ui['led_fb'].configure(text_color=COLOR_PENDING)
self.publish(f"sensor/control/enable/{channel}", "on" if is_on else "off")
def on_sensor_cal_start(self, channel, entry_widget):
val = entry_widget.get()
if val.isdigit():
self.publish(f"sensor/control/calibrate/start/{channel}", int(val))
def on_sensor_cal_finish(self, channel, entry_widget):
val = entry_widget.get()
if val.isdigit():
self.publish(f"sensor/control/calibrate/finish/{channel}", int(val))
def on_sensor_clear_cal(self, channel):
self.publish(f"sensor/control/clear_cal/{channel}", "1")
def run(self):
self.root.mainloop()
self.client.loop_stop()
self.client.disconnect()
if __name__ == "__main__":
app = MotorSCADA()
app.run()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -1,2 +0,0 @@
paho-mqtt
customtkinter

View File

@@ -2,6 +2,10 @@
__pycache__/
*.pyc
debug_frames/
logs/
*.jpg
*.png
.pytest_cache/
config.yaml
.env
.env.*

176
cv/README.md Normal file
View File

@@ -0,0 +1,176 @@
# Real CV prototype — RealSense D415 + OpenCV
**Status:** WORKING_PROTOTYPE
**Production integrated:** NO (https://arhipovdan.ru does **not** consume this pipeline)
**Source:** consolidated from branch `drho1y-mvp_1` (`vision_classifier/`) into `cv/`
Same Track 3 B/C/D rules as the web twin; different input path (real depth camera vs simulated sensor).
## Purpose
Measure parcels on a conveyor with an Intel RealSense D415 (depth + color), estimate L×W×H and circularity, classify into zones **B / C / D**, optionally publish results over MQTT for hardware routing.
## Data flow
```
RealSense D415 (V4L2 depth + color)
→ OpenCV segmentation on depth (optional RGB flat detect)
→ measure L×W×H + circle_ratio
→ stabilize (median / vote → LOCK)
→ classify B/C/D
→ optional MQTT (category, dimensions, servo/motor topics)
```
## Entrypoints (start here)
| Command | Role |
|---|---|
| `./demo.sh` | Browser HUD demo on `:8080` (needs camera for live view) |
| `./run.sh --preview` | Live pipeline with JPEG preview frames |
| `./run.sh --once --no-mqtt --no-motor` | Single-shot / dry hardware |
| `.venv/bin/python test_classify.py` | Classifier unit checks **without camera** |
| `.venv/bin/python test_geometry.py` | Geometry helpers **without camera** |
Primary modules: `main.py` (live), `demo.py` (HUD), `classify.py` (rules), `measure.py` (depth metrics), `camera.py` (V4L2 RealSense).
## Classification rules (Track 3)
1. Dimensions must be strictly **> 10×10×10 mm** and **< 450×320×320 mm** → else **C**
2. Else if `circle_ratio ≥ 0.8`**D**
3. Else → **B**
Stabilization: median window + vote → **LOCK**. Uncertain cases fall back to zone **C** after N frames.
## File structure
```
cv/
main.py # live pipeline entry
demo.py / demo.sh # browser demo
run.sh # venv bootstrap + main.py
camera.py # RealSense via V4L2 + ffmpeg depth
measure.py # segmentation + dimensions
classify.py # B/C/D rules
stabilize.py # temporal LOCK
mqtt_bridge.py # optional MQTT (disabled by default)
calibrate.py # fx/fy + belt height calibration
align_color.py # RGB↔depth alignment helper
tracker.py # multi-object tracking assist
journal.py # decisions JSONL writer
demo_hud.py # HUD rendering
collect_log.py # log helper
test_classify.py # no-camera tests
test_geometry.py # no-camera tests
config.example.yaml # safe defaults (commit)
config.yaml # local only (gitignored)
requirements.txt
Dockerfile / docker-compose.yml
```
## Dependencies
**Software**
- Python **3.10+** (3.11 recommended; Docker image uses 3.11)
- `opencv-python-headless`, `numpy`, `PyYAML`, `pillow`, `paho-mqtt` — see `requirements.txt`
- System: **ffmpeg**, V4L2 (`v4l-utils` useful)
**Hardware (live mode)**
- Intel **RealSense D415** on USB3
- Linux host with `/dev/video*` depth+color nodes (Orange PI / x86)
`npm` / Node packages are **not** used here.
## Installation
```bash
cd cv
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -r requirements.txt
cp config.example.yaml config.yaml # optional; scripts auto-copy
```
Or simply:
```bash
cd cv
./demo.sh # creates .venv and config.yaml on first run
```
## Demo / tests without claiming live camera
Classifier and geometry (no RealSense required):
```bash
cd cv
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python test_classify.py
.venv/bin/python test_geometry.py
python3 -m compileall .
```
Live HUD (requires D415):
```bash
./demo.sh
# open http://127.0.0.1:8080/
```
Live pipeline:
```bash
./run.sh --preview --no-mqtt --no-motor
# or full hardware once MQTT/routing configured in local config.yaml:
./run.sh --preview
```
## Configuration
| File | Role |
|---|---|
| `config.example.yaml` | Committed safe defaults; **MQTT/motor/routing disabled** |
| `config.yaml` | Local overrides — **gitignored**; never commit credentials |
Optional MQTT (enable only locally):
```yaml
mqtt:
enabled: true
broker: "127.0.0.1"
port: 1883
user: "<your-user>"
password: "<your-password>"
```
CLI overrides: `--no-mqtt`, `--no-motor`, `--dry-route`, `--once`, `--preview`.
## Output schema (LOCK)
- Zone: `B` | `C` | `D`
- Dimensions mm: L×W×H
- `circle_ratio`
- Optional MQTT topics (when enabled): `vision/feedback/category`, `…/dimensions`, `…/circle_ratio`
- Optional JSONL: `logs/decisions.jsonl` (local, gitignored)
## Limitations
- Not connected to the web digital twin runtime.
- Requires calibrated intrinsics / belt height for accurate mm.
- Live demo needs a physical D415; CI hosts usually lack it.
- MQTT/servo/motor path is optional and site-specific.
## Troubleshooting
| Symptom | Check |
|---|---|
| No `/dev/video*` | USB3, `lsusb`, `v4l2-ctl --list-devices` |
| Depth empty | ffmpeg installed; correct `depth_device` |
| Wrong sizes | run `calibrate.py --length … --width …` |
| MQTT offline | expected when `mqtt.enabled: false` |
## Relation to web twin
Web (`src/domain/classifier.ts`) and CV (`classify.py`) implement the **same official bounds**. The public site uses a **digital sensor simulation**; this folder is the **hardware prototype** for future integration.

View File

@@ -135,6 +135,12 @@ def main() -> int:
args = parser.parse_args()
cfg_path = Path(args.config)
if not cfg_path.exists():
example = cfg_path.with_name("config.example.yaml")
if not example.exists():
raise SystemExit(f"Config not found: {cfg_path}")
cfg_path = example
print(f"[calib] using {cfg_path.name} (copy to config.yaml before saving results)")
cfg = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
cam_cfg = cfg["camera"]

90
cv/config.example.yaml Normal file
View File

@@ -0,0 +1,90 @@
# Vision classifier config EXAMPLE — copy to config.yaml and edit locally.
# Do not commit config.yaml (it may contain site-specific credentials).
camera:
depth_device: /dev/video0
color_device: /dev/video4
width: 640
height: 480
fps: 30
depth_scale_mm: 1.0
# D415 @ 640x480 approximate intrinsics — calibrate on your setup
fx: 564.0
fy: 564.0
cx: 320.0
cy: 240.0
color_dx: -40.0
color_dy: -8.0
color_scale: 1.030
belt_distance_mm: 594
belt_tolerance_mm: 25
min_object_height_mm: 20
min_object_area_px: 800
roi_margin:
top: 0.12
bottom: 0.02
left: 0.05
right: 0.12
max_objects_in_frame: 3
use_color: true
use_background_map: false
detect_flat_rgb: false
rgb_diff_threshold: 35
classification:
min_mm: [10, 10, 10]
max_mm: [450, 320, 320]
circle_ratio_threshold: 0.8
uncertain_after_frames: 45
uncertain_fallback_zone: C
# MQTT is OPTIONAL and disabled by default for safe local runs.
mqtt:
broker: "127.0.0.1"
port: 1883
user: ""
password: ""
client_id: "vision_classifier"
topic_result: "vision/feedback/category"
topic_dims: "vision/feedback/dimensions"
topic_circle: "vision/feedback/circle_ratio"
topic_debug: "vision/feedback/debug"
enabled: false
motor:
enabled: false
rpm: -200
current_percent: 50
microsteps: 16
stealthchop: true
disable_on_stop: false
routing:
enabled: false
zones:
B:
servo: 0
idle_angle: 0
divert_angle: 0
hold_ms: 500
C:
servo: 1
idle_angle: 0
divert_angle: 90
hold_ms: 800
D:
servo: 2
idle_angle: 0
divert_angle: 90
hold_ms: 800
cooldown_ms: 1500
runtime:
show_preview: false
save_debug_frames: false
debug_dir: "debug_frames"
decisions_log: "logs/decisions.jsonl"
preview_every_n: 3
process_every_n: 1
confirm_frames: 8

View File

@@ -473,8 +473,18 @@ def make_handler() -> type:
return Handler
def resolve_config_path(path: Path) -> Path:
if path.exists():
return path
example = path.with_name("config.example.yaml")
if example.exists():
return example
raise FileNotFoundError(f"Config not found: {path} (and no config.example.yaml)")
def load_config(path: Path) -> Dict[str, Any]:
with open(path, "r", encoding="utf-8") as f:
resolved = resolve_config_path(Path(path))
with open(resolved, "r", encoding="utf-8") as f:
return yaml.safe_load(f)

View File

@@ -3,6 +3,11 @@ set -euo pipefail
DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$DIR"
if [[ ! -f config.yaml && -f config.example.yaml ]]; then
cp config.example.yaml config.yaml
echo "[cv] created config.yaml from config.example.yaml (MQTT disabled)"
fi
if [[ ! -d .venv ]]; then
python3 -m venv .venv
.venv/bin/pip install -U pip

View File

@@ -13,8 +13,10 @@ services:
- /dev/video4:/dev/video4
- /dev/video5:/dev/video5
volumes:
# Copy config.example.yaml → config.yaml locally before enabling MQTT/hardware.
- ./config.yaml:/app/config.yaml:ro
- ./debug_frames:/app/debug_frames
# Prefer: command with --no-mqtt until credentials are configured.
group_add:
- video
environment:

View File

@@ -33,8 +33,18 @@ ZONE_TO_CATEGORY = {
}
def resolve_config_path(path: Path) -> Path:
if path.exists():
return path
example = path.with_name("config.example.yaml")
if example.exists():
return example
raise FileNotFoundError(f"Config not found: {path} (and no config.example.yaml)")
def load_config(path: Path) -> Dict[str, Any]:
with open(path, "r", encoding="utf-8") as f:
resolved = resolve_config_path(Path(path))
with open(resolved, "r", encoding="utf-8") as f:
return yaml.safe_load(f)

View File

@@ -15,7 +15,7 @@ from classify import Category, ClassificationResult
class MqttBridge:
def __init__(self, cfg: Dict[str, Any]) -> None:
self.cfg = cfg
self.enabled = bool(cfg.get("enabled", True))
self.enabled = bool(cfg.get("enabled", False))
self.routing_cfg = cfg.get("_routing", {})
self.motor_cfg = cfg.get("_motor", {})
self._last_route_ts = 0.0

View File

@@ -3,6 +3,11 @@ set -euo pipefail
DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$DIR"
if [[ ! -f config.yaml && -f config.example.yaml ]]; then
cp config.example.yaml config.yaml
echo "[cv] created config.yaml from config.example.yaml (MQTT disabled)"
fi
if [[ ! -d .venv ]]; then
python3 -m venv .venv
.venv/bin/pip install -U pip

View File

@@ -0,0 +1,6 @@
services:
web:
build: .
ports:
- "127.0.0.1:3100:80"
restart: unless-stopped

83
docs/ENGINEERING.md Normal file
View File

@@ -0,0 +1,83 @@
# Engineering notes (canonical)
Companion to `README.md` and `/documentation`. Not a stage changelog.
## Coordinate conventions
- World units: **1 unit = 1 meter**.
- Belt travel primarily along **+X**; belt top Y ≈ **0.70 m**.
- Lateral: **+Z** = physical LEFT (category C), **Z** = physical RIGHT (category D).
- Sorter CAD module origin X = `0`; camera module `2.01`; clean module `4.02`.
- Longitudinal plane S for diverter timing is world X along the sorter module.
## Canonical constants (code)
| Symbol | Value | File |
|---|---|---|
| `DIVERTER_LEFT_SIGNED_DEG` | 45 | `src/domain/pusherMotion.ts` |
| `DIVERTER_RIGHT_SIGNED_DEG` | +45 | same |
| `rotationDurationSec()` | 0.50 | same (45° / 90°/s) |
| `OPENING_SAFETY_MARGIN_SEC` | 0.15 | same |
| Contact / clear planes | ≈1.0538 / 1.6000 | `buildDiverterPlanes` + mount hinge |
| Classifier min/max | exclusive 10³ / 450×320×320 | `src/domain/classifier.ts` |
| Roundness | K > 0.8 | same |
| `CONVEYOR_CAD_URL` | `/models/sorter/conveyor-clean.glb` | `ConveyorCadModel.tsx` |
## Active source tree (runtime)
```
src/main.tsx
src/App.tsx
src/pages/{MainPage,DocumentationPage}.tsx
src/components/{AppNav,SorterScene,CVInspectionOverlay,BuildIdentityBadge}.tsx
src/components/ThreeD/* (active twin only)
src/domain/* (classifier, playback, layout, diverter, physics helpers)
src/data/{items,modelAssets,resolveItem,productionStatusSummary,demoPlaylist,scenarios}.ts
src/styles.css
```
## Asset provenance
| Role | Path |
|---|---|
| Author CAD | `3d_models/conveer.FCStd` |
| Runtime conveyor | `public/models/sorter/conveyor-clean.glb` |
| Products | `public/models/*.stl` from official STL ZIP |
| Classifier PDF | `official_sources/doc-1783095831.pdf` |
| Workspace / scoring PDFs | `input_info/doc-1783009942.pdf`, `doc-1783011400.pdf` |
## Physics roadmap (not completed)
1. Surface-velocity belt at 1 m/s with visual loop.
2. Contact-validated CAD diverter deflection for all playlist SKUs.
3. Calibrated per-SKU mass, COM, friction, damping.
4. Receiver capture verification under dynamic drops.
CCD for light/thin items exists in runtime/sim; that alone is **not** full contact validation.
## Compliance evidence rules
- Prefer present official files under `input_info/` and `official_sources/doc-1783095831.pdf`.
- Missing: `input_info/extracted/Постановка_Задача_3_сжато_2.pdf` — never claim it is available.
- Internal engineering criteria are not automatic Ozon pass/fail.
## Соответствие подтверждённым требованиям
Grounded only in present sources + current `main` code/tests (not a full official scorecard):
| Area | Official source present | Current implementation | Evidence | Status |
|---|---|---|---|---|
| B/C/D bounds & roundness | `official_sources/doc-1783095831.pdf` | web `classifier.ts`, CV `cv/classify.py` | unit tests | PARTIAL (PDF not re-parsed each pass) |
| Digital twin demo | workspace PDFs in `input_info/` | `/` + `/documentation` | e2e smoke/routes, production | PASS (demo present) |
| CAD conveyor | author pack / STL references | `3d_models/`, `conveyor-clean.glb` | checksums in README | PASS (assets present) |
| Real measurement CV | Track 3 camera intent in briefs | `cv/` RealSense+OpenCV | `cv/README.md`, `test_classify.py` | PARTIAL (prototype, not live web) |
| Physical industrial line | scoring/workspace PDFs | web physics + optional MQTT CV | code; contact not fully validated | PARTIAL |
| Presentation / video | platform rules | cloud links | README §17 | NOT_VERIFIED (links missing) |
## Real CV prototype
Path `cv/` — OpenCV + RealSense D415 depth pipeline. Same B/C/D domain as the web twin. **Not** consumed by https://arhipovdan.ru. Details: `cv/README.md`.
## Layout drawing
`docs/engineering/work-area-layout-source.png` — workspace layout provenance image retained for engineering reference.

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

55
e2e/helpers.ts Normal file
View File

@@ -0,0 +1,55 @@
/** Shared e2e helpers — Stage 2B autostart means / opens already running. */
import { expect, type Page } from '@playwright/test';
/** Close the Demo Complete overlay if it is covering the controls. */
export async function dismissFinished(page: Page) {
const finished = page.getByTestId('demo-finished');
if (await finished.isVisible().catch(() => false)) {
await finished.getByRole('button', { name: /Replay/i }).click({ force: true });
await expect(finished).toHaveCount(0, { timeout: 10_000 });
}
}
/** Ensure playback is running (noop if already on Pause button). */
export async function ensureRunning(page: Page) {
await dismissFinished(page);
const pause = page.getByTestId('demo-pause');
if (await pause.isVisible().catch(() => false)) return;
const play = page.getByTestId('demo-play');
await expect(play.or(pause)).toBeVisible({ timeout: 15_000 });
if (await play.isVisible().catch(() => false)) {
await play.click({ force: true });
}
await expect(page.getByTestId('demo-pause')).toBeVisible({ timeout: 10_000 });
}
export async function ensurePaused(page: Page) {
await dismissFinished(page);
const play = page.getByTestId('demo-play');
if (await play.isVisible().catch(() => false)) return;
await page.getByTestId('demo-pause').click({ force: true });
await expect(page.getByTestId('demo-play')).toBeVisible({ timeout: 10_000 });
}
/** Assert either Play or Pause control is present (autostart-safe). */
export async function expectPlaybackControl(page: Page) {
await expect(
page.getByTestId('demo-play').or(page.getByTestId('demo-pause')),
).toBeVisible({ timeout: 15_000 });
}
/**
* Open debug demo, pause, then seek to a case WHILE paused (seek preserves
* paused status — unlike seek from idle which auto-starts).
*/
export async function openPausedCase(page: Page, caseIndex: number, speed: '0.5' | '1' | '1.5' | '2' = '1') {
await page.goto('/?debug=1');
await expect(page.locator('canvas')).toBeVisible({ timeout: 60_000 });
await expect(page.getByTestId('demo-hud')).toBeVisible();
await ensurePaused(page);
await page.getByTestId(`demo-speed-${speed}`).click();
await page.getByTestId(`demo-case-${caseIndex}`).click();
await dismissFinished(page);
await ensurePaused(page);
await expect(page.getByTestId('demo-case-label')).toHaveText(`${caseIndex + 1}/12`);
}

33
e2e/routes.spec.ts Normal file
View File

@@ -0,0 +1,33 @@
import { test, expect } from '@playwright/test';
test.describe('routes', () => {
test('two-page UI: simulation, documentation, unknown redirect', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveURL(/\/$/);
await expect(page.getByTestId('app-nav').first()).toBeVisible({ timeout: 30_000 });
await expect(page.getByTestId('nav-simulation').first()).toHaveClass(/active/);
await page.getByTestId('nav-documentation').first().click();
await expect(page).toHaveURL(/\/documentation\/?$/);
await expect(page.getByTestId('documentation-page')).toBeVisible();
await expect(page.getByTestId('docs-production-status')).toContainText('DATA_ACQUISITION_PACK_READY');
await expect(page.getByTestId('nav-documentation').first()).toHaveClass(/active/);
await page.reload();
await expect(page).toHaveURL(/\/documentation\/?$/);
await expect(page.getByTestId('documentation-page')).toBeVisible();
await page.getByTestId('nav-simulation').first().click();
await expect(page).toHaveURL(/\/$/);
await expect(page.getByTestId('demo-hud')).toBeVisible({ timeout: 60_000 });
await page.goto('/details');
await expect(page).toHaveURL(/\/$/);
await page.goto('/device-test');
await expect(page).toHaveURL(/\/$/);
await page.goto('/old-route');
await expect(page).toHaveURL(/\/$/);
});
});

31
e2e/smoke.spec.ts Normal file
View File

@@ -0,0 +1,31 @@
import { test, expect } from '@playwright/test';
import { ensureRunning, expectPlaybackControl } from './helpers';
test.describe('smoke', () => {
test('home opens, canvas loads, play works', async ({ page }) => {
const pageErrors: Error[] = [];
page.on('pageerror', (err) => pageErrors.push(err));
await page.goto('/');
const loading = page.locator('.three-loading');
const canvas = page.locator('canvas');
await expect(loading.or(canvas).first()).toBeVisible({ timeout: 30_000 });
await expect(canvas).toBeVisible({ timeout: 60_000 });
await expect(page.getByTestId('demo-hud')).toBeVisible();
// Stage 2B: demo autostarts — Pause is present immediately; Play after pause.
await expectPlaybackControl(page);
await ensureRunning(page);
await expect(page.getByTestId('demo-status')).not.toHaveText('FINISHED');
const finished = page.getByTestId('demo-finished');
if (await finished.isVisible().catch(() => false)) {
await page.getByTestId('demo-play').click();
await expect(finished).toBeHidden({ timeout: 10_000 });
await expect(page.getByTestId('demo-pause')).toBeVisible({ timeout: 10_000 });
}
expect(pageErrors, `pageerrors: ${pageErrors.map((e) => e.message).join('; ')}`).toEqual([]);
});
});

14
index.html Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#070b12" />
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%230b1220'/%3E%3Crect x='3' y='7' width='10' height='2' fill='%233b82f6'/%3E%3C/svg%3E" />
<title>OZON Tech Sorter Simulation</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

28546
input_info/doc-1783011400.pdf Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@@ -1,2 +0,0 @@
*.lck
.history

Submodule kicad/ozon/.history deleted from 2a8a462227

View File

@@ -1,2 +0,0 @@
(kicad_pcb (version 20260206) (generator "pcbnew") (generator_version "10.0")
)

View File

@@ -1,105 +0,0 @@
{
"board": {
"active_layer": 0,
"active_layer_preset": "",
"auto_track_width": true,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"images": 0.6,
"pads": 1.0,
"shapes": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"prototype_zone_fills": false,
"selection_filter": {
"dimensions": true,
"footprints": true,
"graphics": true,
"keepouts": true,
"lockedItems": false,
"otherItems": true,
"pads": true,
"text": true,
"tracks": true,
"vias": true,
"zones": true
},
"visible_items": [
"vias",
"footprint_text",
"footprint_anchors",
"ratsnest",
"grid",
"footprints_front",
"footprints_back",
"footprint_values",
"footprint_references",
"tracks",
"drc_errors",
"drawing_sheet",
"bitmaps",
"pads",
"zones",
"drc_warnings",
"drc_exclusions",
"locked_item_shadows",
"conflict_shadows",
"shapes",
"board_outline_area",
"ly_points"
],
"visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff",
"zone_display_mode": 0
},
"git": {
"integration_disabled": false,
"repo_type": "",
"repo_username": "",
"ssh_key": ""
},
"meta": {
"filename": "ozon.kicad_prl",
"version": 5
},
"net_inspector_panel": {
"col_hidden": [],
"col_order": [],
"col_widths": [],
"custom_group_rules": [],
"expanded_rows": [],
"filter_by_net_name": true,
"filter_by_netclass": true,
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_time_domain_details": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
"sorting_column": -1
},
"open_jobsets": [],
"project": {
"files": []
},
"schematic": {
"hierarchy_collapsed": [],
"selection_filter": {
"graphics": true,
"images": true,
"labels": true,
"lockedItems": false,
"otherItems": true,
"pins": true,
"ruleAreas": true,
"symbols": true,
"text": true,
"wires": true
}
}
}

View File

@@ -1,435 +0,0 @@
{
"board": {
"3dviewports": [],
"ipc2581": {
"bom_rev": "",
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": "",
"sch_revision": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"component_class_settings": {
"assignments": [],
"meta": {
"version": 0
},
"sheet_component_classes": {
"enabled": false
}
},
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"field_name_whitespace": "warning",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"ground_pin_not_ground": "warning",
"hier_label_mismatch": "error",
"isolated_pin_label": "warning",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"stacked_pin_name": "warning",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "ozon.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"tuning_profile": "",
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 5
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"idf": "",
"netlist": "",
"plot": "",
"specctra_dsn": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"annotation": {
"method": 0,
"sort_order": 0
},
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "Default Editing",
"sort_asc": true,
"sort_field": "Обозначение"
},
"bus_aliases": {},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"hop_over_size_choice": 0,
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"page_layout_descr_file": "",
"plot_directory": "",
"reuse_designators": true,
"subpart_first_id": 65,
"subpart_id_separator": 0,
"top_level_sheets": [
{
"filename": "ozon.kicad_sch",
"name": "Корневой лист",
"uuid": "8eb30ef0-8ef0-4108-b9d0-4c3ae489af42"
}
],
"used_designators": "R1,M1-5,#PWR1-10,U1-6",
"variants": []
},
"sheets": [
[
"8eb30ef0-8ef0-4108-b9d0-4c3ae489af42",
"Корневой лист"
]
],
"text_variables": {},
"tuning_profiles": {
"meta": {
"version": 0
},
"tuning_profiles_impedance_geometric": []
}
}

File diff suppressed because it is too large Load Diff

56
nginx.conf Normal file
View File

@@ -0,0 +1,56 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Keep default MIME map (html/js/css). Do NOT put a server-level `types {}`
# here — it replaces mime.types and forces downloads (octet-stream + nosniff).
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Security headers (Stage 2B §24.3)
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# gzip for text payloads (GLB/STL are already compressed/binary)
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/javascript image/svg+xml;
location = /version.json {
add_header Cache-Control "no-store, no-cache, must-revalidate";
add_header X-Content-Type-Options "nosniff" always;
try_files $uri =404;
}
location / {
try_files $uri $uri/ /index.html;
}
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 3D models only — scoped types override (does not wipe html/js MIME)
location /models/ {
types {
model/gltf-binary glb;
model/stl stl;
application/octet-stream bin;
}
default_type application/octet-stream;
expires 1y;
add_header Cache-Control "public, immutable";
}
location /draco/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}

File diff suppressed because one or more lines are too long

2227
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

37
package.json Normal file
View File

@@ -0,0 +1,37 @@
{
"name": "ozon-tech-sorter-simulation",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1 --port 3100",
"build": "tsc -b && vite build",
"preview": "vite preview --host 127.0.0.1 --port 3100",
"test": "vitest run --config vitest.config.ts",
"test:e2e": "playwright test",
"test:e2e:headed": "playwright test --headed"
},
"dependencies": {
"@dimforge/rapier3d-compat": "^0.19.3",
"@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.6.1",
"@react-three/postprocessing": "^3.0.4",
"@react-three/rapier": "^2.2.0",
"@vitejs/plugin-react": "latest",
"postprocessing": "^6.39.4",
"react": "latest",
"react-dom": "latest",
"react-router-dom": "^7.18.1",
"three": "^0.185.1",
"typescript": "latest",
"vite": "latest"
},
"devDependencies": {
"@playwright/test": "^1.61.1",
"@types/node": "^26.1.0",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@types/three": "^0.185.0",
"vitest": "^4.1.9"
}
}

42
playwright.config.ts Normal file
View File

@@ -0,0 +1,42 @@
import { defineConfig, devices } from '@playwright/test';
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? 'http://127.0.0.1:3101';
const startServer = process.env.PLAYWRIGHT_START_SERVER === '1';
export default defineConfig({
testDir: './e2e',
fullyParallel: false,
workers: 1,
retries: 1,
forbidOnly: !!process.env.CI,
reporter: [['list'], ['html', { open: 'never' }]],
use: {
baseURL,
trace: 'on-first-retry',
screenshot: 'only-on-failure',
viewport: { width: 1280, height: 720 },
},
expect: {
toHaveScreenshot: {
// Soft thresholds — WebGL/fonts can vary slightly across environments
threshold: 0.35,
maxDiffPixelRatio: 0.08,
},
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
// Keep visual/e2e deterministic on one worker (CI + local)
// Production smoke is excluded via package.json --grep-invert @production
webServer: startServer
? {
command: 'npm run preview -- --host 127.0.0.1 --port 3101',
url: baseURL,
reuseExistingServer: !process.env.CI,
timeout: 120_000,
}
: undefined,
});

31
public/draco/README.md Normal file
View File

@@ -0,0 +1,31 @@
# 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/');
```
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)

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -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);

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -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);

BIN
public/models/bottle.stl Normal file

Binary file not shown.

BIN
public/models/box-300.stl Normal file

Binary file not shown.

BIN
public/models/box-400.stl Normal file

Binary file not shown.

BIN
public/models/cylinder.stl Normal file

Binary file not shown.

BIN
public/models/detergent.stl Normal file

Binary file not shown.

BIN
public/models/lunchbox.stl Normal file

Binary file not shown.

BIN
public/models/pen.stl Normal file

Binary file not shown.

BIN
public/models/plate.stl Normal file

Binary file not shown.

BIN
public/models/pouf.stl Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -1,26 +0,0 @@
# Наброски будущей схемы конвейера
![](mvp_1.jpg)
## Список материалов:
В этот список необходимо доложить энкодер для двигателя!
| Название | Сумма, ₽ | Количество |
| :--- | :--- | :--- |
| Медная лента для удаления припоя / Оплетка для выпайки диаметр 2 мм длина 1.5 м | 164 | 1 |
| Набор проводов для пайки | 855 | 1 |
| Флюс гель универсальный безотмывочный, для пайки микросхем и компонентов Flux RMA-223-UV-10г | 162 | 1 |
| Припой для пайки с канифолью 1мм 50гр ПОС-61 на катушке (ГОСТ) | 423 | 1 |
| ALIENTEK Паяльник 140 Вт, 7 предметов | 4 985 | 1 |
| Преобразователь DC-DC понижающий с 8-60V до 1-36V 15A max | 805 | 1 |
| Беспаечная макетная плата (breadboard) MB-102, 830 точек, для Arduino и прочих устройств | 1 112 | 2 |
| 120 шт. Провода перемычки для макетных плат, соединительные провода для модулей, контроллеров arduino (10 см) 3 вида по 40 шт папа-папа, мама-мама, папа-мама | 636 | 2 |
| Homeled, Блок питания, 12V, 200W, 180-265 вольт. С клеммами. Импульсный для светодиодных лент и светильников | 599 | 1 |
| Сервопривод MG996R, 10 шт, 180 , металлические шестерни, для Arduino, роботов и RC, размеры стандарт | 2 990 | 1 |
| PWM PCA9685 драйвер на 16 сервоприводов расширитель портов с I2C интерфейсом для Led и Servo (12 bit, I2C) в IIC/I2C/TWI/SPI c тестером сервоприводов 3 режима, набор | 1 178 | 2 |
| Модуль I2C-мультиплексора CJMCU-9548 на базе TCA9548A / PCA9548A | 532 | 2 |
| Модуль VL53L0X лазерный дальномер GY-530 (до 2м, питание 3-5В, I2C) | 1064 | 4 |
| Модуль ESP32 TYPE-C CH340C + плата расширения ESP32 | 1040 | 2 |
[Ссылка на товары](https://www.ozon.ru/cart?share=wGhPkQd)

View File

@@ -1,54 +0,0 @@
# Алгоритм работы сортировщика
```mermaid
mindmap
root((Алгоритм работы))
Этап 1: Ожидание и Движение
Arduino запускает шаговый двигатель
Лента движется с постоянной скоростью
Этап 2: Детекция и Анализ
Объект проходит под датчиком и камерой
Предмет соответствует габаритам?
Да
Есть признак круга в сечении?
Да - Требуется доупаковка
Секция 2
Нет - Подходит для сортировки
Секция 3
Нет
Не подходит по габаритам
Секция 1
Этап 3: Трекинг Синхронизация
Arduino отсчитывает шаги двигателя
Вычисляется момент времени T
Этап 4: Маршрутизация
В момент T сервопривод поворачивает барьер на 45°
Объект смещается в выбранную зону
Сервопривод возвращается в исходное положение
Этап 5: Завершение
Объект попадает в накопитель
Система возвращается в состояние Ожидание
```
## Блок схема:
```mermaid
flowchart TD
Start[Этап 1: Ожидание и Движение] --> Detect[Этап 2: Детекция и Анализ]
Detect --> SizeCheck{Предмет соответствует<br/>габаритам?}
SizeCheck -->|Нет| Reject[Секция 1:<br/>Не габарит]
SizeCheck -->|Да| CircleCheck{Есть признак<br/>круга?}
CircleCheck -->|Да| Repack[Секция 2:<br/>Требуется доупаковка]
CircleCheck -->|Нет| Sort[Секция 3:<br/>Подходит для сортировки]
Reject --> Track[Этап 3: Трекинг]
Repack --> Track
Sort --> Track
Track --> Route[Этап 4: Маршрутизация]
Route --> End[Этап 5: Завершение]
End --> Start
```

View File

@@ -1,277 +0,0 @@
<mxfile host="server.home">
<diagram name="Страница-1" id="mppyHKg2Vbg7W8e0-Fd9">
<mxGraphModel dx="3902" dy="2031" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1654" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="jfPUc6vKa1uVjcLF1lrC-2" edge="1" parent="1" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="37" y="773" as="sourcePoint" />
<mxPoint x="1547" y="773" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-6" edge="1" parent="1" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="257" y="720" as="sourcePoint" />
<mxPoint x="1617" y="720" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-7" edge="1" parent="1" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="37" y="770" as="sourcePoint" />
<mxPoint x="107" y="720" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-8" edge="1" parent="1" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="1547" y="774" as="sourcePoint" />
<mxPoint x="1617" y="724" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-9" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" value="&lt;font style=&quot;font-size: 30px;&quot;&gt;1&lt;/font&gt;" vertex="1">
<mxGeometry height="80" width="160" x="847" y="830" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-10" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;" value="&lt;font style=&quot;font-size: 30px;&quot;&gt;2&lt;/font&gt;" vertex="1">
<mxGeometry height="80" width="160" x="1097" y="830" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-11" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;" value="&lt;font style=&quot;font-size: 30px;&quot;&gt;3&lt;/font&gt;" vertex="1">
<mxGeometry height="80" width="160" x="1377" y="830" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-12" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" value="" vertex="1">
<mxGeometry height="80" width="160" x="67" y="680" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-13" edge="1" parent="1" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="227" y="680" as="sourcePoint" />
<mxPoint x="257" y="660" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-14" edge="1" parent="1" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="227" y="760" as="sourcePoint" />
<mxPoint x="257" y="740" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-16" edge="1" parent="1" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="67" y="680" as="sourcePoint" />
<mxPoint x="97" y="660" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-17" edge="1" parent="1" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="97" y="660" as="sourcePoint" />
<mxPoint x="257" y="660" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-18" edge="1" parent="1" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="257" y="740" as="sourcePoint" />
<mxPoint x="257" y="660" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-19" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" value="" vertex="1">
<mxGeometry height="80" width="50" x="477" y="390" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-20" edge="1" parent="1" source="jfPUc6vKa1uVjcLF1lrC-19" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="567" y="620" as="sourcePoint" />
<mxPoint x="527" y="530" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-21" edge="1" parent="1" source="jfPUc6vKa1uVjcLF1lrC-19" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="487" y="520" as="sourcePoint" />
<mxPoint x="477" y="530" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-22" edge="1" parent="1" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="477" y="530" as="sourcePoint" />
<mxPoint x="527" y="530" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-23" edge="1" parent="1" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;dashed=1;dashPattern=12 12;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="477" y="530" as="sourcePoint" />
<mxPoint x="417" y="750" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-24" edge="1" parent="1" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;dashed=1;dashPattern=12 12;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="527" y="530" as="sourcePoint" />
<mxPoint x="587" y="750" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-26" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;" value="" vertex="1">
<mxGeometry height="80" width="50" x="307" y="390" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-27" edge="1" parent="1" source="jfPUc6vKa1uVjcLF1lrC-26" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;dashed=1;dashPattern=8 8;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="357" y="580" as="sourcePoint" />
<mxPoint x="332" y="740" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-29" parent="1" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;fillColor=#f0a30a;strokeColor=#BD7000;fillStyle=solid;direction=west;gradientDirection=north;shadow=1;size=20;autosizeText=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontColor=#000000;fontSize=17;align=left;" value="Лазерный датчик расстояния.&lt;br&gt;В нашем случае он измеряет высоту объекта.&lt;br&gt;Так как камера у нас расположена перпендикулярно движению ленты. И корректно не сможет определить высоту объектов" vertex="1">
<mxGeometry height="160" width="315" x="42" y="210" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-30" parent="1" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;fillColor=#f0a30a;strokeColor=#BD7000;fillStyle=solid;direction=west;gradientDirection=north;shadow=1;size=20;autosizeText=1;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontColor=#000000;fontSize=24;align=left;" value="Тут у нас установлена камера, первично мы ее откалибруем и сможем визуально оценить размер объекта" vertex="1">
<mxGeometry height="160" width="315" x="387" y="210" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-32" parent="1" style="ellipse;whiteSpace=wrap;html=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" value="" vertex="1">
<mxGeometry height="40" width="40" x="804" y="640" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-37" edge="1" parent="1" source="jfPUc6vKa1uVjcLF1lrC-38" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;startSize=14;endArrow=open;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;dashed=1;dashPattern=12 12;" target="jfPUc6vKa1uVjcLF1lrC-32" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="847" y="370" as="sourcePoint" />
<mxPoint x="967" y="710" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-38" parent="1" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;fillColor=#f0a30a;strokeColor=#BD7000;fillStyle=solid;direction=west;gradientDirection=north;shadow=1;size=20;autosizeText=1;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontColor=#000000;fontSize=26;align=left;" value="Датчик ультразвуковой, определят что объект приехал на сортировку" vertex="1">
<mxGeometry height="160" width="315" x="737" y="190" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-39" edge="1" parent="1" source="jfPUc6vKa1uVjcLF1lrC-40" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=open;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;exitX=0.542;exitY=0.018;exitDx=0;exitDy=0;exitPerimeter=0;dashed=1;dashPattern=12 12;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="1197" y="360" as="sourcePoint" />
<mxPoint x="937" y="610" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-40" parent="1" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;fillColor=#f0a30a;strokeColor=#BD7000;fillStyle=solid;direction=west;gradientDirection=north;shadow=1;size=20;autosizeText=1;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontColor=#000000;fontSize=25;align=left;" value="Сервопривод с ограничителем движения который не дает объекту двигаться дальше." vertex="1">
<mxGeometry height="160" width="315" x="1062" y="190" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-42" connectable="0" parent="1" style="group" value="" vertex="1">
<mxGeometry height="80" width="90" x="882" y="620" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-31" parent="jfPUc6vKa1uVjcLF1lrC-42" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" value="" vertex="1">
<mxGeometry height="80" width="90" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-34" edge="1" parent="jfPUc6vKa1uVjcLF1lrC-42" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="45" y="80" as="sourcePoint" />
<mxPoint x="95" y="150" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-35" edge="1" parent="jfPUc6vKa1uVjcLF1lrC-42" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="95" y="150" as="sourcePoint" />
<mxPoint x="95" y="40" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-36" edge="1" parent="jfPUc6vKa1uVjcLF1lrC-42" source="jfPUc6vKa1uVjcLF1lrC-31" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="45" y="-20" as="sourcePoint" />
<mxPoint x="95" y="40" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-48" parent="1" style="ellipse;whiteSpace=wrap;html=1;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;" value="" vertex="1">
<mxGeometry height="40" width="40" x="1062" y="640" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-49" edge="1" parent="1" source="jfPUc6vKa1uVjcLF1lrC-38" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=open;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;dashed=1;dashPattern=12 12;" target="jfPUc6vKa1uVjcLF1lrC-48" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="948" y="430" as="sourcePoint" />
<mxPoint x="877" y="720" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-50" edge="1" parent="1" source="jfPUc6vKa1uVjcLF1lrC-40" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=open;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;exitX=0.542;exitY=-0.004;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;dashed=1;dashPattern=12 12;" target="jfPUc6vKa1uVjcLF1lrC-44" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="1436" y="410" as="sourcePoint" />
<mxPoint x="1167" y="673" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-51" connectable="0" parent="1" style="group" value="" vertex="1">
<mxGeometry height="80" width="90" x="1137" y="620" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-44" parent="jfPUc6vKa1uVjcLF1lrC-51" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;" value="" vertex="1">
<mxGeometry height="80" width="90" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-45" edge="1" parent="jfPUc6vKa1uVjcLF1lrC-51" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="45" y="80" as="sourcePoint" />
<mxPoint x="140" y="90" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-46" edge="1" parent="jfPUc6vKa1uVjcLF1lrC-51" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="140" y="90" as="sourcePoint" />
<mxPoint x="140" y="-10" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-47" edge="1" parent="jfPUc6vKa1uVjcLF1lrC-51" source="jfPUc6vKa1uVjcLF1lrC-44" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="45" y="-20" as="sourcePoint" />
<mxPoint x="140" y="-10" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-52" connectable="0" parent="1" style="group" value="" vertex="1">
<mxGeometry height="80" width="90" x="1407" y="620" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-53" parent="jfPUc6vKa1uVjcLF1lrC-52" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;" value="" vertex="1">
<mxGeometry height="80" width="90" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-54" edge="1" parent="jfPUc6vKa1uVjcLF1lrC-52" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="45" y="80" as="sourcePoint" />
<mxPoint x="140" y="90" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-55" edge="1" parent="jfPUc6vKa1uVjcLF1lrC-52" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="140" y="90" as="sourcePoint" />
<mxPoint x="140" y="-10" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-56" edge="1" parent="jfPUc6vKa1uVjcLF1lrC-52" source="jfPUc6vKa1uVjcLF1lrC-53" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=none;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;endFill=0;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="45" y="-20" as="sourcePoint" />
<mxPoint x="140" y="-10" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-57" parent="1" style="ellipse;whiteSpace=wrap;html=1;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;" value="" vertex="1">
<mxGeometry height="40" width="40" x="1337" y="640" as="geometry" />
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-58" edge="1" parent="1" source="jfPUc6vKa1uVjcLF1lrC-38" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=open;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;dashed=1;dashPattern=12 12;" target="jfPUc6vKa1uVjcLF1lrC-57" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="957" y="530" as="sourcePoint" />
<mxPoint x="1144" y="820" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="jfPUc6vKa1uVjcLF1lrC-59" edge="1" parent="1" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;rounded=0;fontFamily=Architects Daughter;fontSource=https://fonts.googleapis.com/css?family=Architects+Daughter;fontSize=16;startSize=14;endArrow=open;endSize=14;sourcePerimeterSpacing=8;targetPerimeterSpacing=8;curved=1;dashed=1;dashPattern=12 12;" value="">
<mxGeometry relative="1" width="140" as="geometry">
<Array as="points" />
<mxPoint x="1207" y="350" as="sourcePoint" />
<mxPoint x="1447" y="620" as="targetPoint" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -1,97 +0,0 @@
Привет! Как инженер-конструктор и эксперт по электронике, я с удовольствием помогу тебе спроектировать этот макет. Задача ясна: создаем систему автоматизированной сортировки с точным управлением скоростью и маршрутизацией по высоте.
Давай разберем твои вопросы по порядку.
### 1. Выбор двигателя: Шаговый vs Коллекторный
Для твоей задачи — **максимально точного выдерживания скорости** и управления с микроконтроллера — выбор очевиден.
| Характеристика | Коллекторный двигатель (DC) | Шаговый двигатель (Stepper) |
| :--- | :--- | :--- |
| **Точность скорости** | Низкая. Скорость зависит от напряжения и нагрузки. Для точности нужен энкодер (обратная связь). | **Высокая**. Скорость задается частотой импульсов. Постоянна при заданной нагрузке. |
| **Управление** | Простое (PWM/ШИМ). | Требует специальный драйвер (например, A4988 или TMC2208). |
| **Пуск/Стоп** | Плавный разгон, сложно точно позиционировать. | **Идеальный**. Можно точно задать количество шагов и мгновенно остановиться. |
| **Крутящий момент** | Высокий на высоких оборотах. | Высокий на низких скоростях (идеально для ленты). |
| **Сложность реализации** | Очень низкая. | Средняя (нужен драйвер и настройка тока). |
**Мой вердикт:** Используй **шаговый двигатель (например, NEMA 17)**.
Если ты хочешь «выдерживать скорость максимально точно», обычный DC-мотор тебя разочарует: при увеличении веса объекта на ленте скорость будет падать. Шаговый же двигатель будет поддерживать заданный темп, пока не будет достигнут предел его крутящего момента.
---
### 2. Селекторы (Сервоприводы)
Использование сервоприводов с барьером под 45° — это классическое и эффективное решение для макета.
**Советы по реализации:**
* **Механика:** Чтобы объект не застревал и не «зажевывался», сделай барьер обтекаемым или используй гладкий пластик/алюминий.
* **Угол:** 45 градусов — оптимально, но убедись, что при срабатывании сервопривод не толкает объект слишком резко, чтобы тот не перевернулся.
* **Логика:** В промышленном подходе, как отмечено в контексте, важно отслеживать «корректность маршрутизации» [1]. Рекомендую добавить в код проверку: действительно ли объект ушел в нужную зону, или произошла ошибка.
---
### 3. Датчик определения высоты
Для измерения высоты объекта сверху, когда лента движется, тебе подойдут три варианта в зависимости от требуемой точности:
1. **Ультразвуковой датчик (HC-SR04):** Самый дешевый и популярный.
* *Минус:* Широкий луч (может «цеплять» борта конвейера) и низкая скорость обновления.
2. **ИК-датчик расстояния (Sharp GP2Y0A21YK0F):** Более точный, узкий луч.
* *Минус:* Аналоговый сигнал, чувствителен к цвету объекта (черный поглощает ИК).
3. **Лазерный ToF-датчик (VL53L0X / VL53L1X):** **Мой выбор для этого проекта.**
* *Плюсы:* Измеряет время полета фотона (Time-of-Flight), работает независимо от цвета объекта, очень высокая точность (до миллиметров) и очень узкий луч.
---
### Итоговая архитектура (предложение):
* **Контроллер:** Arduino (Uno или Mega, если планируешь расширять количество датчиков).
* **Привод ленты:** Шаговый двигатель NEMA 17 $\rightarrow$ Драйвер A4988/TMC2208 $\rightarrow$ Arduino.
* **Сортировка:** 3 $\times$ Сервоприводы SG90 или MG90S (с металлическим редуктором надежнее).
* **Датчик высоты:** VL53L0X (I2C интерфейс).
* **Питание:** Отдельный блок питания 12В для двигателя и 5В для Arduino/сервоприводов (общий минус/земля).
**Важное замечание из спецификации:**
Не забудь предусмотреть в логике категорию «Не подходит для сортировки» [1]. Если датчик зафиксирует объект, который либо слишком высокий, либо слишком низкий (выходит за допустимые габариты), система должна отправить его в отдельный поток или оставить на ленте до конца [1].
Поскольку мы проектируем макет, важно учитывать не только функциональность, но и ограничения, упомянутые в техническом контексте: доступную площадь участка [1], разнообразие форм товаров (включая круглые в сечении) и необходимость бережного обращения с хрупкими или ценными объектами [1].
# Сервоприводы с барьером — это надежный вариант, но в зависимости от того, какие объекты вы будете сортировать, рассмотрите следующие альтернативы:
### 1. Электромагнитные толкатели (Solenoids)
Вместо плавного поворота барьера, соленоид дает резкий линейный толчок.
* **Плюсы:** Очень высокая скорость срабатывания, компактность [1].
* **Минусы:** Резкий удар. Если ваши объекты хрупкие, этот вариант **не рекомендуется**, так как он противоречит требованию к «мягкости обращения» [1].
* **Когда использовать:** Для прочных, тяжелых или однотипных деталей.
### 2. Откидная платформа (Tipping Platform)
Это участок конвейера, который может наклоняться в сторону или под ним открывается люк.
* **Плюсы:** Позволяет перемещать объекты за счет гравитации. Это один из самых бережных способов перемещения, что важно для ценных или хрупких товаров [1].
* **Минусы:** Требует более сложной механической интеграции в саму ленту.
* **Когда использовать:** Если приоритетом является сохранность объекта и минимизация механических ударов.
### 3. Поворотный диск-распределитель (Rotating Disc)
В конце основной ленты устанавливается диск, который поворачивается на определенный угол, направляя объект в один из трех лотков.
* **Плюсы:** Занимает минимум места (оптимально для ограниченной площади участка [1]). Отлично работает с объектами, имеющими круг в сечении [1].
* **Минусы:** Требует точной синхронизации остановки ленты и поворота диска.
* **Когда использовать:** Если макет должен быть максимально компактным, а объекты имеют схожую форму (например, цилиндры или сферы).
### 4. Боковой конвейер (Cross-Belt / Diverter)
Это короткий перпендикулярный отрезок ленты, который включается только тогда, когда объект должен быть сброшен в зону.
* **Плюсы:** Самый профессиональный и «мягкий» метод. Идеален для хрупких товаров [1].
* **Минусы:** Высокая стоимость и сложность (нужно еще 3 маленьких мотора и дополнительные ленты).
* **Когда использовать:** Если вы хотите создать максимально приближенный к реальному промышленному производству макет.
### Сводная таблица для выбора:
| Вариант | Бережность (для хрупких [1]) | Компактность (площадь [1]) | Сложность реализации | Работа с круглыми объектами [1] |
| :--- | :--- | :--- | :--- | :--- |
| **Серво-барьер** | Средняя | Высокая | Низкая | Хорошо |
| **Соленоид** | Низкая | Очень высокая | Низкая | Средне |
| **Платформа** | Высокая | Средняя | Средняя | Хорошо |
| **Диск** | Средняя | Очень высокая | Средняя | Идеально |
| **Боковой конвейер**| Очень высокая | Низкая | Высокая | Хорошо |
**Моя рекомендация:**
Если вы хотите сохранить простоту управления через Arduino, но при этом учесть требования к бережности и компактности [1], я рекомендую остаться на **сервоприводах**, но изменить конструкцию барьера: вместо «толкателя» сделать **«поворотный желоб»** или **«шлюз»**. Это позволит объекту плавно скатываться в нужную зону, не подвергаясь резким ударам.

Some files were not shown because too many files have changed in this diff Show More