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
Binary file modified Codesys/ArfBot.project
Binary file not shown.
212 changes: 160 additions & 52 deletions Codesys/ArfBot.xml

Large diffs are not rendered by default.

40 changes: 18 additions & 22 deletions Codesys/_cnc/CNC_Cone.cnc
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
( G91 incremental | G17 xy | G02 cw with XY end and R )
( Trafo ABC = yaw / pitch / roll. Look-at the apex at every corner. )
( Jog TCP onto the apex, tool Z along the cone axis, then run. )
( H = 40, R = 23.094, cone half-angle = 30 )
( )
( End of each quarter, ABC that point tool Z at the apex: )
( start / N060: A0 B30 C0 lean in +X, pitch )
( N070 +Y: A0 B0 C30 lean in -Y, roll )
( N080 +X: A0 B-30 C0 lean in -X, pitch the other way )
( N090 -Y: A0 B0 C-30 lean in +Y, roll the other way )
( N100 -X: A0 B30 C0 back to start )
( G91 values below are the deltas that reach those end angles. )

N000 G91
N020 G17
( G17 xy plane | G18 zx plane | G16 ijk normal )
( G91 incremental | G99 IJK relative to current TCP )
( G55 rel DCS | G56 current pose = DCS origin | G53 reset DCS )
( G51 start angle smoothing | G50 end )
( A yaw, B pitch, C roll. Trafo is yaw/pitch/roll. )
( Jog TCP onto the apex, tool Z along the cone axis, then run. )

N050 G01 X0 Y0 Z0 A0 B30 C0 F30
N060 G01 X-23.094 Y0 Z-40 A0 B0 C0 F30
N000 G91 ( incremental, like CNC_FromFile )
N010 G17 ( xy plane - circle is perpendicular to the cone axis )

N070 G02 X23.094 Y23.094 R23.094 A0 B-30 C30 F30
N080 G02 X23.094 Y-23.094 R23.094 A0 B-30 C-30 F30
N090 G02 X-23.094 Y-23.094 R23.094 A0 B30 C-30 F30
N100 G02 X-23.094 Y23.094 R23.094 A0 B30 C30 F30
(N040 M510) ( wait for flag 10 )
N050 G01 X-60 Y0 Z-80 A0 B10 C0 E300 F100

N110 G01 X23.094 Y0 Z40 A0 B-30 C0 F30
N130 G53
N070 G02 X60 Y60 R60 A0 B-10 C-10
N070 G02 X60 Y-60 R60 A0 B-10 C10
N070 G03 X-60 Y60 R60 A0 B10 C-10
N070 G03 X-60 Y-60 R60 A0 B10 C10
N070 G03 X60 Y-60 R60 A0 B-10 C10
N070 G03 X60 Y60 R60 A0 B-10 C-10

N140 G53 ( reset decoder coordinate system )
6 changes: 3 additions & 3 deletions Web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ One Flask app on port 5000 that hosts the operator pages:

| URL | Page |
| --- | --- |
| `http://<pi>:5000/vision` | Vision template capture and locate ROI |
| `http://<pi>:5000/vision/files` | Saved templates |
| `http://<pi>:5000/vision` | Vision: Capture, ROI, and Templates |
| `http://<pi>:5000/vision/templates` | Saved templates (old `/vision/files` redirects here) |
| `http://<pi>:5000/animator` | 6-axis robot animator (joints, TCP / MCS / PCS, Euler convention) |
| `http://<pi>:5000/bluetooth` | Bluetooth pairing (DualSense and other adapters) |
| `http://<pi>:5000/vision/output_sized?width=365&height=255` | HMI vision result image |

`/` redirects to `/vision` when vision is enabled, `/bluetooth` on a `--plc-only` install, or `/animator` if that is the only enabled page. Old `/template`, `/files/`, and `/output_sized` URLs redirect to the new paths.

The CODESYS HMI loads `/vision/output_sized`. Template files still go to `/var/opt/codesys/PlcLogic/Application/Vision/Templates`. Locate search ROI is `.../Vision/roi.yaml`, set from the Vision page ROI tab (`GET`/`POST /vision/roi`). The latest processed image is still `/var/opt/codesys/PlcLogic/visu/outputimage.jpg`.
The CODESYS HMI loads `/vision/output_sized`. Template files still go to `/var/opt/codesys/PlcLogic/Application/Vision/Templates`. Names cannot contain spaces; the capture size is appended (`screwdriver_640x400.jpg`) so a 2000×2000 snip is not used on a 640×400 Locate. Locate search ROI is `.../Vision/roi.yaml`, set from the Vision page ROI tab (`GET`/`POST /vision/roi`). The latest processed image is still `/var/opt/codesys/PlcLogic/visu/outputimage.jpg`.

## Capture size and HQ camera FOV

Expand Down
24 changes: 22 additions & 2 deletions Web/app/static/css/arfbot-night.css
Original file line number Diff line number Diff line change
Expand Up @@ -469,17 +469,30 @@ pre.log {
overflow: hidden;
margin: 0 auto 12px;
}
.mode-toggle button {
.mode-toggle button,
.mode-toggle a {
border: none;
border-radius: 0;
margin: 0;
background: var(--control);
color: var(--muted);
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
font: inherit;
font-weight: 650;
}
.mode-toggle button.active {
.mode-toggle button.active,
.mode-toggle a.active {
background: var(--accent);
color: var(--text-on-accent);
}
.muted {
color: var(--muted);
font-size: 13px;
}
#roi-status {
color: var(--accent);
font-weight: 650;
Expand Down Expand Up @@ -571,6 +584,13 @@ pre.log {
max-width: min(100%, 640px);
height: auto;
}
#template-preview {
width: 100%;
margin: 0;
color: var(--muted);
font-size: 12px;
font-weight: 650;
}
#canvas {
cursor: crosshair;
border: 1px solid var(--frame-soft);
Expand Down
5 changes: 2 additions & 3 deletions Web/app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{% else %}
<title>ArfBot</title>
{% endif %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/arfbot-night.css') }}?v=fov-notes">
<link rel="stylesheet" href="{{ url_for('static', filename='css/arfbot-night.css') }}?v=vision-tabs">
{% block head %}{% endblock %}
</head>
<body class="{% block body_class %}{% endblock %}">
Expand Down Expand Up @@ -36,8 +36,7 @@
{% endif %}
<nav class="nav">
{% if enable_vision %}
<a href="{{ url_for('vision.template') }}" {% if request.endpoint in ['vision.template'] %}class="active"{% endif %}>Vision</a>
<a href="{{ url_for('vision.files') }}" {% if request.endpoint in ['vision.files'] %}class="active"{% endif %}>Files</a>
<a href="{{ url_for('vision.template') }}" {% if request.endpoint in ['vision.template', 'vision.templates', 'vision.files'] %}class="active"{% endif %}>Vision</a>
{% endif %}
{% if enable_animator %}
<a href="{{ url_for('animator.index') }}" {% if request.endpoint and request.endpoint.startswith('animator.') %}class="active"{% endif %}>Animator</a>
Expand Down
12 changes: 12 additions & 0 deletions Web/app/templates/vision/_tabs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% set vision_tab = vision_tab|default('capture') %}
<div class="mode-toggle vision-tabs" role="tablist">
{% if vision_tab == 'templates' %}
<a href="{{ url_for('vision.template') }}">Capture</a>
<a href="{{ url_for('vision.template', tab='roi') }}">ROI</a>
<a href="{{ url_for('vision.templates') }}" class="active">Templates</a>
{% else %}
<button type="button" id="mode-capture" class="{% if vision_tab == 'capture' %}active{% endif %}">Capture</button>
<button type="button" id="mode-roi" class="{% if vision_tab == 'roi' %}active{% endif %}">ROI</button>
<a href="{{ url_for('vision.templates') }}">Templates</a>
{% endif %}
</div>
8 changes: 5 additions & 3 deletions Web/app/templates/vision/saved_templates.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{% extends "base.html" %}
{% block page_class %}centered{% endblock %}
{% block page_class %}centered vision-page{% endblock %}

{% block content %}
<h1>Saved Templates</h1>
<p>Templates live in /var/opt/codesys/PlcLogic/Application/Vision/Templates</p>
<h1 class="vision-title">Templates</h1>
{% set vision_tab = 'templates' %}
{% include "vision/_tabs.html" %}
<p class="muted">Saved crops in /var/opt/codesys/PlcLogic/Application/Vision/Templates. Names include the capture size (name_WIDTHxHEIGHT.jpg).</p>

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script type="text/javascript">
Expand Down
97 changes: 72 additions & 25 deletions Web/app/templates/vision/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
{% block content %}
<h1 class="vision-title" id="vision-title">Click and Drag to Create a Template</h1>

<div class="mode-toggle" role="tablist">
<button type="button" id="mode-template" class="active">Template</button>
<button type="button" id="mode-roi">ROI</button>
</div>
{% set vision_tab = 'roi' if request.args.get('tab') == 'roi' else 'capture' %}
{% include "vision/_tabs.html" %}

<div id="results">
<p id="current">pointer location: x: , y:</p>
Expand All @@ -26,7 +24,7 @@ <h1 class="vision-title" id="vision-title">Click and Drag to Create a Template</

<details class="capture-notes">
<summary>Capture size vs field of view (IMX477)</summary>
<p>libcamera picks a sensor mode from width×height, then center-crops to that aspect. Different sizes do not see the same scene. Use the same size for cal, ROI, templates, and Locate.</p>
<p>libcamera picks a sensor mode from width×height, then center-crops to that aspect. Different sizes do not see the same scene. Use the same size for cal, ROI, templates, and Locate. Saved templates are named <code>name_WIDTHxHEIGHT.jpg</code> so a 2000×2000 snip is not used on a 640×400 search.</p>
<table>
<thead>
<tr><th>Requested</th><th>Sensor mode</th><th>Field of view</th></tr>
Expand All @@ -42,16 +40,17 @@ <h1 class="vision-title" id="vision-title">Click and Drag to Create a Template</
</details>

<div id="imgcontainer">
<img class="img" id="cameraimage" src="{{ url_for('vision.captured_image') }}" alt="" />
<img class="img" id="cameraimage" alt="Capture an image to begin" />
<canvas id="canvas" width="640" height="400"></canvas>
</div>

<div class="controls" id="template-controls">
<form>
<label for="template_filename">Template Name:</label>
<input type="text" id="template_filename" name="template_filename" />
<input type="text" id="template_filename" name="template_filename" autocomplete="off" spellcheck="false" title="No spaces. Capture size is appended automatically." />
<a href="#" id="save"><button type="button" class="primary">Save Template</button></a>
</form>
<p id="template-preview">Saved as: _640x400.jpg</p>
</div>

<div class="controls" id="roi-controls" hidden>
Expand Down Expand Up @@ -111,6 +110,7 @@ <h1 class="vision-title" id="vision-title">Click and Drag to Create a Template</
canvas.style.height = dispH + "px";
drawOverlay();
updateRoiStatus();
updateTemplatePreview();
}

function roiFromPoints(a, b) {
Expand Down Expand Up @@ -193,8 +193,13 @@ <h1 class="vision-title" id="vision-title">Click and Drag to Create a Template</

function setMode(next) {
mode = next;
document.getElementById("mode-template").classList.toggle("active", mode === "template");
document.getElementById("mode-roi").classList.toggle("active", mode === "roi");
var captureBtn = document.getElementById("mode-capture");
var roiBtn = document.getElementById("mode-roi");
if (captureBtn) captureBtn.classList.toggle("active", mode === "template");
if (roiBtn) roiBtn.classList.toggle("active", mode === "roi");
if (window.history && window.history.replaceState) {
window.history.replaceState(null, "", mode === "roi" ? "/vision?tab=roi" : "/vision");
}
document.getElementById("template-controls").hidden = mode !== "template";
document.getElementById("roi-controls").hidden = mode !== "roi";
document.getElementById("croppedcontainer").hidden = mode !== "template";
Expand Down Expand Up @@ -271,17 +276,8 @@ <h1 class="vision-title" id="vision-title">Click and Drag to Create a Template</

$(function() {
$('a#capture').on('click', function(e) {
e.preventDefault()
$.getJSON('/captured_image',
function(data) {
})
.done(function() {
})
.fail(function() {
})
.always(function() {
refreshCameraImage();
});
e.preventDefault();
refreshCameraImage();
return false;
});
});
Expand All @@ -292,7 +288,7 @@ <h1 class="vision-title" id="vision-title">Click and Drag to Create a Template</
});
});

document.getElementById("mode-template").addEventListener("click", function() { setMode("template"); });
document.getElementById("mode-capture").addEventListener("click", function() { setMode("template"); });
document.getElementById("mode-roi").addEventListener("click", function() { setMode("roi"); });
document.getElementById("save-roi").addEventListener("click", saveRoi);
document.getElementById("reset-roi").addEventListener("click", function() {
Expand Down Expand Up @@ -320,10 +316,51 @@ <h1 class="vision-title" id="vision-title">Click and Drag to Create a Template</
})
img.setAttribute('src', "{{ url_for('vision.cropped') }}" + "?top_left="+Math.round(startPoint[0])+","+Math.round(startPoint[1])+"&bot_right="+Math.round(endPoint[0])+","+Math.round(endPoint[1])+"&uid=" + new Date().getTime())
}
function templateBaseName() {
return (document.getElementById("template_filename").value || "").replace(/\s+/g, "");
}

function templateFileName() {
var base = templateBaseName().replace(/\.(jpg|jpeg|png)$/i, "");
base = base.replace(/_\d+x\d+$/i, "");
return base + "_" + captureWidth + "x" + captureHeight + ".jpg";
}

function updateTemplatePreview() {
var el = document.getElementById("template-preview");
var base = templateBaseName();
if (!base) {
el.textContent = "Saved as: name_" + captureWidth + "x" + captureHeight + ".jpg (no spaces)";
return;
}
el.textContent = "Saved as: " + templateFileName();
}

function saveCroppedImage() {
$.get("{{ url_for('vision.save_template') }}"+"/"+template_filename.value+".jpg",function(data,status) {
},'html');
alert("Template is saved!");
var base = templateBaseName();
document.getElementById("template_filename").value = base;
if (!base) {
alert("Enter a template name with no spaces.");
return;
}
if (/\s/.test(base)) {
alert("Template name cannot contain spaces.");
return;
}
var filename = templateFileName();
fetch("{{ url_for('vision.save_template') }}/" + encodeURIComponent(filename) +
"?width=" + captureWidth + "&height=" + captureHeight)
.then(function(res) { return res.json().then(function(data) { return {ok: res.ok, data: data}; }); })
.then(function(result) {
if (!result.ok) {
alert(result.data.error || "Template save failed");
return;
}
alert("Template is saved as " + result.data.filename);
})
.catch(function() {
alert("Template save failed");
});
}
function saveRoi() {
var roi = pendingRoi || savedRoi;
Expand Down Expand Up @@ -366,7 +403,17 @@ <h1 class="vision-title" id="vision-title">Click and Drag to Create a Template</
h.value = captureHeight;
fitCaptureFrame();
loadRoi();
setMode("template");
var tab = new URLSearchParams(window.location.search).get("tab");
setMode(tab === "roi" ? "roi" : "template");
var nameInput = document.getElementById("template_filename");
nameInput.addEventListener("input", function() {
var cleaned = nameInput.value.replace(/\s+/g, "");
if (cleaned !== nameInput.value) {
nameInput.value = cleaned;
}
updateTemplatePreview();
});
updateTemplatePreview();
}

document.getElementById("cameraimage").addEventListener("load", function() {
Expand Down
Loading
Loading