fixed cad area and added dfx
This commit is contained in:
+217
-93
@@ -160,7 +160,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
#cadAreaTable th:nth-child(2),
|
||||
#cadAreaTable td:nth-child(2) {
|
||||
width: 260px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#cadAreaTable th:nth-child(3),
|
||||
@@ -171,20 +171,13 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
#cadAreaTable th:nth-child(4),
|
||||
#cadAreaTable td:nth-child(4),
|
||||
#cadAreaTable th:nth-child(5),
|
||||
#cadAreaTable td:nth-child(5),
|
||||
#cadAreaTable th:nth-child(6),
|
||||
#cadAreaTable td:nth-child(6) {
|
||||
#cadAreaTable td:nth-child(5) {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
#cadAreaTable th:nth-child(7),
|
||||
#cadAreaTable td:nth-child(7) {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
#cadAreaTable th:nth-child(8),
|
||||
#cadAreaTable td:nth-child(8) {
|
||||
width: 360px;
|
||||
#cadAreaTable th:nth-child(6),
|
||||
#cadAreaTable td:nth-child(6) {
|
||||
width: 190px;
|
||||
}
|
||||
|
||||
.processing-overlay {
|
||||
@@ -353,10 +346,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
<th>Stato</th>
|
||||
<th>Area mm²</th>
|
||||
<th>Area cm²</th>
|
||||
<th>Metodo</th>
|
||||
<th>Confidenza</th>
|
||||
<th>Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@@ -376,9 +366,8 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$areaMm2 = $job['area_mm2'] !== null ? number_format((float)$job['area_mm2'], 3, ',', '.') : '-';
|
||||
$areaCm2 = $job['area_cm2'] !== null ? number_format((float)$job['area_cm2'], 4, ',', '.') : '-';
|
||||
$method = $job['strategy_used'] ?: '-';
|
||||
$confidence = $job['confidence'] ?: '-';
|
||||
$fileUrl = htmlspecialchars($job['file_url'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||
$dxfUrl = htmlspecialchars($job['dxf_url'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||
?>
|
||||
<tr data-id="<?= (int)$job['id']; ?>">
|
||||
<td><?= (int)$job['id']; ?></td>
|
||||
@@ -390,24 +379,29 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
<td><?= $badge; ?></td>
|
||||
<td class="fw-semibold"><?= $areaMm2; ?></td>
|
||||
<td class="fw-semibold"><?= $areaCm2; ?></td>
|
||||
<td><?= htmlspecialchars((string)$method, ENT_QUOTES, 'UTF-8'); ?></td>
|
||||
<td><?= htmlspecialchars((string)$confidence, ENT_QUOTES, 'UTF-8'); ?></td>
|
||||
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<?php if (!empty($job['file_url'])): ?>
|
||||
<a href="<?= $fileUrl; ?>" target="_blank" class="btn btn-sm btn-outline-dark">
|
||||
📄 Apri
|
||||
<a href="<?= $fileUrl; ?>" target="_blank" class="btn btn-sm btn-outline-dark" title="Apri PDF">
|
||||
📄
|
||||
</a>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-outline-success manual-trace"
|
||||
data-id="<?= (int)$job['id']; ?>"
|
||||
data-url="<?= $fileUrl; ?>">
|
||||
✏️ Traccia Manuale
|
||||
data-url="<?= $fileUrl; ?>"
|
||||
title="Traccia Manuale">
|
||||
✏️
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($job['dxf_url'])): ?>
|
||||
<a href="<?= $dxfUrl; ?>" download class="btn btn-sm btn-outline-primary">
|
||||
📐 DXF
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<button class="btn btn-sm btn-outline-danger delete-one" data-id="<?= (int)$job['id']; ?>">
|
||||
🗑️
|
||||
</button>
|
||||
@@ -540,6 +534,10 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
Chiudi
|
||||
</button>
|
||||
|
||||
<button type="button" id="exportDxfBtn" class="btn btn-outline-primary" disabled>
|
||||
📐 Esporta DXF
|
||||
</button>
|
||||
|
||||
<button type="button" id="saveManualAreaBtn" class="btn btn-add" disabled>
|
||||
💾 Salva area
|
||||
</button>
|
||||
@@ -607,9 +605,15 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
let edgeSnapEnabled = false;
|
||||
let edgeSnapPreviewPoint = null;
|
||||
|
||||
const EDGE_SNAP_RADIUS = 45;
|
||||
const EDGE_DARK_THRESHOLD = 245;
|
||||
const EDGE_GRADIENT_THRESHOLD = 8;
|
||||
const EDGE_SNAP_RADIUS = 50;
|
||||
const EDGE_DARK_THRESHOLD = 200;
|
||||
|
||||
let edgeGray = null;
|
||||
let edgeGrayW = 0;
|
||||
let edgeGrayH = 0;
|
||||
let snapRafPending = false;
|
||||
let lastSnapMousePos = null;
|
||||
let edgeCacheDirty = true;
|
||||
|
||||
let lastManualResult = null;
|
||||
|
||||
@@ -863,6 +867,8 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
manualCurrentView = 'full';
|
||||
manualRoiPageRect = null;
|
||||
|
||||
edgeCacheDirty = true;
|
||||
|
||||
const viewport = manualPdfPage.getViewport({
|
||||
scale: manualBaseScale
|
||||
});
|
||||
@@ -880,6 +886,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
canvasContext: manualPdfCtx,
|
||||
viewport: viewport
|
||||
}).promise.then(() => {
|
||||
rebuildEdgeCache();
|
||||
updateManualViewBadge();
|
||||
redrawManualOverlay();
|
||||
});
|
||||
@@ -953,6 +960,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
manualCurrentView = 'roi';
|
||||
edgeCacheDirty = true;
|
||||
|
||||
const scale = manualBaseScale * MANUAL_ROI_ZOOM_FACTOR;
|
||||
const viewport = manualPdfPage.getViewport({
|
||||
@@ -983,6 +991,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
viewport: viewport
|
||||
}).promise.then(() => {
|
||||
manualPdfCtx.restore();
|
||||
rebuildEdgeCache();
|
||||
updateManualViewBadge();
|
||||
redrawManualOverlay();
|
||||
}).catch(error => {
|
||||
@@ -1067,6 +1076,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
document.getElementById('saveManualAreaBtn').disabled = true;
|
||||
document.getElementById('manualCoordsPreview').innerText = 'Nessun dato calcolato.';
|
||||
document.getElementById('exportDxfBtn').disabled = true;
|
||||
|
||||
edgeSnapEnabled = false;
|
||||
|
||||
@@ -1171,8 +1181,18 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
return;
|
||||
}
|
||||
if (edgeSnapEnabled && ['polygon', 'hole', 'edit'].includes(currentTool)) {
|
||||
edgeSnapPreviewPoint = snapToNearestEdge(pos, EDGE_SNAP_RADIUS);
|
||||
redrawManualOverlay();
|
||||
lastSnapMousePos = pos;
|
||||
|
||||
if (!snapRafPending) {
|
||||
snapRafPending = true;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
snapRafPending = false;
|
||||
edgeSnapPreviewPoint = snapToNearestEdge(lastSnapMousePos, EDGE_SNAP_RADIUS);
|
||||
redrawManualOverlay();
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1318,96 +1338,119 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
return pos;
|
||||
}
|
||||
|
||||
function rebuildEdgeCache() {
|
||||
edgeGray = null;
|
||||
|
||||
if (!manualPdfCanvas || !manualPdfCtx) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const w = manualPdfCanvas.width;
|
||||
const h = manualPdfCanvas.height;
|
||||
const d = manualPdfCtx.getImageData(0, 0, w, h).data;
|
||||
|
||||
edgeGray = new Uint8Array(w * h);
|
||||
|
||||
for (let i = 0, p = 0; i < edgeGray.length; i++, p += 4) {
|
||||
edgeGray[i] = (d[p] + d[p + 1] + d[p + 2]) / 3;
|
||||
}
|
||||
|
||||
edgeGrayW = w;
|
||||
edgeGrayH = h;
|
||||
edgeCacheDirty = false;
|
||||
|
||||
console.log('Edge cache OK:', w + 'x' + h);
|
||||
} catch (e) {
|
||||
console.warn('Edge cache failed:', e);
|
||||
edgeGray = null;
|
||||
}
|
||||
}
|
||||
|
||||
function snapToNearestEdge(pos, radius) {
|
||||
const canvasWidth = manualPdfCanvas.width;
|
||||
const canvasHeight = manualPdfCanvas.height;
|
||||
if (edgeCacheDirty || !edgeGray || edgeGrayW !== manualPdfCanvas.width || edgeGrayH !== manualPdfCanvas.height) {
|
||||
rebuildEdgeCache();
|
||||
}
|
||||
|
||||
if (!edgeGray) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const w = edgeGrayW;
|
||||
const h = edgeGrayH;
|
||||
|
||||
const cx = Math.round(pos.x);
|
||||
const cy = Math.round(pos.y);
|
||||
|
||||
const x0 = Math.max(1, cx - radius);
|
||||
const y0 = Math.max(1, cy - radius);
|
||||
const x1 = Math.min(canvasWidth - 2, cx + radius);
|
||||
const y1 = Math.min(canvasHeight - 2, cy + radius);
|
||||
|
||||
const w = x1 - x0 + 1;
|
||||
const h = y1 - y0 + 1;
|
||||
|
||||
if (w <= 3 || h <= 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let imageData;
|
||||
|
||||
try {
|
||||
imageData = manualPdfCtx.getImageData(x0, y0, w, h);
|
||||
} catch (e) {
|
||||
console.warn('Edge snap getImageData failed:', e);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = imageData.data;
|
||||
|
||||
function grayAt(localX, localY) {
|
||||
const index = (localY * w + localX) * 4;
|
||||
const r = data[index];
|
||||
const g = data[index + 1];
|
||||
const b = data[index + 2];
|
||||
|
||||
return (r + g + b) / 3;
|
||||
}
|
||||
const x0 = Math.max(0, cx - radius);
|
||||
const y0 = Math.max(0, cy - radius);
|
||||
const x1 = Math.min(w - 1, cx + radius);
|
||||
const y1 = Math.min(h - 1, cy + radius);
|
||||
|
||||
let best = null;
|
||||
let bestScore = -Infinity;
|
||||
let bestScore = Infinity;
|
||||
const r2 = radius * radius;
|
||||
|
||||
for (let yy = 1; yy < h - 1; yy++) {
|
||||
for (let xx = 1; xx < w - 1; xx++) {
|
||||
const globalX = x0 + xx;
|
||||
const globalY = y0 + yy;
|
||||
for (let y = y0; y <= y1; y++) {
|
||||
const rowOffset = y * w;
|
||||
|
||||
const dx = globalX - pos.x;
|
||||
const dy = globalY - pos.y;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
for (let x = x0; x <= x1; x++) {
|
||||
const v = edgeGray[rowOffset + x];
|
||||
|
||||
if (dist > radius) {
|
||||
if (v > EDGE_DARK_THRESHOLD) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const center = grayAt(xx, yy);
|
||||
const dx = x - pos.x;
|
||||
const dy = y - pos.y;
|
||||
const d2 = dx * dx + dy * dy;
|
||||
|
||||
const gx = Math.abs(grayAt(xx + 1, yy) - grayAt(xx - 1, yy));
|
||||
const gy = Math.abs(grayAt(xx, yy + 1) - grayAt(xx, yy - 1));
|
||||
const gradient = gx + gy;
|
||||
|
||||
const isDarkEnough = center < EDGE_DARK_THRESHOLD;
|
||||
const isRealEdge = gradient > EDGE_GRADIENT_THRESHOLD;
|
||||
|
||||
if (!isDarkEnough && !isRealEdge) {
|
||||
if (d2 > r2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prefer:
|
||||
* - dark strokes
|
||||
* - real contrast edges
|
||||
* - points close to the cursor
|
||||
*/
|
||||
const darknessScore = Math.max(0, 255 - center);
|
||||
const gradientScore = gradient * 3;
|
||||
const distancePenalty = dist * 4;
|
||||
const score = Math.sqrt(d2) - (EDGE_DARK_THRESHOLD - v) * 0.03;
|
||||
|
||||
const score = darknessScore + gradientScore - distancePenalty;
|
||||
|
||||
if (score > bestScore) {
|
||||
if (score < bestScore) {
|
||||
bestScore = score;
|
||||
best = {
|
||||
x: globalX,
|
||||
y: globalY
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!best) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let sumX = 0;
|
||||
let sumY = 0;
|
||||
let sumW = 0;
|
||||
|
||||
for (let y = Math.max(0, best.y - 2); y <= Math.min(h - 1, best.y + 2); y++) {
|
||||
for (let x = Math.max(0, best.x - 2); x <= Math.min(w - 1, best.x + 2); x++) {
|
||||
const v = edgeGray[y * w + x];
|
||||
|
||||
if (v > EDGE_DARK_THRESHOLD) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const weight = EDGE_DARK_THRESHOLD - v;
|
||||
sumX += x * weight;
|
||||
sumY += y * weight;
|
||||
sumW += weight;
|
||||
}
|
||||
}
|
||||
|
||||
if (sumW > 0) {
|
||||
return {
|
||||
x: sumX / sumW,
|
||||
y: sumY / sumW
|
||||
};
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
@@ -1756,6 +1799,12 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
if (saveBtn) {
|
||||
saveBtn.disabled = true;
|
||||
}
|
||||
|
||||
const dxfBtn = document.getElementById('exportDxfBtn');
|
||||
|
||||
if (dxfBtn) {
|
||||
dxfBtn.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
function isSelectedPoint(type, holeIndex, pointIndex) {
|
||||
@@ -2315,6 +2364,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
};
|
||||
|
||||
document.getElementById('saveManualAreaBtn').disabled = false;
|
||||
document.getElementById('exportDxfBtn').disabled = false;
|
||||
|
||||
setManualStatus(
|
||||
`Area finale: ${finalAreaMm2.toFixed(3)} mm² = ${finalAreaCm2.toFixed(4)} cm². ` +
|
||||
@@ -2335,8 +2385,17 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
return;
|
||||
}
|
||||
|
||||
let dxfContent = '';
|
||||
|
||||
try {
|
||||
dxfContent = buildDxfString();
|
||||
} catch (e) {
|
||||
console.warn('DXF build failed:', e);
|
||||
}
|
||||
|
||||
const payload = {
|
||||
id: currentManualJobId,
|
||||
dxf_content: dxfContent,
|
||||
|
||||
area_mm2: +lastManualResult.area_mm2.toFixed(6),
|
||||
area_cm2: +lastManualResult.area_cm2.toFixed(6),
|
||||
@@ -2417,6 +2476,69 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
});
|
||||
}
|
||||
|
||||
function toMmPoint(p) {
|
||||
return {
|
||||
x: p.x * mmPerPx,
|
||||
y: (manualOverlayCanvas.height - p.y) * mmPerPx
|
||||
};
|
||||
}
|
||||
|
||||
function dxfPolyline(points, layer) {
|
||||
let s = '0\nPOLYLINE\n8\n' + layer + '\n66\n1\n70\n1\n';
|
||||
|
||||
points.forEach(p => {
|
||||
const m = toMmPoint(p);
|
||||
s += '0\nVERTEX\n8\n' + layer +
|
||||
'\n10\n' + m.x.toFixed(4) +
|
||||
'\n20\n' + m.y.toFixed(4) +
|
||||
'\n30\n0.0\n';
|
||||
});
|
||||
|
||||
return s + '0\nSEQEND\n';
|
||||
}
|
||||
|
||||
function buildDxfString() {
|
||||
let dxf = '0\nSECTION\n2\nHEADER\n9\n$INSUNITS\n70\n4\n0\nENDSEC\n';
|
||||
dxf += '0\nSECTION\n2\nENTITIES\n';
|
||||
dxf += dxfPolyline(polygonPoints, 'PROFILO');
|
||||
|
||||
holes.forEach(hole => {
|
||||
if (hole.length >= 3) {
|
||||
dxf += dxfPolyline(hole, 'ESCLUSIONI');
|
||||
}
|
||||
});
|
||||
|
||||
dxf += '0\nENDSEC\n0\nEOF\n';
|
||||
return dxf;
|
||||
}
|
||||
|
||||
function exportDxf() {
|
||||
if (!lastManualResult || !mmPerPx || polygonPoints.length < 3) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Prima calcola l\u2019area',
|
||||
text: 'Il DXF viene generato dal profilo calibrato.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const dxf = buildDxfString();
|
||||
|
||||
const blob = new Blob([dxf], {
|
||||
type: 'application/dxf'
|
||||
});
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = 'profilo_job_' + currentManualJobId + '.dxf';
|
||||
a.click();
|
||||
URL.revokeObjectURL(a.href);
|
||||
}
|
||||
|
||||
document.getElementById('exportDxfBtn').addEventListener('click', function() {
|
||||
exportDxf();
|
||||
});
|
||||
|
||||
function polygonAreaPx2(points) {
|
||||
let area = 0;
|
||||
|
||||
@@ -2634,6 +2756,8 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
btn.classList.add('btn-warning', 'active');
|
||||
btn.innerText = '🧲 Magnete bordo ON';
|
||||
|
||||
rebuildEdgeCache();
|
||||
|
||||
setManualStatus(
|
||||
'Magnete bordo attivo: quando disegni o sposti punti, il sistema prova ad agganciarli al bordo scuro più vicino.'
|
||||
);
|
||||
|
||||
@@ -50,6 +50,7 @@ try {
|
||||
$outerPolygon = $input['manual_polygon'] ?? null;
|
||||
$holes = $input['manual_holes'] ?? [];
|
||||
$roi = $input['roi'] ?? null;
|
||||
$dxfContent = isset($input['dxf_content']) ? (string)$input['dxf_content'] : '';
|
||||
|
||||
if ($areaMm2 <= 0) {
|
||||
jsonResponse([
|
||||
@@ -106,6 +107,43 @@ try {
|
||||
|
||||
$manualHolesJson = json_encode($holes);
|
||||
|
||||
$dxfFilename = null;
|
||||
$dxfUrl = null;
|
||||
|
||||
if ($dxfContent !== '') {
|
||||
|
||||
if (strlen($dxfContent) > 5 * 1024 * 1024) {
|
||||
jsonResponse([
|
||||
'success' => false,
|
||||
'message' => 'DXF troppo grande.'
|
||||
]);
|
||||
}
|
||||
|
||||
if (strpos($dxfContent, 'SECTION') === false || strpos($dxfContent, 'EOF') === false) {
|
||||
jsonResponse([
|
||||
'success' => false,
|
||||
'message' => 'Contenuto DXF non valido.'
|
||||
]);
|
||||
}
|
||||
|
||||
$dxfDir = __DIR__ . '/uploads/cad_area/dxf/';
|
||||
|
||||
if (!is_dir($dxfDir)) {
|
||||
mkdir($dxfDir, 0755, true);
|
||||
}
|
||||
|
||||
$dxfFilename = 'profilo_' . $id . '_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.dxf';
|
||||
|
||||
if (file_put_contents($dxfDir . $dxfFilename, $dxfContent) === false) {
|
||||
jsonResponse([
|
||||
'success' => false,
|
||||
'message' => 'Impossibile salvare il file DXF sul server.'
|
||||
]);
|
||||
}
|
||||
|
||||
$dxfUrl = 'uploads/cad_area/dxf/' . $dxfFilename;
|
||||
}
|
||||
|
||||
$roiX = null;
|
||||
$roiY = null;
|
||||
$roiW = null;
|
||||
@@ -152,6 +190,9 @@ try {
|
||||
manual_holes_json = ?,
|
||||
manual_status = 'completed',
|
||||
|
||||
dxf_filename = COALESCE(?, dxf_filename),
|
||||
dxf_url = COALESCE(?, dxf_url),
|
||||
|
||||
scale_used = ?,
|
||||
scale_detected = ?,
|
||||
confidence = 'manual_validated',
|
||||
@@ -188,6 +229,9 @@ try {
|
||||
$manualPolygonJson,
|
||||
$manualHolesJson,
|
||||
|
||||
$dxfFilename,
|
||||
$dxfUrl,
|
||||
|
||||
$mmPerPx,
|
||||
'manual',
|
||||
|
||||
@@ -227,6 +271,9 @@ try {
|
||||
manual_holes_json = ?,
|
||||
manual_status = 'completed',
|
||||
|
||||
dxf_filename = COALESCE(?, dxf_filename),
|
||||
dxf_url = COALESCE(?, dxf_url),
|
||||
|
||||
scale_used = ?,
|
||||
scale_detected = ?,
|
||||
confidence = 'manual_validated',
|
||||
@@ -264,6 +311,9 @@ try {
|
||||
$manualPolygonJson,
|
||||
$manualHolesJson,
|
||||
|
||||
$dxfFilename,
|
||||
$dxfUrl,
|
||||
|
||||
$mmPerPx,
|
||||
'manual',
|
||||
|
||||
@@ -285,7 +335,8 @@ try {
|
||||
'area_mm2' => $areaMm2,
|
||||
'area_cm2' => $areaCm2,
|
||||
'outer_area_mm2' => $outerAreaMm2,
|
||||
'holes_area_mm2' => $holesAreaMm2
|
||||
'holes_area_mm2' => $holesAreaMm2,
|
||||
'dxf_url' => $dxfUrl
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
error_log('CAD manual area save error: ' . $e->getMessage());
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,158 @@
|
||||
0
|
||||
SECTION
|
||||
2
|
||||
HEADER
|
||||
9
|
||||
$INSUNITS
|
||||
70
|
||||
4
|
||||
0
|
||||
ENDSEC
|
||||
0
|
||||
SECTION
|
||||
2
|
||||
ENTITIES
|
||||
0
|
||||
POLYLINE
|
||||
8
|
||||
PROFILO
|
||||
66
|
||||
1
|
||||
70
|
||||
1
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
PROFILO
|
||||
10
|
||||
5.9087
|
||||
20
|
||||
9.0157
|
||||
30
|
||||
0.0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
PROFILO
|
||||
10
|
||||
6.0608
|
||||
20
|
||||
2.8408
|
||||
30
|
||||
0.0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
PROFILO
|
||||
10
|
||||
11.0190
|
||||
20
|
||||
2.8256
|
||||
30
|
||||
0.0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
PROFILO
|
||||
10
|
||||
16.7528
|
||||
20
|
||||
3.2362
|
||||
30
|
||||
0.0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
PROFILO
|
||||
10
|
||||
17.9239
|
||||
20
|
||||
4.4834
|
||||
30
|
||||
0.0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
PROFILO
|
||||
10
|
||||
17.7262
|
||||
20
|
||||
6.6887
|
||||
30
|
||||
0.0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
PROFILO
|
||||
10
|
||||
17.1939
|
||||
20
|
||||
8.1336
|
||||
30
|
||||
0.0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
PROFILO
|
||||
10
|
||||
16.0076
|
||||
20
|
||||
10.2476
|
||||
30
|
||||
0.0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
PROFILO
|
||||
10
|
||||
15.3688
|
||||
20
|
||||
10.9320
|
||||
30
|
||||
0.0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
PROFILO
|
||||
10
|
||||
13.4373
|
||||
20
|
||||
11.0233
|
||||
30
|
||||
0.0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
PROFILO
|
||||
10
|
||||
9.0722
|
||||
20
|
||||
11.4339
|
||||
30
|
||||
0.0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
PROFILO
|
||||
10
|
||||
8.1597
|
||||
20
|
||||
11.2210
|
||||
30
|
||||
0.0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
PROFILO
|
||||
10
|
||||
7.3992
|
||||
20
|
||||
10.7343
|
||||
30
|
||||
0.0
|
||||
0
|
||||
SEQEND
|
||||
0
|
||||
ENDSEC
|
||||
0
|
||||
EOF
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user