fixed button cad area

This commit is contained in:
2026-07-06 12:06:14 +02:00
parent 4fc2828aaf
commit 1ef6cca149
+345 -56
View File
@@ -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,70 +468,74 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
</div>
<div class="manual-toolbar mb-3">
<button type="button" id="toolRoiBtn" class="btn btn-outline-primary active">
🎯 1. ROI
<div class="toolbar-group">
<button type="button" id="toolRoiBtn" class="btn btn-sm btn-outline-primary active" title="Disegna un rettangolo attorno alla sezione">
🎯 ROI
</button>
<button type="button" id="toolCalibrationBtn" class="btn btn-outline-danger">
📏 2. Calibra quota
<button type="button" id="zoomRoiBtn" class="btn btn-sm btn-outline-info" title="Ingrandisci la zona selezionata">
🔍 Zoom
</button>
<button type="button" id="toolPolygonBtn" class="btn btn-outline-success">
✏️ 3. Disegna profilo
<button type="button" id="fullPageBtn" class="btn btn-sm btn-outline-dark" disabled title="Torna alla pagina intera">
↩️ Pagina
</button>
<button type="button" id="edgeSnapBtn" class="btn btn-outline-warning">
🧲 Magnete bordo OFF
<button type="button" id="toolCalibrationBtn" class="btn btn-sm btn-outline-danger" title="Clicca due punti su una quota nota">
📏 Calibra
</button>
</div>
<div class="toolbar-group">
<button type="button" id="toolPolygonBtn" class="btn btn-sm btn-outline-success" title="Clicca i punti del contorno esterno">
✏️ Profilo
</button>
<button type="button" id="toolHoleBtn" class="btn btn-outline-danger">
4. Area da escludere
<button type="button" id="toolBrushBtn" class="btn btn-sm btn-outline-success" title="Trascina lungo il bordo per tracciare">
🖌️ Pennello
</button>
<button type="button" id="closeHoleBtn" class="btn btn-outline-danger">
Chiudi esclusione
<button type="button" id="edgeSnapBtn" class="btn btn-sm btn-outline-warning" title="Aggancia i punti al bordo scuro più vicino">
🧲 OFF
</button>
<button type="button" id="toolEditBtn" class="btn btn-outline-secondary">
Modifica punti
<button type="button" id="toolHoleBtn" class="btn btn-sm btn-outline-danger" title="Traccia un foro/cavità da sottrarre">
Esclusione
</button>
<button type="button" id="undoPointBtn" class="btn btn-outline-secondary">
↩️ Annulla punto
<button type="button" id="closeHoleBtn" class="btn btn-sm btn-outline-danger" title="Chiudi l'esclusione corrente">
Chiudi
</button>
</div>
<div class="toolbar-group">
<button type="button" id="toolEditBtn" class="btn btn-sm btn-outline-secondary" title="Sposta, elimina o inserisci punti">
Modifica
</button>
<button type="button" id="deleteSelectedPointBtn" class="btn btn-outline-danger">
Elimina punto
<button type="button" id="undoPointBtn" class="btn btn-sm btn-outline-secondary" title="Rimuovi l'ultimo punto inserito">
↩️ Annulla
</button>
<button type="button" id="resetManualBtn" class="btn btn-outline-warning">
<button type="button" id="deleteSelectedPointBtn" class="btn btn-sm btn-outline-danger" title="Elimina il punto selezionato">
Elimina
</button>
<button type="button" id="resetManualBtn" class="btn btn-sm btn-outline-warning" title="Cancella tutto e ricomincia">
🔄 Reset
</button>
</div>
<button type="button" id="zoomRoiBtn" class="btn btn-outline-info">
🔍 Zoom su ROI
</button>
<button type="button" id="toolAutoContourRoiBtn" class="btn btn-outline-info">
🎯 ROI autocontorno
</button>
<button type="button" id="autoContourBtn" class="btn btn-outline-primary">
🤖 Proponi contorno
</button>
<button type="button" id="fullPageBtn" class="btn btn-outline-dark" disabled>
↩️ Pagina intera
</button>
<div class="toolbar-group">
<span id="manualViewBadge" class="manual-view-badge">
Vista: pagina intera
</span>
<button type="button" id="calculateManualBtn" class="btn btn-add">
<button type="button" id="calculateManualBtn" class="btn btn-sm btn-add">
🧮 Calcola area
</button>
</div>
</div>
<div class="manual-result-box mb-3" id="manualStatusBox">
Modalità attiva: ROI. Disegna un rettangolo attorno alla sezione.
@@ -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({