From 1ef6cca1492bd5760319c79c893537a7ac8f2b91 Mon Sep 17 00:00:00 2001 From: solocla Date: Mon, 6 Jul 2026 12:06:14 +0200 Subject: [PATCH] fixed button cad area --- public/userarea/cad-area.php | 427 +++++++++++++++++++++++++++++------ 1 file changed, 358 insertions(+), 69 deletions(-) diff --git a/public/userarea/cad-area.php b/public/userarea/cad-area.php index f118770..5d8cd55 100644 --- a/public/userarea/cad-area.php +++ b/public/userarea/cad-area.php @@ -234,12 +234,30 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); .manual-toolbar { display: flex; - gap: 8px; + gap: 6px 14px; justify-content: center; align-items: center; flex-wrap: wrap; } + .toolbar-group { + display: flex; + gap: 5px; + align-items: center; + padding-right: 14px; + border-right: 1px solid #cbd5e1; + } + + .toolbar-group:last-child { + border-right: none; + padding-right: 0; + } + + .manual-view-badge { + padding: 5px 10px; + font-size: 0.82rem; + } + .manual-toolbar .btn.active { box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.2); font-weight: 700; @@ -450,69 +468,73 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
- +
+ - + - + - + +
- +
+ - + - + - + - + +
- +
+ - + - + - + +
- +
+ + Vista: pagina intera + - - Vista: pagina intera - - - + +
@@ -615,6 +637,14 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); let lastSnapMousePos = null; let edgeCacheDirty = true; + let isBrushing = false; + let lastBrushPoint = null; + let brushStartIndex = 0; + + const BRUSH_STEP = 10; + const BRUSH_SIMPLIFY_EPSILON = 2.0; + const BRUSH_CONTINUITY_RATIO = 0.7; + let lastManualResult = null; $(document).ready(function() { @@ -1084,7 +1114,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); if (edgeSnapBtn) { edgeSnapBtn.classList.remove('btn-warning', 'active'); edgeSnapBtn.classList.add('btn-outline-warning'); - edgeSnapBtn.innerText = '🧲 Magnete bordo OFF'; + edgeSnapBtn.innerText = '🧲 OFF'; } setTool('roi'); @@ -1108,7 +1138,27 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); return; } + if (currentTool === 'brush') { + if (!mmPerPx) { + Swal.fire({ + icon: 'warning', + title: 'Calibrazione mancante', + text: 'Prima devi calibrare una quota nota.' + }); + return; + } + isBrushing = true; + brushStartIndex = polygonPoints.length; + + const startPos = snapToNearestEdge(pos, EDGE_SNAP_RADIUS) || pos; + polygonPoints.push(startPos); + lastBrushPoint = startPos; + + markManualResultDirty(); + redrawManualOverlay(); + return; + } if (currentTool === 'auto_roi') { isDrawingAutoContourRoi = true; autoContourRoiStartX = pos.x; @@ -1170,7 +1220,34 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); redrawManualOverlay(); return; } + if (currentTool === 'brush' && isBrushing) { + if (!lastBrushPoint || distance(pos, lastBrushPoint) < BRUSH_STEP) { + return; + } + let added = false; + let guard = 0; + + while (guard < 25 && lastBrushPoint && distance(pos, lastBrushPoint) > BRUSH_STEP) { + guard++; + + const next = brushWalkAlongEdge(lastBrushPoint, pos); + + if (!next) { + break; + } + + polygonPoints.push(next); + lastBrushPoint = next; + added = true; + } + + if (added) { + redrawManualOverlay(); + } + + return; + } if (currentTool === 'edit' && isDraggingEditPoint && selectedEditPoint) { const finalPos = getSnappedPoint(pos); updateSelectedPointPosition(finalPos); @@ -1219,7 +1296,26 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); return; } + if (currentTool === 'brush' && isBrushing) { + isBrushing = false; + lastBrushPoint = null; + if (polygonPoints.length - brushStartIndex >= 3) { + const stroke = polygonPoints.slice(brushStartIndex); + const simplified = simplifyRdp(stroke, BRUSH_SIMPLIFY_EPSILON); + polygonPoints = polygonPoints.slice(0, brushStartIndex).concat(simplified); + } + + markManualResultDirty(); + redrawManualOverlay(); + updateManualPreview(); + + setManualStatus( + 'Tratto completato: ' + polygonPoints.length + ' punti totali. ' + + 'Continua con un altro tratto, oppure passa a "Modifica punti" per rifinire e poi calcola l\u2019area.' + ); + return; + } if (currentTool === 'auto_roi' && isDrawingAutoContourRoi) { isDrawingAutoContourRoi = false; @@ -2638,6 +2734,193 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); parts.length ? parts.join(' | ') : 'Nessun dato calcolato.'; } + function segmentDarkRatio(a, b) { + const steps = Math.max(4, Math.round(Math.hypot(b.x - a.x, b.y - a.y))); + let dark = 0; + + for (let i = 1; i <= steps; i++) { + const t = i / steps; + const x = Math.round(a.x + (b.x - a.x) * t); + const y = Math.round(a.y + (b.y - a.y) * t); + + let found = false; + + for (let yy = -1; yy <= 1 && !found; yy++) { + for (let xx = -1; xx <= 1 && !found; xx++) { + const px = x + xx; + const py = y + yy; + + if (px < 0 || py < 0 || px >= edgeGrayW || py >= edgeGrayH) { + continue; + } + + if (edgeGray[py * edgeGrayW + px] <= EDGE_DARK_THRESHOLD) { + found = true; + } + } + } + + if (found) { + dark++; + } + } + + return dark / steps; + } + + function brushWalkAlongEdge(fromPoint, mousePos) { + if (edgeCacheDirty || !edgeGray) { + rebuildEdgeCache(); + } + + if (!edgeGray) { + return null; + } + + const w = edgeGrayW; + const h = edgeGrayH; + + const dirX = mousePos.x - fromPoint.x; + const dirY = mousePos.y - fromPoint.y; + const dirLen = Math.hypot(dirX, dirY); + + if (dirLen < 0.001) { + return null; + } + + const ndx = dirX / dirLen; + const ndy = dirY / dirLen; + + const fx = Math.round(fromPoint.x); + const fy = Math.round(fromPoint.y); + + let sx = -1; + let sy = -1; + let sBest = Infinity; + + for (let y = Math.max(0, fy - 6); y <= Math.min(h - 1, fy + 6); y++) { + for (let x = Math.max(0, fx - 6); x <= Math.min(w - 1, fx + 6); x++) { + if (edgeGray[y * w + x] > EDGE_DARK_THRESHOLD) { + continue; + } + + const d = (x - fromPoint.x) * (x - fromPoint.x) + (y - fromPoint.y) * (y - fromPoint.y); + + if (d < sBest) { + sBest = d; + sx = x; + sy = y; + } + } + } + + if (sx < 0) { + return null; + } + + const R = Math.ceil(BRUSH_STEP * 1.8); + const x0 = Math.max(0, sx - R); + const y0 = Math.max(0, sy - R); + const x1 = Math.min(w - 1, sx + R); + const y1 = Math.min(h - 1, sy + R); + const lw = x1 - x0 + 1; + + const visited = new Uint8Array(lw * (y1 - y0 + 1)); + let queue = [sx, sy]; + visited[(sy - y0) * lw + (sx - x0)] = 1; + + let best = null; + let bestScore = -Infinity; + const minDist = BRUSH_STEP * 0.75; + + while (queue.length) { + const next = []; + + for (let q = 0; q < queue.length; q += 2) { + const cx = queue[q]; + const cy = queue[q + 1]; + + const ddx = cx - fromPoint.x; + const ddy = cy - fromPoint.y; + const dist = Math.hypot(ddx, ddy); + + if (dist >= minDist) { + const forward = (ddx * ndx + ddy * ndy) / dist; + + if (forward > 0.05) { + const score = forward * 10 - Math.abs(dist - BRUSH_STEP) * 0.5; + + if (score > bestScore) { + bestScore = score; + best = { + x: cx, + y: cy + }; + } + } + } + + for (let oy = -1; oy <= 1; oy++) { + for (let ox = -1; ox <= 1; ox++) { + if (ox === 0 && oy === 0) { + continue; + } + + const nx = cx + ox; + const ny = cy + oy; + + if (nx < x0 || ny < y0 || nx > x1 || ny > y1) { + continue; + } + + const vi = (ny - y0) * lw + (nx - x0); + + if (visited[vi]) { + continue; + } + + if (edgeGray[ny * w + nx] > EDGE_DARK_THRESHOLD) { + continue; + } + + visited[vi] = 1; + next.push(nx, ny); + } + } + } + + queue = next; + } + + return best; + } + + function simplifyRdp(points, epsilon) { + if (points.length < 3) { + return points; + } + + let maxDist = 0; + let maxIndex = 0; + + for (let i = 1; i < points.length - 1; i++) { + const d = pointToSegmentDistance(points[i], points[0], points[points.length - 1]); + + if (d > maxDist) { + maxDist = d; + maxIndex = i; + } + } + + if (maxDist > epsilon) { + const left = simplifyRdp(points.slice(0, maxIndex + 1), epsilon); + const right = simplifyRdp(points.slice(maxIndex), epsilon); + return left.slice(0, -1).concat(right); + } + + return [points[0], points[points.length - 1]]; + } + function setTool(tool) { currentTool = tool; @@ -2649,7 +2932,13 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); if (autoRoiBtn) { autoRoiBtn.classList.remove('active'); } + const brushBtn = document.getElementById('toolBrushBtn'); + if (brushBtn) { + brushBtn.classList.remove('active'); + } + isBrushing = false; + lastBrushPoint = null; const holeBtn = document.getElementById('toolHoleBtn'); if (holeBtn) { holeBtn.classList.remove('active'); @@ -2685,7 +2974,10 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); document.getElementById('toolPolygonBtn').classList.add('active'); setManualStatus('Modalità profilo esterno: clicca i punti del contorno esterno. Il poligono viene chiuso automaticamente.'); } - + if (tool === 'brush') { + document.getElementById('toolBrushBtn').classList.add('active'); + setManualStatus('Pennello magnetico: tieni premuto e trascina lungo il bordo. I punti si agganciano al tratto scuro. Rilascia per chiudere il tratto, poi rifinisci con "Modifica punti".'); + } if (tool === 'auto_roi') { document.getElementById('toolAutoContourRoiBtn').classList.add('active'); setManualStatus('Modalità ROI autocontorno: disegna un rettangolo stretto solo attorno al profilo, escludendo quote, testi e frecce.'); @@ -2745,7 +3037,19 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); setTool('polygon'); }); + document.getElementById('toolBrushBtn').addEventListener('click', function() { + if (!mmPerPx) { + Swal.fire({ + icon: 'info', + title: 'Prima calibra la scala', + text: 'Clicca \u201cCalibra quota\u201d e seleziona due punti su una quota nota.' + }); + return; + } + rebuildEdgeCache(); + setTool('brush'); + }); document.getElementById('edgeSnapBtn').addEventListener('click', function() { edgeSnapEnabled = !edgeSnapEnabled; @@ -2754,7 +3058,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); if (edgeSnapEnabled) { btn.classList.remove('btn-outline-warning'); btn.classList.add('btn-warning', 'active'); - btn.innerText = '🧲 Magnete bordo ON'; + btn.innerText = '🧲 ON'; rebuildEdgeCache(); @@ -2764,7 +3068,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); } else { btn.classList.remove('btn-warning', 'active'); btn.classList.add('btn-outline-warning'); - btn.innerText = '🧲 Magnete bordo OFF'; + btn.innerText = '🧲 OFF'; edgeSnapPreviewPoint = null; redrawManualOverlay(); @@ -2893,22 +3197,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); renderManualRoiViewFromCurrentRoi(); }); - document.getElementById('toolAutoContourRoiBtn').addEventListener('click', function() { - if (manualCurrentView !== 'roi') { - Swal.fire({ - icon: 'info', - title: 'Prima fai Zoom su ROI', - text: 'La ROI autocontorno va disegnata dentro la vista ingrandita della sezione.' - }); - return; - } - setTool('auto_roi'); - }); - - document.getElementById('autoContourBtn').addEventListener('click', function() { - proposeAutoContour(); - }); document.getElementById('fullPageBtn').addEventListener('click', function() { Swal.fire({