From 40ff0ca9132f2143151a26fe9b401df09f0968ad Mon Sep 17 00:00:00 2001 From: osiu97 Date: Wed, 8 Jul 2026 22:03:59 +0200 Subject: [PATCH] Add bg park postitions. Closes #18 --- web/assets/backgrounds/default/spots.json | 1 + web/assets/backgrounds/image_spec.md | 20 +++++++++--- web/js/sections/board-control.js | 29 ++++++++++++++--- web/js/sections/input-text.js | 29 ++++++++++++++--- web/js/sections/sequence-editor.js | 39 ++++++++++++++++++++--- 5 files changed, 99 insertions(+), 19 deletions(-) diff --git a/web/assets/backgrounds/default/spots.json b/web/assets/backgrounds/default/spots.json index e9c3b00..c358e85 100644 --- a/web/assets/backgrounds/default/spots.json +++ b/web/assets/backgrounds/default/spots.json @@ -1,5 +1,6 @@ { "hasAlphabet": true, + "parkPosition": { "x": 0, "y": 80 }, "bounds": { "xMin": -150, "xMax": 150, "yMin": -30, "yMax": 145 }, "spots": [ { "id": "YES", "label": "YES", "x": -70, "y": 90 }, diff --git a/web/assets/backgrounds/image_spec.md b/web/assets/backgrounds/image_spec.md index a00c0fa..fb51b91 100644 --- a/web/assets/backgrounds/image_spec.md +++ b/web/assets/backgrounds/image_spec.md @@ -46,6 +46,7 @@ This file tells the web UI where your letters/targets are located in **physical ```json { "hasAlphabet": true, + "parkPosition": { "x": 0, "y": 80 }, "bounds": { "xMin": -150, "xMax": 150, @@ -60,9 +61,20 @@ This file tells the web UI where your letters/targets are located in **physical } ``` - **hasAlphabet**: (Optional, boolean) If `true`, this background is used for the "Input Text" feature. The letters in `spots` are used to spell out typed input. +- **parkPosition**: (Optional, object) Defines a background-specific "Park Position" or resting spot where the arm can go when not actively in use. If omitted, defaults to `{ "x": 0, "y": 0 }`. - **bounds**: Defines the physical bounding box (in mm) that your background image represents. This automatically adjusts the UI's aspect ratio and coordinate mapping! - **spots**: Array of target objects. -- **id**: Unique identifier for the dropdown list. -- **label**: Human-readable text shown on the UI. -- **x**: The X coordinate in physical mm (`-150` to `+150`). -- **y**: The Y coordinate in physical mm (`-30` to `+145`). + - **id**: Unique identifier for the dropdown list. + - **label**: Human-readable text shown on the UI. + - **x**: The X coordinate in physical mm (`-150` to `+150`). + - **y**: The Y coordinate in physical mm (`-30` to `+145`). + +## 5. Excluded Zones (No-Go Zones) + +When defining spots or the `parkPosition`, you **must avoid** the central mechanism housing. The SCARA arm will reject commands that enter this region to prevent mechanical collisions. + +The exact exclusion box (the "No-Go Zone") is: +- **X Range:** `-30` mm to `+30` mm +- **Y Range:** `-10` mm to `+40` mm + +Make sure that your `parkPosition` and all clickable spots lie outside this rectangle. diff --git a/web/js/sections/board-control.js b/web/js/sections/board-control.js index 0c0c1cb..6d94208 100644 --- a/web/js/sections/board-control.js +++ b/web/js/sections/board-control.js @@ -96,9 +96,14 @@ function buildHTML() {
${isHomed ? '✓ HOMED' : '⚠ NOT HOMED! Movement Locked.'}
- +
+ + +
@@ -191,12 +196,21 @@ async function loadSpots() { try { const res = await fetch(`web/assets/backgrounds/${currentBg}/spots.json?v=${Date.now()}`); const data = await res.json(); + let parsedSpots = []; + let parkPosition = { x: 0, y: 0 }; + if (Array.isArray(data)) { - spots = data; + parsedSpots = data; } else { - spots = data.spots || []; + parsedSpots = data.spots || []; if (data.bounds) bounds = data.bounds; + if (data.parkPosition) parkPosition = data.parkPosition; } + + if (!parsedSpots.find(s => s.id === 'PARK')) { + parsedSpots.push({ id: 'PARK', label: 'Park Position', x: parkPosition.x, y: parkPosition.y }); + } + spots = parsedSpots; document.getElementById('bg-image').src = `web/assets/backgrounds/${currentBg}/bg.svg?v=${Date.now()}`; renderSpots(); } catch(e) { @@ -335,6 +349,11 @@ export default { } }); + document.getElementById('btn-park').addEventListener('click', () => { + const parkSpot = spots.find(s => s.id === 'PARK'); + if (parkSpot) executeMove(parkSpot); + }); + // ── List Selection ──────────────────────────────────────── const listEl = document.getElementById('spot-list'); const goBtn = document.getElementById('btn-go'); diff --git a/web/js/sections/input-text.js b/web/js/sections/input-text.js index 234898e..1707109 100644 --- a/web/js/sections/input-text.js +++ b/web/js/sections/input-text.js @@ -112,9 +112,14 @@ function buildHTML() {
${isHomed ? '✓ HOMED' : '⚠ NOT HOMED! Movement Locked.'}
- +
+ + +
@@ -245,12 +250,21 @@ async function loadSpots() { UI.log(`Background '${currentBg}' does not support an alphabet.`, 'warn'); } + let parsedSpots = []; + let parkPosition = { x: 0, y: 0 }; + if (Array.isArray(data)) { - spots = data; + parsedSpots = data; } else { - spots = data.spots || []; + parsedSpots = data.spots || []; if (data.bounds) bounds = data.bounds; + if (data.parkPosition) parkPosition = data.parkPosition; } + + if (!parsedSpots.find(s => s.id === 'PARK')) { + parsedSpots.push({ id: 'PARK', label: 'Park Position', x: parkPosition.x, y: parkPosition.y }); + } + spots = parsedSpots; document.getElementById('bg-image').src = `web/assets/backgrounds/${currentBg}/bg.svg?v=${Date.now()}`; @@ -404,6 +418,11 @@ export default { } }); + document.getElementById('btn-park').addEventListener('click', () => { + const parkSpot = spots.find(s => s.id === 'PARK'); + if (parkSpot) executeMove(parkSpot); + }); + // ── Spelling Logic ──────────────────────────────────────── const btnSpell = document.getElementById('btn-spell'); const btnStop = document.getElementById('btn-stop'); diff --git a/web/js/sections/sequence-editor.js b/web/js/sections/sequence-editor.js index 81d3a63..2e719c4 100644 --- a/web/js/sections/sequence-editor.js +++ b/web/js/sections/sequence-editor.js @@ -3,6 +3,7 @@ import UI from '../ui.js'; import IK from '../kinematics.js'; import Settings from '../settings.js'; import SequenceRunner from '../sequence-runner.js'; +import Motion from '../motion.js'; let eventCleanup = []; let isHomed = false; @@ -244,9 +245,14 @@ function buildHTML() {
${isHomed ? '✓ HOMED' : '⚠ NOT HOMED! Movement Locked.'}
- +
+ + +
@@ -409,14 +415,23 @@ async function loadSpots() { try { const res = await fetch(`web/assets/backgrounds/${currentBg}/spots.json?v=${Date.now()}`); const data = await res.json(); + let parsedSpots = []; + let parkPosition = { x: 0, y: 0 }; + if (Array.isArray(data)) { - spots = data; + parsedSpots = data; hasAlphabet = false; } else { - spots = data.spots || []; + parsedSpots = data.spots || []; if (data.bounds) bounds = data.bounds; + if (data.parkPosition) parkPosition = data.parkPosition; hasAlphabet = !!data.hasAlphabet; } + + if (!parsedSpots.find(s => s.id === 'PARK')) { + parsedSpots.push({ id: 'PARK', label: 'Park Position', x: parkPosition.x, y: parkPosition.y }); + } + spots = parsedSpots; document.getElementById('text-input-row').style.display = hasAlphabet ? 'grid' : 'none'; document.getElementById('text-input-divider').style.display = hasAlphabet ? 'block' : 'none'; @@ -723,6 +738,20 @@ export default { } }); + document.getElementById('btn-park').addEventListener('click', async () => { + const parkSpot = spots.find(s => s.id === 'PARK'); + if (parkSpot) { + if (!isHomed) { + UI.log('Cannot move: System is not homed!', 'error'); + return; + } + const modeEl = document.getElementById('seq-mode-select'); + const mode = modeEl ? modeEl.value : 'direct'; + UI.log(`Moving to Park Position (${parkSpot.x}, ${parkSpot.y})`, 'info'); + await Motion.goto(parkSpot.x, parkSpot.y, mode); + } + }); + // ── Sync Steps from BLE ─────────────────────────────────── const onBLEStatus = (e) => { const msg = e.detail;