cad area update con autocontorno
This commit is contained in:
@@ -496,6 +496,14 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
🔍 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>
|
||||
@@ -571,6 +579,11 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
let roiStartY = 0;
|
||||
let roiRect = null;
|
||||
|
||||
let isDrawingAutoContourRoi = false;
|
||||
let autoContourRoiStartX = 0;
|
||||
let autoContourRoiStartY = 0;
|
||||
let autoContourRoiRect = null;
|
||||
|
||||
let calibrationPoints = [];
|
||||
let calibrationMm = null;
|
||||
let calibrationPx = null;
|
||||
@@ -973,6 +986,11 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
roiStartY = 0;
|
||||
roiRect = null;
|
||||
|
||||
isDrawingAutoContourRoi = false;
|
||||
autoContourRoiStartX = 0;
|
||||
autoContourRoiStartY = 0;
|
||||
autoContourRoiRect = null;
|
||||
|
||||
calibrationPoints = [];
|
||||
calibrationMm = null;
|
||||
calibrationPx = null;
|
||||
@@ -1017,6 +1035,11 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
roiStartY = 0;
|
||||
roiRect = null;
|
||||
|
||||
isDrawingAutoContourRoi = false;
|
||||
autoContourRoiStartX = 0;
|
||||
autoContourRoiStartY = 0;
|
||||
autoContourRoiRect = null;
|
||||
|
||||
calibrationPoints = [];
|
||||
calibrationMm = null;
|
||||
calibrationPx = null;
|
||||
@@ -1056,6 +1079,21 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTool === 'auto_roi') {
|
||||
isDrawingAutoContourRoi = true;
|
||||
autoContourRoiStartX = pos.x;
|
||||
autoContourRoiStartY = pos.y;
|
||||
|
||||
autoContourRoiRect = {
|
||||
x: autoContourRoiStartX,
|
||||
y: autoContourRoiStartY,
|
||||
w: 0,
|
||||
h: 0
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTool === 'edit') {
|
||||
selectedEditPoint = findNearestEditablePoint(pos, POINT_HIT_RADIUS);
|
||||
|
||||
@@ -1090,6 +1128,18 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTool === 'auto_roi' && isDrawingAutoContourRoi) {
|
||||
autoContourRoiRect = {
|
||||
x: Math.min(autoContourRoiStartX, pos.x),
|
||||
y: Math.min(autoContourRoiStartY, pos.y),
|
||||
w: Math.abs(pos.x - autoContourRoiStartX),
|
||||
h: Math.abs(pos.y - autoContourRoiStartY)
|
||||
};
|
||||
|
||||
redrawManualOverlay();
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTool === 'edit' && isDraggingEditPoint && selectedEditPoint) {
|
||||
updateSelectedPointPosition(pos);
|
||||
editDragMoved = true;
|
||||
@@ -1118,6 +1168,23 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTool === 'auto_roi' && isDrawingAutoContourRoi) {
|
||||
isDrawingAutoContourRoi = false;
|
||||
|
||||
if (autoContourRoiRect && (autoContourRoiRect.w < 8 || autoContourRoiRect.h < 8)) {
|
||||
autoContourRoiRect = null;
|
||||
}
|
||||
|
||||
redrawManualOverlay();
|
||||
updateManualPreview();
|
||||
|
||||
if (autoContourRoiRect) {
|
||||
setManualStatus('ROI autocontorno definita. Ora clicca “Proponi contorno”: Python analizzerà solo questa zona, senza quote/testi esterni.');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTool === 'edit' && isDraggingEditPoint) {
|
||||
isDraggingEditPoint = false;
|
||||
|
||||
@@ -1338,6 +1405,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
manualOverlayCtx.clearRect(0, 0, manualOverlayCanvas.width, manualOverlayCanvas.height);
|
||||
|
||||
drawRoi();
|
||||
drawAutoContourRoi();
|
||||
drawCalibration();
|
||||
drawPolygon();
|
||||
drawHoles();
|
||||
@@ -1356,6 +1424,41 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
manualOverlayCtx.strokeRect(roiRect.x, roiRect.y, roiRect.w, roiRect.h);
|
||||
}
|
||||
|
||||
function drawAutoContourRoi() {
|
||||
if (!autoContourRoiRect) {
|
||||
return;
|
||||
}
|
||||
|
||||
manualOverlayCtx.fillStyle = 'rgba(168, 85, 247, 0.12)';
|
||||
manualOverlayCtx.strokeStyle = 'rgba(168, 85, 247, 0.98)';
|
||||
manualOverlayCtx.lineWidth = 2;
|
||||
manualOverlayCtx.setLineDash([8, 4]);
|
||||
|
||||
manualOverlayCtx.fillRect(
|
||||
autoContourRoiRect.x,
|
||||
autoContourRoiRect.y,
|
||||
autoContourRoiRect.w,
|
||||
autoContourRoiRect.h
|
||||
);
|
||||
|
||||
manualOverlayCtx.strokeRect(
|
||||
autoContourRoiRect.x,
|
||||
autoContourRoiRect.y,
|
||||
autoContourRoiRect.w,
|
||||
autoContourRoiRect.h
|
||||
);
|
||||
|
||||
manualOverlayCtx.setLineDash([]);
|
||||
|
||||
manualOverlayCtx.fillStyle = 'rgba(168, 85, 247, 1)';
|
||||
manualOverlayCtx.font = '13px Arial';
|
||||
manualOverlayCtx.fillText(
|
||||
'ROI autocontorno',
|
||||
autoContourRoiRect.x + 6,
|
||||
Math.max(14, autoContourRoiRect.y - 6)
|
||||
);
|
||||
}
|
||||
|
||||
function drawCalibration() {
|
||||
if (calibrationPoints.length === 0) {
|
||||
return;
|
||||
@@ -1750,6 +1853,215 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function proposeAutoContour() {
|
||||
if (!manualPdfCanvas || !manualOverlayCanvas) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Canvas non pronto',
|
||||
text: 'Apri prima il PDF nel tracciamento manuale.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (manualCurrentView !== 'roi') {
|
||||
Swal.fire({
|
||||
icon: 'info',
|
||||
title: 'Prima fai Zoom su ROI',
|
||||
text: 'Per l’autocontorno devi prima disegnare la ROI principale e cliccare “Zoom su ROI”.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!autoContourRoiRect || autoContourRoiRect.w <= 0 || autoContourRoiRect.h <= 0) {
|
||||
Swal.fire({
|
||||
icon: 'info',
|
||||
title: 'Definisci ROI autocontorno',
|
||||
text: 'Clicca “ROI autocontorno” e disegna un rettangolo stretto solo attorno al profilo, senza quote, testi o frecce.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
sendAutoContourRoiToPython();
|
||||
}
|
||||
|
||||
function sendAutoContourRoiToPython() {
|
||||
if (!mmPerPx) {
|
||||
Swal.fire({
|
||||
icon: 'info',
|
||||
title: 'Prima calibra la scala',
|
||||
text: 'Consiglio: calibra prima una quota, poi proponi il contorno. Il contorno può essere generato anche senza scala, ma il calcolo area richiede calibrazione.'
|
||||
});
|
||||
}
|
||||
|
||||
setManualStatus('Analisi automatica del contorno sulla ROI autocontorno...');
|
||||
showOverlay();
|
||||
|
||||
const cropCanvas = document.createElement('canvas');
|
||||
const cropCtx = cropCanvas.getContext('2d');
|
||||
|
||||
const sx = Math.max(0, Math.floor(autoContourRoiRect.x));
|
||||
const sy = Math.max(0, Math.floor(autoContourRoiRect.y));
|
||||
const sw = Math.min(
|
||||
manualPdfCanvas.width - sx,
|
||||
Math.max(1, Math.ceil(autoContourRoiRect.w))
|
||||
);
|
||||
const sh = Math.min(
|
||||
manualPdfCanvas.height - sy,
|
||||
Math.max(1, Math.ceil(autoContourRoiRect.h))
|
||||
);
|
||||
|
||||
cropCanvas.width = sw;
|
||||
cropCanvas.height = sh;
|
||||
|
||||
cropCtx.drawImage(
|
||||
manualPdfCanvas,
|
||||
sx,
|
||||
sy,
|
||||
sw,
|
||||
sh,
|
||||
0,
|
||||
0,
|
||||
sw,
|
||||
sh
|
||||
);
|
||||
|
||||
cropCanvas.toBlob(function(blob) {
|
||||
if (!blob) {
|
||||
hideOverlay();
|
||||
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Errore immagine',
|
||||
text: 'Impossibile generare il crop della ROI autocontorno.'
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('image', blob, 'auto_contour_roi.png');
|
||||
formData.append('max_points', '90');
|
||||
|
||||
fetch('http://127.0.0.1:5055/auto-contour-image', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(async response => {
|
||||
const text = await response.text();
|
||||
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (e) {
|
||||
console.error('Risposta non JSON da Python auto-contour:', text);
|
||||
|
||||
throw new Error(
|
||||
'Il servizio Python non ha restituito JSON. Prima parte risposta: ' +
|
||||
text.substring(0, 180)
|
||||
);
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
hideOverlay();
|
||||
|
||||
if (!data.success) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Contorno non trovato',
|
||||
text: data.message || 'Python non è riuscito a proporre un contorno affidabile.'
|
||||
});
|
||||
|
||||
setManualStatus('Contorno automatico non trovato. Prova una ROI autocontorno più stretta/pulita oppure procedi manualmente.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(data.outer_polygon) || data.outer_polygon.length < 3) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Contorno non valido',
|
||||
text: 'Il contorno proposto non contiene abbastanza punti.'
|
||||
});
|
||||
|
||||
setManualStatus('Contorno automatico non valido. Procedi con il tracciamento manuale.');
|
||||
return;
|
||||
}
|
||||
|
||||
applyAutoContourProposal(data);
|
||||
})
|
||||
.catch(error => {
|
||||
hideOverlay();
|
||||
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Errore auto-contorno',
|
||||
text: error.message || 'Errore durante la proposta del contorno.'
|
||||
});
|
||||
|
||||
setManualStatus('Errore durante l’auto-contorno. Procedi manualmente.');
|
||||
});
|
||||
}, 'image/png');
|
||||
}
|
||||
|
||||
function applyAutoContourProposal(data) {
|
||||
const previousPolygon = polygonPoints.map(p => ({
|
||||
x: p.x,
|
||||
y: p.y
|
||||
}));
|
||||
const previousSelected = selectedEditPoint ? {
|
||||
...selectedEditPoint
|
||||
} : null;
|
||||
|
||||
const baseRect = autoContourRoiRect || {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: manualOverlayCanvas.width,
|
||||
h: manualOverlayCanvas.height
|
||||
};
|
||||
|
||||
const proposedPolygon = data.outer_polygon.map(point => ({
|
||||
x: baseRect.x + point.x * baseRect.w,
|
||||
y: baseRect.y + point.y * baseRect.h
|
||||
}));
|
||||
|
||||
polygonPoints = proposedPolygon;
|
||||
selectedEditPoint = null;
|
||||
currentHolePoints = [];
|
||||
lastManualResult = null;
|
||||
document.getElementById('saveManualAreaBtn').disabled = true;
|
||||
|
||||
redrawManualOverlay();
|
||||
updateManualPreview();
|
||||
|
||||
const diagnostics = data.diagnostics || {};
|
||||
const pointsCount = polygonPoints.length;
|
||||
const method = diagnostics.method || 'opencv';
|
||||
|
||||
Swal.fire({
|
||||
icon: 'question',
|
||||
title: 'Contorno proposto',
|
||||
html: `Ho trovato un possibile contorno esterno con <strong>${pointsCount}</strong> punti.<br>` +
|
||||
`Metodo: <strong>${escapeHtml(String(method))}</strong><br><br>` +
|
||||
`Vuoi usarlo come profilo esterno e poi modificarlo con “Modifica punti”?`,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Sì, usa questo contorno',
|
||||
cancelButtonText: 'No, annulla'
|
||||
}).then(result => {
|
||||
if (!result.isConfirmed) {
|
||||
polygonPoints = previousPolygon;
|
||||
selectedEditPoint = previousSelected;
|
||||
redrawManualOverlay();
|
||||
updateManualPreview();
|
||||
setManualStatus('Proposta contorno annullata. Puoi tracciare manualmente o riprovare.');
|
||||
return;
|
||||
}
|
||||
|
||||
markManualResultDirty();
|
||||
setTool('edit');
|
||||
setManualStatus('Contorno automatico caricato. Usa “Modifica punti” per spostare/eliminare/inserire punti, poi calcola l’area.');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function calculateManualArea() {
|
||||
if (!mmPerPx || !calibrationMm || !calibrationPx) {
|
||||
Swal.fire({
|
||||
@@ -1997,6 +2309,10 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
parts.push(`ROI: ${Math.round(roiRect.w)}x${Math.round(roiRect.h)} px`);
|
||||
}
|
||||
|
||||
if (autoContourRoiRect) {
|
||||
parts.push(`ROI autocontorno: ${Math.round(autoContourRoiRect.w)}x${Math.round(autoContourRoiRect.h)} px`);
|
||||
}
|
||||
|
||||
if (mmPerPx) {
|
||||
parts.push(`Scala: ${mmPerPx.toFixed(6)} mm/px`);
|
||||
}
|
||||
@@ -2028,6 +2344,11 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
document.getElementById('toolCalibrationBtn').classList.remove('active');
|
||||
document.getElementById('toolPolygonBtn').classList.remove('active');
|
||||
|
||||
const autoRoiBtn = document.getElementById('toolAutoContourRoiBtn');
|
||||
if (autoRoiBtn) {
|
||||
autoRoiBtn.classList.remove('active');
|
||||
}
|
||||
|
||||
const holeBtn = document.getElementById('toolHoleBtn');
|
||||
if (holeBtn) {
|
||||
holeBtn.classList.remove('active');
|
||||
@@ -2064,6 +2385,11 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
setManualStatus('Modalità profilo esterno: clicca i punti del contorno esterno. Il poligono viene chiuso automaticamente.');
|
||||
}
|
||||
|
||||
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.');
|
||||
}
|
||||
|
||||
if (tool === 'hole') {
|
||||
document.getElementById('toolHoleBtn').classList.add('active');
|
||||
setManualStatus('Modalità area da escludere: clicca i punti del foro/cavità da sottrarre, poi clicca “Chiudi esclusione”.');
|
||||
@@ -2234,6 +2560,23 @@ $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({
|
||||
icon: 'warning',
|
||||
|
||||
Reference in New Issue
Block a user