From 320c6f1bd968f47492396baad133bf91ca305bb9 Mon Sep 17 00:00:00 2001 From: mikael-lovqvists-claude-agent Date: Wed, 1 Apr 2026 05:13:57 +0000 Subject: [PATCH] Add physical dimensions to bin editor for correct aspect ratio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When de-perspectiving a bin photo taken at an angle, inferring the output size from the quadrilateral shape squishes the result. Entering real-world W×H (mm) lets the server use the correct aspect ratio, scaled to the same resolution as the inferred size. - Bin editor dialog: W×H number inputs, pre-filled from saved phys_w/phys_h - PUT /api/bins/:id/corners: accepts optional phys_w/phys_h; when provided, derives bin_w/bin_h from the physical aspect ratio at equivalent area - phys_w/phys_h stored on the bin record for re-use on next edit - future-plans.md: bin types note (reusable dimensions per model) Co-Authored-By: Claude Sonnet 4.6 --- future-plans.md | 10 ++++++++++ public/app.mjs | 6 +++++- public/lib/api.mjs | 2 +- public/style.css | 16 ++++++++++++++++ public/templates.html | 9 +++++++++ server.mjs | 23 +++++++++++++++++++---- 6 files changed, 60 insertions(+), 6 deletions(-) diff --git a/future-plans.md b/future-plans.md index 7a7b6cb..4879ea5 100644 --- a/future-plans.md +++ b/future-plans.md @@ -381,6 +381,16 @@ used/visited locations at the top so you can quickly re-select where you just we Useful when processing a batch of components into the same storage location — you shouldn't have to navigate the grid picker from scratch each time. +## Bins + +### Bin types +Define reusable bin type records (e.g. "Sortimo L-Boxx insert small", "Wago 221 +connector box") that store physical dimensions (mm), and optionally a default +compartment layout. When creating or editing a bin, the user picks a type and the +dimensions are pre-filled — no need to re-enter for every bin of the same model. +This also enables filtering/grouping bins by type, and makes it easy to re-process +all bins of a type if the corner algorithm improves. + ## Grids ### Grid view layers diff --git a/public/app.mjs b/public/app.mjs index 7d3b891..8ea418c 100644 --- a/public/app.mjs +++ b/public/app.mjs @@ -2106,6 +2106,8 @@ function open_bin_editor(bin) { bin_editor_bin_id = bin.id; document.getElementById('bin-editor-name').value = bin.name; + document.getElementById('bin-editor-width').value = bin.phys_w ?? ''; + document.getElementById('bin-editor-height').value = bin.phys_h ?? ''; // Show dialog first so the canvas has correct layout dimensions before // load_image reads parentElement.clientWidth to size itself. @@ -2353,6 +2355,8 @@ async function init() { document.getElementById('bin-editor-save').addEventListener('click', async () => { const corners = bin_editor_instance?.get_corners(); const name = document.getElementById('bin-editor-name').value.trim(); + const phys_w = parseFloat(document.getElementById('bin-editor-width').value) || null; + const phys_h = parseFloat(document.getElementById('bin-editor-height').value) || null; if (!corners) { alert('Load an image first.'); return; } const id = bin_editor_bin_id; try { @@ -2361,7 +2365,7 @@ async function init() { const r = await api.rename_bin(id, name); updated = r.bin; } - const r2 = await api.update_bin_corners(id, corners); + const r2 = await api.update_bin_corners(id, corners, phys_w, phys_h); updated = r2.bin; all_bins = all_bins.map(b => b.id === id ? updated : b); document.getElementById('dialog-bin-editor').close(); diff --git a/public/lib/api.mjs b/public/lib/api.mjs index fa38b67..ff90334 100644 --- a/public/lib/api.mjs +++ b/public/lib/api.mjs @@ -66,7 +66,7 @@ export const get_bins = () => req('GET', '/api/bins'); export const create_bin_from_source = (source_id, name) => req('POST', '/api/bins/from-source', { source_id, name }); export const get_bin = (id) => req('GET', `/api/bins/${id}`); export const rename_bin = (id, name) => req('PUT', `/api/bins/${id}`, { name }); -export const update_bin_corners = (id, corners) => req('PUT', `/api/bins/${id}/corners`, { corners }); +export const update_bin_corners = (id, corners, phys_w, phys_h) => req('PUT', `/api/bins/${id}/corners`, { corners, phys_w, phys_h }); export const delete_bin = (id) => req('DELETE', `/api/bins/${id}`); export async function upload_bin(file, name) { diff --git a/public/style.css b/public/style.css index a72029b..6cf08e0 100644 --- a/public/style.css +++ b/public/style.css @@ -1972,6 +1972,22 @@ nav { flex: 1; } +.bin-editor-dims { + display: flex; + align-items: center; + gap: 0.4rem; +} + +.bin-editor-dims input { + width: 5rem; +} + +.form-hint { + font-size: 0.78rem; + color: var(--text-faint); + margin-left: 0.25rem; +} + .bin-editor-canvas { display: block; border-radius: 4px; diff --git a/public/templates.html b/public/templates.html index 41aac83..9e2bd81 100644 --- a/public/templates.html +++ b/public/templates.html @@ -656,6 +656,15 @@ +
+ +
+ + × + + Leave blank to infer from corners +
+
diff --git a/server.mjs b/server.mjs index f82c0bd..ecd8c23 100644 --- a/server.mjs +++ b/server.mjs @@ -741,19 +741,34 @@ app.post('/api/bins', upload.single('image'), async (req, res) => { app.put('/api/bins/:id/corners', async (req, res) => { const bin = get_bin(req.params.id); if (!bin) return fail(res, 'not found', 404); - const { corners } = req.body; + const { corners, phys_w, phys_h } = req.body; if (!corners || corners.length !== 4) return fail(res, 'corners must be array of 4 points'); try { - // Delete old processed image if any if (bin.image_filename) remove_image_file(bin.image_filename); const source_path = join('./data/images', bin.source_id); - const { bin_w, bin_h } = compute_bin_size(corners); + let bin_w, bin_h; + if (phys_w > 0 && phys_h > 0) { + // Use physical aspect ratio scaled to the same area as computed size + const computed = compute_bin_size(corners); + const area = computed.bin_w * computed.bin_h; + const aspect = phys_w / phys_h; + bin_h = Math.round(Math.sqrt(area / aspect)); + bin_w = Math.round(bin_h * aspect); + } else { + ({ bin_w, bin_h } = compute_bin_size(corners)); + } + const cells = await process_grid_image(source_path, corners, 1, 1, bin_w, bin_h, './data/images'); const image_filename = cells[0][0]; - const updated = { ...bin, corners, image_filename, bin_w, bin_h, updated_at: Date.now() }; + const updated = { + ...bin, corners, image_filename, bin_w, bin_h, + phys_w: phys_w > 0 ? phys_w : (bin.phys_w ?? null), + phys_h: phys_h > 0 ? phys_h : (bin.phys_h ?? null), + updated_at: Date.now(), + }; set_bin(updated); ok(res, { bin: updated }); } catch (err) {