cad area punti mpodifica manuale
This commit is contained in:
+490
-24
@@ -280,6 +280,15 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.manual-edit-help {
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px dashed #cbd5e1;
|
||||||
|
color: #475569;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -467,10 +476,18 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
✅ Chiudi esclusione
|
✅ Chiudi esclusione
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button type="button" id="toolEditBtn" class="btn btn-outline-secondary">
|
||||||
|
✋ Modifica punti
|
||||||
|
</button>
|
||||||
|
|
||||||
<button type="button" id="undoPointBtn" class="btn btn-outline-secondary">
|
<button type="button" id="undoPointBtn" class="btn btn-outline-secondary">
|
||||||
↩️ Annulla punto
|
↩️ Annulla punto
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button type="button" id="deleteSelectedPointBtn" class="btn btn-outline-danger">
|
||||||
|
❌ Elimina punto
|
||||||
|
</button>
|
||||||
|
|
||||||
<button type="button" id="resetManualBtn" class="btn btn-outline-warning">
|
<button type="button" id="resetManualBtn" class="btn btn-outline-warning">
|
||||||
🔄 Reset
|
🔄 Reset
|
||||||
</button>
|
</button>
|
||||||
@@ -562,6 +579,14 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
let polygonPoints = [];
|
let polygonPoints = [];
|
||||||
let currentHolePoints = [];
|
let currentHolePoints = [];
|
||||||
let holes = [];
|
let holes = [];
|
||||||
|
|
||||||
|
let selectedEditPoint = null;
|
||||||
|
let isDraggingEditPoint = false;
|
||||||
|
let editDragMoved = false;
|
||||||
|
|
||||||
|
const POINT_HIT_RADIUS = 12;
|
||||||
|
const SEGMENT_HIT_RADIUS = 12;
|
||||||
|
|
||||||
let lastManualResult = null;
|
let lastManualResult = null;
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
@@ -957,6 +982,10 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
currentHolePoints = [];
|
currentHolePoints = [];
|
||||||
holes = [];
|
holes = [];
|
||||||
|
|
||||||
|
selectedEditPoint = null;
|
||||||
|
isDraggingEditPoint = false;
|
||||||
|
editDragMoved = false;
|
||||||
|
|
||||||
lastManualResult = null;
|
lastManualResult = null;
|
||||||
|
|
||||||
document.getElementById('saveManualAreaBtn').disabled = true;
|
document.getElementById('saveManualAreaBtn').disabled = true;
|
||||||
@@ -1023,24 +1052,52 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
w: 0,
|
w: 0,
|
||||||
h: 0
|
h: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTool === 'edit') {
|
||||||
|
selectedEditPoint = findNearestEditablePoint(pos, POINT_HIT_RADIUS);
|
||||||
|
|
||||||
|
if (selectedEditPoint) {
|
||||||
|
isDraggingEditPoint = true;
|
||||||
|
editDragMoved = false;
|
||||||
|
updateSelectedPointPosition(pos);
|
||||||
|
markManualResultDirty();
|
||||||
|
redrawManualOverlay();
|
||||||
|
updateManualPreview();
|
||||||
|
} else {
|
||||||
|
redrawManualOverlay();
|
||||||
|
setManualStatus('Modifica punti: clicca un punto per selezionarlo, trascinalo per spostarlo, doppio click su un segmento per inserire un nuovo punto.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
manualOverlayCanvas.onmousemove = function(e) {
|
manualOverlayCanvas.onmousemove = function(e) {
|
||||||
if (currentTool !== 'roi' || !isDrawingRoi) {
|
const pos = getCanvasMousePosition(e, manualOverlayCanvas);
|
||||||
|
|
||||||
|
if (currentTool === 'roi' && isDrawingRoi) {
|
||||||
|
roiRect = {
|
||||||
|
x: Math.min(roiStartX, pos.x),
|
||||||
|
y: Math.min(roiStartY, pos.y),
|
||||||
|
w: Math.abs(pos.x - roiStartX),
|
||||||
|
h: Math.abs(pos.y - roiStartY)
|
||||||
|
};
|
||||||
|
|
||||||
|
redrawManualOverlay();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pos = getCanvasMousePosition(e, manualOverlayCanvas);
|
if (currentTool === 'edit' && isDraggingEditPoint && selectedEditPoint) {
|
||||||
|
updateSelectedPointPosition(pos);
|
||||||
roiRect = {
|
editDragMoved = true;
|
||||||
x: Math.min(roiStartX, pos.x),
|
markManualResultDirty();
|
||||||
y: Math.min(roiStartY, pos.y),
|
redrawManualOverlay();
|
||||||
w: Math.abs(pos.x - roiStartX),
|
updateManualPreview();
|
||||||
h: Math.abs(pos.y - roiStartY)
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
redrawManualOverlay();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
manualOverlayCanvas.onmouseup = function() {
|
manualOverlayCanvas.onmouseup = function() {
|
||||||
@@ -1055,14 +1112,48 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
updateManualPreview();
|
updateManualPreview();
|
||||||
|
|
||||||
if (roiRect) {
|
if (roiRect) {
|
||||||
setManualStatus('ROI definita. Ora clicca “Calibra quota” e seleziona due punti su una quota nota.');
|
setManualStatus('ROI definita. Ora puoi fare “Zoom su ROI” oppure calibrare una quota nota.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTool === 'edit' && isDraggingEditPoint) {
|
||||||
|
isDraggingEditPoint = false;
|
||||||
|
|
||||||
|
if (selectedEditPoint) {
|
||||||
|
const label = getSelectedPointLabel(selectedEditPoint);
|
||||||
|
|
||||||
|
if (editDragMoved) {
|
||||||
|
setManualStatus(`Punto spostato (${label}). Puoi calcolare di nuovo oppure continuare a modificare.`);
|
||||||
|
} else {
|
||||||
|
setManualStatus(`Punto selezionato (${label}). Trascinalo per spostarlo, usa “Elimina punto”, oppure doppio click su un segmento per inserire un punto.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editDragMoved = false;
|
||||||
|
redrawManualOverlay();
|
||||||
|
updateManualPreview();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
manualOverlayCanvas.onclick = function(e) {
|
manualOverlayCanvas.onclick = function(e) {
|
||||||
const pos = getCanvasMousePosition(e, manualOverlayCanvas);
|
const pos = getCanvasMousePosition(e, manualOverlayCanvas);
|
||||||
|
|
||||||
|
if (currentTool === 'edit') {
|
||||||
|
selectedEditPoint = findNearestEditablePoint(pos, POINT_HIT_RADIUS);
|
||||||
|
redrawManualOverlay();
|
||||||
|
|
||||||
|
if (selectedEditPoint) {
|
||||||
|
setManualStatus(`Punto selezionato (${getSelectedPointLabel(selectedEditPoint)}). Trascina per spostarlo o clicca “Elimina punto”.`);
|
||||||
|
} else {
|
||||||
|
setManualStatus('Nessun punto selezionato. Clicca vicino a un punto oppure doppio click su un segmento per aggiungerne uno.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentTool === 'calibration') {
|
if (currentTool === 'calibration') {
|
||||||
handleCalibrationClick(pos);
|
handleCalibrationClick(pos);
|
||||||
return;
|
return;
|
||||||
@@ -1078,6 +1169,30 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
manualOverlayCanvas.ondblclick = function(e) {
|
||||||
|
if (currentTool !== 'edit') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const pos = getCanvasMousePosition(e, manualOverlayCanvas);
|
||||||
|
const inserted = insertPointOnNearestSegment(pos, SEGMENT_HIT_RADIUS);
|
||||||
|
|
||||||
|
if (inserted) {
|
||||||
|
markManualResultDirty();
|
||||||
|
redrawManualOverlay();
|
||||||
|
updateManualPreview();
|
||||||
|
setManualStatus(`Nuovo punto inserito (${getSelectedPointLabel(selectedEditPoint)}). Ora puoi trascinarlo per rifinire il profilo.`);
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'info',
|
||||||
|
title: 'Nessun segmento vicino',
|
||||||
|
text: 'Fai doppio click vicino a una linea del contorno esterno o di una esclusione.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCalibrationClick(pos) {
|
function handleCalibrationClick(pos) {
|
||||||
@@ -1153,6 +1268,12 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
}
|
}
|
||||||
|
|
||||||
polygonPoints.push(pos);
|
polygonPoints.push(pos);
|
||||||
|
selectedEditPoint = {
|
||||||
|
type: 'outer',
|
||||||
|
pointIndex: polygonPoints.length - 1
|
||||||
|
};
|
||||||
|
|
||||||
|
markManualResultDirty();
|
||||||
redrawManualOverlay();
|
redrawManualOverlay();
|
||||||
updateManualPreview();
|
updateManualPreview();
|
||||||
}
|
}
|
||||||
@@ -1177,9 +1298,12 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
}
|
}
|
||||||
|
|
||||||
currentHolePoints.push(pos);
|
currentHolePoints.push(pos);
|
||||||
lastManualResult = null;
|
selectedEditPoint = {
|
||||||
document.getElementById('saveManualAreaBtn').disabled = true;
|
type: 'currentHole',
|
||||||
|
pointIndex: currentHolePoints.length - 1
|
||||||
|
};
|
||||||
|
|
||||||
|
markManualResultDirty();
|
||||||
redrawManualOverlay();
|
redrawManualOverlay();
|
||||||
updateManualPreview();
|
updateManualPreview();
|
||||||
}
|
}
|
||||||
@@ -1196,9 +1320,9 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
holes.push([...currentHolePoints]);
|
holes.push([...currentHolePoints]);
|
||||||
currentHolePoints = [];
|
currentHolePoints = [];
|
||||||
|
selectedEditPoint = null;
|
||||||
|
|
||||||
lastManualResult = null;
|
markManualResultDirty();
|
||||||
document.getElementById('saveManualAreaBtn').disabled = true;
|
|
||||||
|
|
||||||
redrawManualOverlay();
|
redrawManualOverlay();
|
||||||
updateManualPreview();
|
updateManualPreview();
|
||||||
@@ -1276,22 +1400,27 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
manualOverlayCtx.stroke();
|
manualOverlayCtx.stroke();
|
||||||
|
|
||||||
polygonPoints.forEach(point => {
|
polygonPoints.forEach((point, index) => {
|
||||||
drawPoint(point, 'rgba(22, 163, 74, 1)', 4);
|
const selected = isSelectedPoint('outer', null, index);
|
||||||
|
drawPoint(point, selected ? 'rgba(245, 158, 11, 1)' : 'rgba(22, 163, 74, 1)', selected ? 7 : 4);
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
drawPointRing(point, 'rgba(245, 158, 11, 1)', 11);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawHoles() {
|
function drawHoles() {
|
||||||
holes.forEach(hole => {
|
holes.forEach((hole, holeIndex) => {
|
||||||
drawHolePolygon(hole, true);
|
drawHolePolygon(hole, true, holeIndex, 'hole');
|
||||||
});
|
});
|
||||||
|
|
||||||
if (currentHolePoints.length > 0) {
|
if (currentHolePoints.length > 0) {
|
||||||
drawHolePolygon(currentHolePoints, false);
|
drawHolePolygon(currentHolePoints, false, null, 'currentHole');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawHolePolygon(points, closed) {
|
function drawHolePolygon(points, closed, holeIndex = null, type = 'hole') {
|
||||||
if (!points || points.length === 0) {
|
if (!points || points.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1314,8 +1443,13 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
manualOverlayCtx.stroke();
|
manualOverlayCtx.stroke();
|
||||||
|
|
||||||
points.forEach(point => {
|
points.forEach((point, index) => {
|
||||||
drawPoint(point, 'rgba(220, 38, 38, 1)', 4);
|
const selected = isSelectedPoint(type, holeIndex, index);
|
||||||
|
drawPoint(point, selected ? 'rgba(245, 158, 11, 1)' : 'rgba(220, 38, 38, 1)', selected ? 7 : 4);
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
drawPointRing(point, 'rgba(245, 158, 11, 1)', 11);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1326,6 +1460,296 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
manualOverlayCtx.fill();
|
manualOverlayCtx.fill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawPointRing(point, color, radius) {
|
||||||
|
manualOverlayCtx.beginPath();
|
||||||
|
manualOverlayCtx.arc(point.x, point.y, radius, 0, Math.PI * 2);
|
||||||
|
manualOverlayCtx.strokeStyle = color;
|
||||||
|
manualOverlayCtx.lineWidth = 2;
|
||||||
|
manualOverlayCtx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
function markManualResultDirty() {
|
||||||
|
lastManualResult = null;
|
||||||
|
|
||||||
|
const saveBtn = document.getElementById('saveManualAreaBtn');
|
||||||
|
|
||||||
|
if (saveBtn) {
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelectedPoint(type, holeIndex, pointIndex) {
|
||||||
|
if (!selectedEditPoint) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
selectedEditPoint.type === type &&
|
||||||
|
selectedEditPoint.pointIndex === pointIndex &&
|
||||||
|
(
|
||||||
|
type !== 'hole' ||
|
||||||
|
selectedEditPoint.holeIndex === holeIndex
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedPointLabel(selection) {
|
||||||
|
if (!selection) {
|
||||||
|
return 'nessun punto';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.type === 'outer') {
|
||||||
|
return `contorno esterno, punto ${selection.pointIndex + 1}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.type === 'hole') {
|
||||||
|
return `esclusione ${selection.holeIndex + 1}, punto ${selection.pointIndex + 1}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.type === 'currentHole') {
|
||||||
|
return `esclusione attiva, punto ${selection.pointIndex + 1}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'punto';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEditablePointList() {
|
||||||
|
const refs = [];
|
||||||
|
|
||||||
|
polygonPoints.forEach((point, pointIndex) => {
|
||||||
|
refs.push({
|
||||||
|
type: 'outer',
|
||||||
|
holeIndex: null,
|
||||||
|
pointIndex: pointIndex,
|
||||||
|
point: point
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
holes.forEach((hole, holeIndex) => {
|
||||||
|
hole.forEach((point, pointIndex) => {
|
||||||
|
refs.push({
|
||||||
|
type: 'hole',
|
||||||
|
holeIndex: holeIndex,
|
||||||
|
pointIndex: pointIndex,
|
||||||
|
point: point
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
currentHolePoints.forEach((point, pointIndex) => {
|
||||||
|
refs.push({
|
||||||
|
type: 'currentHole',
|
||||||
|
holeIndex: null,
|
||||||
|
pointIndex: pointIndex,
|
||||||
|
point: point
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return refs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNearestEditablePoint(pos, radius) {
|
||||||
|
let best = null;
|
||||||
|
let bestDistance = radius;
|
||||||
|
|
||||||
|
getEditablePointList().forEach(ref => {
|
||||||
|
const d = distance(pos, ref.point);
|
||||||
|
|
||||||
|
if (d <= bestDistance) {
|
||||||
|
bestDistance = d;
|
||||||
|
best = {
|
||||||
|
type: ref.type,
|
||||||
|
holeIndex: ref.holeIndex,
|
||||||
|
pointIndex: ref.pointIndex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPointArrayBySelection(selection) {
|
||||||
|
if (!selection) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.type === 'outer') {
|
||||||
|
return polygonPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.type === 'hole') {
|
||||||
|
return holes[selection.holeIndex] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.type === 'currentHole') {
|
||||||
|
return currentHolePoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSelectedPointPosition(pos) {
|
||||||
|
const arr = getPointArrayBySelection(selectedEditPoint);
|
||||||
|
|
||||||
|
if (!arr || !selectedEditPoint) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
arr[selectedEditPoint.pointIndex] = {
|
||||||
|
x: pos.x,
|
||||||
|
y: pos.y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSelectedPoint() {
|
||||||
|
if (!selectedEditPoint) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'info',
|
||||||
|
title: 'Nessun punto selezionato',
|
||||||
|
text: 'Clicca prima un punto in modalità “Modifica punti”.'
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const arr = getPointArrayBySelection(selectedEditPoint);
|
||||||
|
|
||||||
|
if (!arr) {
|
||||||
|
selectedEditPoint = null;
|
||||||
|
redrawManualOverlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedEditPoint.type === 'outer' && arr.length <= 3) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Non posso eliminare',
|
||||||
|
text: 'Il contorno esterno deve avere almeno 3 punti.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedEditPoint.type === 'hole' && arr.length <= 3) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Non posso eliminare',
|
||||||
|
text: 'Una esclusione chiusa deve avere almeno 3 punti. Usa “Annulla punto” in modalità esclusione per rimuovere l’intera esclusione.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
arr.splice(selectedEditPoint.pointIndex, 1);
|
||||||
|
|
||||||
|
selectedEditPoint = null;
|
||||||
|
markManualResultDirty();
|
||||||
|
redrawManualOverlay();
|
||||||
|
updateManualPreview();
|
||||||
|
|
||||||
|
setManualStatus('Punto eliminato. Ricontrolla il profilo e calcola di nuovo l’area.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function pointToSegmentDistance(p, a, b) {
|
||||||
|
const dx = b.x - a.x;
|
||||||
|
const dy = b.y - a.y;
|
||||||
|
|
||||||
|
if (dx === 0 && dy === 0) {
|
||||||
|
return distance(p, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
let t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / (dx * dx + dy * dy);
|
||||||
|
t = Math.max(0, Math.min(1, t));
|
||||||
|
|
||||||
|
const projection = {
|
||||||
|
x: a.x + t * dx,
|
||||||
|
y: a.y + t * dy
|
||||||
|
};
|
||||||
|
|
||||||
|
return distance(p, projection);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNearestSegment(pos, radius) {
|
||||||
|
const candidates = [];
|
||||||
|
|
||||||
|
function addSegments(points, type, holeIndex, closed) {
|
||||||
|
if (!points || points.length < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const limit = closed ? points.length : points.length - 1;
|
||||||
|
|
||||||
|
for (let i = 0; i < limit; i++) {
|
||||||
|
const nextIndex = (i + 1) % points.length;
|
||||||
|
|
||||||
|
if (!closed && nextIndex === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.push({
|
||||||
|
type: type,
|
||||||
|
holeIndex: holeIndex,
|
||||||
|
insertIndex: i + 1,
|
||||||
|
a: points[i],
|
||||||
|
b: points[nextIndex],
|
||||||
|
distance: pointToSegmentDistance(pos, points[i], points[nextIndex])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addSegments(polygonPoints, 'outer', null, polygonPoints.length >= 3);
|
||||||
|
|
||||||
|
holes.forEach((hole, holeIndex) => {
|
||||||
|
addSegments(hole, 'hole', holeIndex, hole.length >= 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
addSegments(currentHolePoints, 'currentHole', null, false);
|
||||||
|
|
||||||
|
let best = null;
|
||||||
|
|
||||||
|
candidates.forEach(candidate => {
|
||||||
|
if (candidate.distance <= radius && (!best || candidate.distance < best.distance)) {
|
||||||
|
best = candidate;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertPointOnNearestSegment(pos, radius) {
|
||||||
|
const segment = findNearestSegment(pos, radius);
|
||||||
|
|
||||||
|
if (!segment) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let arr = null;
|
||||||
|
|
||||||
|
if (segment.type === 'outer') {
|
||||||
|
arr = polygonPoints;
|
||||||
|
} else if (segment.type === 'hole') {
|
||||||
|
arr = holes[segment.holeIndex];
|
||||||
|
} else if (segment.type === 'currentHole') {
|
||||||
|
arr = currentHolePoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!arr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
arr.splice(segment.insertIndex, 0, {
|
||||||
|
x: pos.x,
|
||||||
|
y: pos.y
|
||||||
|
});
|
||||||
|
|
||||||
|
selectedEditPoint = {
|
||||||
|
type: segment.type,
|
||||||
|
holeIndex: segment.holeIndex,
|
||||||
|
pointIndex: segment.insertIndex
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function calculateManualArea() {
|
function calculateManualArea() {
|
||||||
if (!mmPerPx || !calibrationMm || !calibrationPx) {
|
if (!mmPerPx || !calibrationMm || !calibrationPx) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
@@ -1589,6 +2013,10 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
parts.push(`Punti esclusione attiva: ${currentHolePoints.length}`);
|
parts.push(`Punti esclusione attiva: ${currentHolePoints.length}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedEditPoint) {
|
||||||
|
parts.push(`Selezionato: ${getSelectedPointLabel(selectedEditPoint)}`);
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('manualCoordsPreview').innerText =
|
document.getElementById('manualCoordsPreview').innerText =
|
||||||
parts.length ? parts.join(' | ') : 'Nessun dato calcolato.';
|
parts.length ? parts.join(' | ') : 'Nessun dato calcolato.';
|
||||||
}
|
}
|
||||||
@@ -1605,6 +2033,17 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
holeBtn.classList.remove('active');
|
holeBtn.classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const editBtn = document.getElementById('toolEditBtn');
|
||||||
|
if (editBtn) {
|
||||||
|
editBtn.classList.remove('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tool !== 'edit') {
|
||||||
|
selectedEditPoint = null;
|
||||||
|
isDraggingEditPoint = false;
|
||||||
|
editDragMoved = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (tool === 'roi') {
|
if (tool === 'roi') {
|
||||||
document.getElementById('toolRoiBtn').classList.add('active');
|
document.getElementById('toolRoiBtn').classList.add('active');
|
||||||
|
|
||||||
@@ -1629,6 +2068,14 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
document.getElementById('toolHoleBtn').classList.add('active');
|
document.getElementById('toolHoleBtn').classList.add('active');
|
||||||
setManualStatus('Modalità area da escludere: clicca i punti del foro/cavità da sottrarre, poi clicca “Chiudi esclusione”.');
|
setManualStatus('Modalità area da escludere: clicca i punti del foro/cavità da sottrarre, poi clicca “Chiudi esclusione”.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tool === 'edit') {
|
||||||
|
document.getElementById('toolEditBtn').classList.add('active');
|
||||||
|
setManualStatus('Modalità modifica punti: clicca e trascina un punto per spostarlo. Doppio click su un segmento per aggiungere un punto. Usa “Elimina punto” per cancellare quello selezionato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
redrawManualOverlay();
|
||||||
|
updateManualPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setManualStatus(text) {
|
function setManualStatus(text) {
|
||||||
@@ -1720,6 +2167,25 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('toolEditBtn').addEventListener('click', function() {
|
||||||
|
if (polygonPoints.length < 3 && holes.length === 0 && currentHolePoints.length === 0) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'info',
|
||||||
|
title: 'Nessun profilo da modificare',
|
||||||
|
text: 'Disegna prima il contorno esterno oppure una esclusione.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTool('edit');
|
||||||
|
selectedEditPoint = null;
|
||||||
|
redrawManualOverlay();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('deleteSelectedPointBtn').addEventListener('click', function() {
|
||||||
|
deleteSelectedPoint();
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById('toolHoleBtn').addEventListener('click', function() {
|
document.getElementById('toolHoleBtn').addEventListener('click', function() {
|
||||||
if (!mmPerPx) {
|
if (!mmPerPx) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
|
|||||||
Reference in New Issue
Block a user