Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The readout was proposed to the official project in [AgOpenGPS-Official/AgOpenWe

Most of these have been offered to the official project too. When one is merged there, it comes off this list.

- **Obstacles.** In the boundary list, each obstacle gets its own choice: a U-turn before it, Go around (the pass bends round it), or Drive through. Trees can be added as circles with "+ Circles", and small obstacles made round ("Round all" rounds every obstacle up to 10 m across; a bigger one keeps its shape), and "Size all" gives every circle up to 10 m across the diameter in the box. The Go around curve radius and safety margin are on the U-turn tab. Go around is still being tested: don't use it with AutoSteer on a real tractor yet.
- **Obstacles.** In the boundary list, each obstacle gets its own choice: a U-turn before it, Go around (the pass bends round it), or Drive through. Trees can be added as circles with "+ Circles", and small obstacles made round ("Round all" rounds every obstacle up to 10 m across; a bigger one keeps its shape), and "Size all" gives every circle up to 10 m across the diameter in the box. Obstacles ticked in the list (the box in front of each, or the one in the title for all) are outlined red on the map and deleted together with one confirmation; the outer boundary is never deleted that way. The Go around curve radius and safety margin are on the U-turn tab. Go around is still being tested: don't use it with AutoSteer on a real tractor yet.
- **Headland gaps.** With "Drive across a headland gap up to" set on the U-turn tab, a pass that crosses a narrow tip of the field is driven on with the sections off, and the turn is made at the far end.
- **U-turns with no room.** When no proper turn fits, the simple turn is moved back into the field instead of crossing the boundary. Where the headland clips a corner off the pass, AutoSteer drives on across it and says so (a thin spike of the headland line doesn't count).
- **U-turn beside a fence that comes in.** With a hard boundary, a turn that had to be moved back to keep the implement off the fence at the end of the pass could be made far too early instead, where the fence comes in beside the pass: it landed on the next pass inside its crop and left a strip of it unsprayed. Now it is moved back from the end of the pass only.
Expand Down
23 changes: 19 additions & 4 deletions Shared/AgOpenWeb.RemoteServer/wwwroot/css/tool-panels.css
Original file line number Diff line number Diff line change
Expand Up @@ -53,25 +53,25 @@
.rp-del { background: none; border: 0; color: #c97c7c; font-size: 15px; cursor: pointer; padding: 2px 6px; border-radius: 4px; }
.rp-del:hover { background: rgba(220,60,60,0.25); color: var(--text); }
/* Boundary recording menu. */
.bm-panel { width: 500px; font-family: system-ui, sans-serif; }
.bm-panel { width: 542px; font-family: system-ui, sans-serif; }
.bm-toolbar { display: flex; gap: 6px; justify-content: space-between; margin-bottom: 10px; }
.bm-tbtn { position: relative; width: 56px; height: 48px; border: 1px solid var(--border); border-radius: 8px;
background: rgba(var(--ov),0.06); cursor: pointer; display: flex; align-items: center; justify-content: center; }
.bm-tbtn:hover { background: rgba(var(--ov),0.12); }
.bm-tbtn img { width: 32px; height: 32px; }
.bm-tbtn:disabled { opacity: 0.35; cursor: default; }
.bm-plus { position: absolute; right: 4px; bottom: 2px; color: #F5F54D; font-weight: 700; font-size: 15px; }
.bm-lhdr { display: grid; grid-template-columns: 1fr 0.9fr 0.85fr 0.85fr 0.85fr 0.75fr; align-items: center; background: #1565C0; color: #fff;
.bm-lhdr { display: grid; grid-template-columns: 40px 1fr 0.9fr 0.85fr 0.85fr 0.85fr 0.75fr; align-items: center; background: #1565C0; color: #fff;
font-weight: 700; font-size: 12px; padding: 4px 8px; border-radius: 6px 6px 0 0; gap: 2px; }
.bm-lhdr > span { text-align: center; }
.bm-lhdr > span:first-child { text-align: left; }
.bm-lhdr > span:first-of-type { text-align: left; }
/* A choice's title: tap to give every obstacle that choice. */
.bm-colhdr { min-height: 40px; border: 1px solid rgba(255,255,255,0.35); border-radius: 6px; background: rgba(255,255,255,0.1);
color: #fff; font: inherit; font-size: 11px; line-height: 1.15; cursor: pointer; padding: 2px; display: flex;
flex-direction: column; align-items: center; justify-content: center; gap: 2px; }
.bm-colhdr:hover { background: rgba(255,255,255,0.2); }
.bm-list { background: rgba(var(--ov),0.03); border-radius: 0 0 6px 6px; max-height: min(46vh, 400px); overflow-y: auto; }
.bm-row { display: grid; grid-template-columns: 1fr 0.9fr 0.85fr 0.85fr 0.85fr 0.75fr; align-items: center; padding: 4px 8px;
.bm-row { display: grid; grid-template-columns: 40px 1fr 0.9fr 0.85fr 0.85fr 0.85fr 0.75fr; align-items: center; padding: 4px 8px;
font-size: 13px; cursor: pointer; border-bottom: 1px solid var(--bg-raised); gap: 2px; min-height: 40px; }
.bm-row.sel { background: var(--sel); }
.bm-row > span { text-align: center; }
Expand All @@ -93,6 +93,21 @@
.bm-choice.on .bm-dot.k2 { background: rgb(77,208,245); }
.bm-choice.on .bm-dot.k3 { background: rgb(176,176,176); }
.bm-hint { font-size: 11px; color: var(--text-muted); margin-top: 6px; }
/* Ticking obstacles to delete several at once: a box in front of each obstacle and in the
title; the ticked ones outlined red on the map (map-draw.js SKP.boundaryTicked). */
.bm-tick { width: 40px; height: 36px; border: 0; background: transparent; cursor: pointer; padding: 0;
display: flex; align-items: center; justify-content: center; }
.bm-box { width: 22px; height: 22px; box-sizing: border-box; border: 2px solid var(--text-muted); border-radius: 4px;
display: flex; align-items: center; justify-content: center; font-size: 15px; font-weight: 700; line-height: 1; color: #fff; }
.bm-lhdr .bm-box { border-color: rgba(255,255,255,0.8); }
.bm-tick.on .bm-box { background: rgb(229,57,53); border-color: rgb(229,57,53); }
.bm-tick.on .bm-box::after { content: '\2713'; }
.bm-tick.some .bm-box { background: rgba(229,57,53,0.55); border-color: rgb(229,57,53); }
.bm-tick.some .bm-box::after { content: '\2212'; }
.bm-tickbar { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.bm-tickcount { flex: 1; font-size: 13px; font-weight: 700; color: rgb(239,83,80); }
.bm-cbtn.bm-danger { border-color: rgb(229,57,53); color: rgb(239,83,80); }
.bm-cbtn.bm-danger:hover { background: rgba(229,57,53,0.2); }
/* Round obstacles: the diameter box and the circle buttons under the list. */
.bm-circ { display: flex; align-items: center; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
.bm-circlbl { font-size: 13px; color: var(--text-muted); min-width: 84px; }
Expand Down
11 changes: 11 additions & 0 deletions Shared/AgOpenWeb.RemoteServer/wwwroot/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"(− out / + in)": "(− out / + in)",
"+ Circles": "+ Circles",
"1 cm per click": "1 cm per click",
"1 obstacle ticked": "1 obstacle ticked",
"1 tool width": "1 tool width",
"100 = neutral": "100 = neutral",
"10× time: the simulation runs ten times faster, at the speed shown": "10× time: the simulation runs ten times faster, at the speed shown",
Expand Down Expand Up @@ -265,12 +266,17 @@
"Delete last point": "Delete last point",
"Delete NTRIP Profile": "Delete NTRIP Profile",
"Delete NTRIP profile '{name}'?": "Delete NTRIP profile '{name}'?",
"Delete obstacles": "Delete obstacles",
"Delete Profile": "Delete Profile",
"Delete Recorded Path": "Delete Recorded Path",
"Delete selected boundary": "Delete selected boundary",
"Delete Selected Profile": "Delete Selected Profile",
"Delete selected track": "Delete selected track",
"Delete the ticked obstacle? The outer boundary stays. This cannot be undone.": "Delete the ticked obstacle? The outer boundary stays. This cannot be undone.",
"Delete the ticked obstacles": "Delete the ticked obstacles",
"Delete the {n} ticked obstacles? The outer boundary stays. This cannot be undone.": "Delete the {n} ticked obstacles? The outer boundary stays. This cannot be undone.",
"Delete the {type} boundary? This cannot be undone.": "Delete the {type} boundary? This cannot be undone.",
"Delete ticked": "Delete ticked",
"Delete Track": "Delete Track",
"Delete Tram System": "Delete Tram System",
"Delete {kind} profile '{name}'?": "Delete {kind} profile '{name}'?",
Expand Down Expand Up @@ -1093,6 +1099,8 @@
"This mount point does not send RTCM 3. Its data is forwarded as it comes.": "This mount point does not send RTCM 3. Its data is forwarded as it comes.",
"This screen is out of date. Reconnecting…": "This screen is out of date. Reconnecting…",
"This screen now has control": "This screen now has control",
"Tick every obstacle, or none": "Tick every obstacle, or none",
"Tick to delete with the others ticked": "Tick to delete with the others ticked",
"Tilt toward horizon (3D)": "Tilt toward horizon (3D)",
"Tilt toward overhead (2D)": "Tilt toward overhead (2D)",
"Time": "Time",
Expand Down Expand Up @@ -1148,6 +1156,8 @@
"Units": "Units",
"Unknown": "Unknown",
"Unsaved Coverage": "Unsaved Coverage",
"Untick all": "Untick all",
"Untick every obstacle": "Untick every obstacle",
"Up one step": "Up one step",
"Upload Selected": "Upload Selected",
"Use": "Use",
Expand Down Expand Up @@ -1237,6 +1247,7 @@
"{n} messages, {bad} bad checksums, {r} replaced by newer": "{n} messages, {bad} bad checksums, {r} replaced by newer",
"{n} mount points": "{n} mount points",
"{n} mount points, showing {m}. Search to narrow it down.": "{n} mount points, showing {m}. Search to narrow it down.",
"{n} obstacles ticked": "{n} obstacles ticked",
"{n} pass": "{n} pass",
"{n} s ago": "{n} s ago",
"{n} tool widths": "{n} tool widths",
Expand Down
15 changes: 12 additions & 3 deletions Shared/AgOpenWeb.RemoteServer/wwwroot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1513,8 +1513,10 @@
<button id="bm-accept" class="bm-tbtn" title="Accept and close"><img src="/icons/OK64.png" alt=""></button>
</div>
<!-- What the tractor does at each obstacle: one choice per row; a choice's column title
gives it to every obstacle. Coloured as the obstacles on the map. -->
<div class="bm-lhdr"><span>Boundary</span><span>Size</span>
gives it to every obstacle. Coloured as the obstacles on the map. The box in front of
each obstacle ticks it for deleting several at once (the outer boundary has none);
the one in the title ticks them all, or none. -->
<div class="bm-lhdr"><button id="bm-tickall" class="bm-tick" title="Tick every obstacle, or none"><span class="bm-box"></span></button><span>Boundary</span><span>Size</span>
<button class="bm-colhdr" data-all="turn" title="Give every obstacle a U-turn"><span class="bm-dot k1"></span>U-turn</button>
<button class="bm-colhdr" data-all="goRound" title="Go around every obstacle"><span class="bm-dot k2"></span>Go around</button>
<button class="bm-colhdr" data-all="driveThrough" title="Drive through every obstacle"><span class="bm-dot k3"></span>Drive through</button>
Expand All @@ -1533,7 +1535,14 @@
<button id="bm-circ-round" class="bm-cbtn" title="Make the selected obstacle round: the smallest circle that holds it">Round</button>
<button id="bm-circ-all" class="bm-cbtn" title="Make every obstacle up to a tree's size round">Round all</button>
</div>
<div class="bm-hint">Tap a column title to give every obstacle that choice, or an obstacle on the map to find its row.</div>
<div id="bm-hint" class="bm-hint">Tap a column title to give every obstacle that choice, or an obstacle on the map to find its row.</div>
<!-- Shown instead of the hint while obstacles are ticked: the ticked ones are outlined red
on the map and deleted together, after one confirmation (the trash button too). -->
<div id="bm-tickbar" class="bm-tickbar" style="display:none">
<span id="bm-tickcount" class="bm-tickcount"></span>
<button id="bm-untick" class="bm-cbtn" title="Untick every obstacle">Untick all</button>
<button id="bm-delticked" class="bm-cbtn bm-danger" title="Delete the ticked obstacles">Delete ticked</button>
</div>
</div>
<!-- Boundary Player ("Stop Record Pause Boundary") — GPS drive-around recording. -->
<div id="boundaryplayer" class="ln-panel bp-panel">
Expand Down
2 changes: 2 additions & 0 deletions Shared/AgOpenWeb.RemoteServer/wwwroot/js/field-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ for (const b of document.querySelectorAll('#fb-tramedit [data-tmode]')) b.addEve
for (const b of document.querySelectorAll('#fb-tramedit [data-tdir]')) b.addEventListener('pointerdown', e => { e.stopPropagation(); tramSet('dir', b.dataset.tdir); });
document.getElementById('bm-delete').addEventListener('pointerdown', e => {
e.stopPropagation();
// Obstacles ticked in the list: those, all together (one confirmation), not the selected row.
if (bmTickedRows().size) { deleteTickedObstacles(); return; }
if (!boundary || boundary.selectedIndex < 0) return;
const it = boundary.items.find(i => i.index === boundary.selectedIndex);
showConfirm(tr('Delete Boundary'), tr('Delete the {type} boundary? This cannot be undone.', { type: it ? it.boundaryType : tr('selected', {}) }),
Expand Down
3 changes: 2 additions & 1 deletion Shared/AgOpenWeb.RemoteServer/wwwroot/js/frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ function renderSkia(canvas, rp) {
drawGridSk(canvas);
const outOfBounds = !!(tick && tick.op && tick.op.outOfBounds); // #494
if (scene) {
const selRing = boundaryMenuSelectedRing();
const selRing = boundaryMenuSelectedRing(), ticked = boundaryMenuTickedRings();
for (let bi = 0; bi < scene.boundaries.length; bi++) {
if (ticked && ticked.has(bi)) strokeFixedPtsSk(canvas, scene.boundaries[bi], true, SKP.boundaryTicked);
if (bi === selRing) strokeFixedPtsSk(canvas, scene.boundaries[bi], true, SKP.boundarySel);
strokeFixedPtsSk(canvas, scene.boundaries[bi], true, outOfBounds ? SKP.boundaryOut : boundaryRingPaint(bi));
}
Expand Down
77 changes: 75 additions & 2 deletions Shared/AgOpenWeb.RemoteServer/wwwroot/js/hud-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,13 +341,35 @@ function renderRecPath() {
// it: a U-turn, go around, or drive through, one lit cell of three. The obstacles are listed by
// size, smallest first, so the few big ones sit together at the end. Tap a row to select it
// (its obstacle is outlined on the map), a cell to give that obstacle the choice, a column
// title to give it to all of them.
// title to give it to all of them. The box in front of an obstacle ticks it, for deleting
// several at once (a field's trees drawn again).
const OBSTACLE_ACTIONS = ['turn', 'goRound', 'driveThrough']; // ObstacleAction 0, 1, 2
let bmShownSel = -1;
// The ticked obstacles, by list row, and the list they were ticked on. The rows are numbered
// by place, so when the list changes (a row added or deleted, an obstacle resized or made
// round, another field) every tick goes: a tick never moves onto another obstacle. A choice or
// the Hard flag changed keeps them. The host checks the row count again before it deletes.
const bmTicks = new Set();
let bmTickList = '';
function bmListKey(items) {
return items.map(i => i.index + ':' + i.boundaryType + ':' + i.sizeM + ':' + i.circleD + ':' + i.areaHa).join(';');
}
function bmTickedRows() {
const key = boundary ? bmListKey(boundary.items) : '';
if (key !== bmTickList) { bmTicks.clear(); bmTickList = key; }
return bmTicks;
}
function bmToggleTick(index) {
const t = bmTickedRows();
if (t.has(index)) t.delete(index); else t.add(index);
renderBoundaryMenu();
}
function renderBoundaryMenu() {
const b = boundary;
const list = document.getElementById('bm-list'); list.innerHTML = '';
const items = b ? b.items.slice() : [];
const ticks = bmTickedRows();
renderTickBar(items);
if (!items.length) { list.innerHTML = '<div class="fj-empty">No boundaries yet — drive around the field to record one.</div>'; return; }
items.sort((x, y) => (x.isInner - y.isInner) || (x.isInner ? x.sizeM - y.sizeM : 0) || x.index - y.index);
let selRow = null;
Expand All @@ -356,10 +378,20 @@ function renderBoundaryMenu() {
const sel = b && it.index === b.selectedIndex;
row.className = 'bm-row' + (sel ? ' sel' : '');
if (sel) selRow = row;
let tick;
if (it.isInner) {
tick = document.createElement('button');
tick.className = 'bm-tick' + (ticks.has(it.index) ? ' on' : '');
tick.title = tr('Tick to delete with the others ticked');
tick.innerHTML = '<span class="bm-box"></span>';
tick.addEventListener('pointerdown', ev => { ev.stopPropagation(); bmToggleTick(it.index); });
} else {
tick = document.createElement('span'); // the outer boundary is never deleted with the obstacles
}
const name = document.createElement('span'); name.className = 'bm-name'; name.textContent = it.boundaryType;
const size = document.createElement('span'); size.className = 'bm-area';
size.textContent = it.circleD > 0 ? '⌀ ' + fmtUnit(it.circleD, 'm', 1) : it.isInner ? fmtUnit(it.sizeM, 'm', 1) : fmtUnit(it.areaHa, 'ha');
row.append(name, size);
row.append(tick, name, size);
if (it.isInner) {
OBSTACLE_ACTIONS.forEach((act, k) => {
const c = document.createElement('button');
Expand Down Expand Up @@ -393,6 +425,47 @@ function renderBoundaryMenu() {
bmShownSel = selIndex;
renderCircleBar();
}
// The title's box (all ticked, some, none) and the bar under the list: how many are ticked,
// Untick all and Delete ticked, shown instead of the hint while any is ticked.
function renderTickBar(items) {
const n = bmTicks.size, inner = items.filter(i => i.isInner).length;
const all = document.getElementById('bm-tickall');
all.classList.toggle('on', n > 0 && n === inner);
all.classList.toggle('some', n > 0 && n < inner);
document.getElementById('bm-tickbar').style.display = n ? 'flex' : 'none';
document.getElementById('bm-hint').style.display = n ? 'none' : '';
document.getElementById('bm-tickcount').textContent = n === 1 ? tr('1 obstacle ticked') : tr('{n} obstacles ticked', { n });
}
// The ticked obstacles, deleted together after one confirmation; the outer boundary stays.
function deleteTickedObstacles() {
const ticks = bmTickedRows();
if (!ticks.size || !boundary) return;
const rows = [...ticks].sort((x, y) => x - y), count = boundary.items.length;
const msg = rows.length === 1
? tr('Delete the ticked obstacle? The outer boundary stays. This cannot be undone.')
: tr('Delete the {n} ticked obstacles? The outer boundary stays. This cannot be undone.', { n: rows.length });
showConfirm(tr('Delete obstacles'), msg, () => {
transport.send('boundary.deleteObstacles|' + count + ':' + rows.join(','));
bmTicks.clear();
renderBoundaryMenu();
});
}
document.getElementById('bm-tickall').addEventListener('pointerdown', e => {
e.stopPropagation();
const ticks = bmTickedRows();
const inner = boundary ? boundary.items.filter(i => i.isInner) : [];
if (!inner.length) { showToast(tr('The field has no obstacles')); return; }
if (ticks.size === inner.length) ticks.clear();
else for (const i of inner) ticks.add(i.index);
renderBoundaryMenu();
});
document.getElementById('bm-untick').addEventListener('pointerdown', e => { e.stopPropagation(); bmTicks.clear(); renderBoundaryMenu(); });
document.getElementById('bm-delticked').addEventListener('pointerdown', e => { e.stopPropagation(); deleteTickedObstacles(); });
// The rings ticked in the open boundary menu (the list and the rings share their index), outlined
// red on the map. Every frame: the ticks were checked against the list when it last drew.
function boundaryMenuTickedRings() {
return bmTicks.size && document.getElementById('boundarymenu').classList.contains('open') ? bmTicks : null;
}
function choiceLabel(k) { return k === 1 ? tr('Go around') : k === 2 ? tr('Drive through') : tr('U-turn'); }
for (const h of document.querySelectorAll('#boundarymenu .bm-colhdr')) h.addEventListener('pointerdown', e => {
e.stopPropagation();
Expand Down
1 change: 1 addition & 0 deletions Shared/AgOpenWeb.RemoteServer/wwwroot/js/map-draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ function updateLineWidths() {
SKP.boundaryInner.setStrokeWidth(wb(3.0)); // boundaryInner 1 × 3
SKP.boundaryGoRound.setStrokeWidth(wb(3.0)); SKP.boundaryDriveThru.setStrokeWidth(wb(3.0));
SKP.boundarySel.setStrokeWidth(wb(3.0) + 5);
SKP.boundaryTicked.setStrokeWidth(wb(3.0) + 7);
SKP.boundaryOut.setStrokeWidth(3 * 2 * k); // out of bounds: AgOpenGPS ABLine.lineWidth × 3 (#494)
SKP.headland.setStrokeWidth(w(3.0)); // headland 1 × 3
SKP.guidance.setStrokeWidth(w(1.5)); // trackActive 0.5 × 3
Expand Down
Loading
Loading