fixed cad area and added dfx

This commit is contained in:
2026-07-06 11:06:33 +02:00
parent 376252e263
commit db6e73bde5
6 changed files with 2679 additions and 94 deletions
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AddDxfToCadAreaJobs extends AbstractMigration
{
public function change(): void
{
$this->table('cad_area_jobs')
->addColumn('dxf_filename', 'string', [
'limit' => 255,
'null' => true,
'default' => null,
'after' => 'manual_holes_json'
])
->addColumn('dxf_url', 'string', [
'limit' => 500,
'null' => true,
'default' => null,
'after' => 'dxf_filename'
])
->update();
}
}
+216 -92
View File
@@ -160,7 +160,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
#cadAreaTable th:nth-child(2), #cadAreaTable th:nth-child(2),
#cadAreaTable td:nth-child(2) { #cadAreaTable td:nth-child(2) {
width: 260px; width: auto;
} }
#cadAreaTable th:nth-child(3), #cadAreaTable th:nth-child(3),
@@ -171,20 +171,13 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
#cadAreaTable th:nth-child(4), #cadAreaTable th:nth-child(4),
#cadAreaTable td:nth-child(4), #cadAreaTable td:nth-child(4),
#cadAreaTable th:nth-child(5), #cadAreaTable th:nth-child(5),
#cadAreaTable td:nth-child(5), #cadAreaTable td:nth-child(5) {
#cadAreaTable th:nth-child(6),
#cadAreaTable td:nth-child(6) {
width: 120px; width: 120px;
} }
#cadAreaTable th:nth-child(7), #cadAreaTable th:nth-child(6),
#cadAreaTable td:nth-child(7) { #cadAreaTable td:nth-child(6) {
width: 160px; width: 190px;
}
#cadAreaTable th:nth-child(8),
#cadAreaTable td:nth-child(8) {
width: 360px;
} }
.processing-overlay { .processing-overlay {
@@ -353,10 +346,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
<th>Stato</th> <th>Stato</th>
<th>Area mm²</th> <th>Area mm²</th>
<th>Area cm²</th> <th>Area cm²</th>
<th>Metodo</th>
<th>Confidenza</th>
<th>Azioni</th> <th>Azioni</th>
</tr>
</thead> </thead>
<tbody> <tbody>
@@ -376,9 +366,8 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
$areaMm2 = $job['area_mm2'] !== null ? number_format((float)$job['area_mm2'], 3, ',', '.') : '-'; $areaMm2 = $job['area_mm2'] !== null ? number_format((float)$job['area_mm2'], 3, ',', '.') : '-';
$areaCm2 = $job['area_cm2'] !== null ? number_format((float)$job['area_cm2'], 4, ',', '.') : '-'; $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'); $fileUrl = htmlspecialchars($job['file_url'] ?? '', ENT_QUOTES, 'UTF-8');
$dxfUrl = htmlspecialchars($job['dxf_url'] ?? '', ENT_QUOTES, 'UTF-8');
?> ?>
<tr data-id="<?= (int)$job['id']; ?>"> <tr data-id="<?= (int)$job['id']; ?>">
<td><?= (int)$job['id']; ?></td> <td><?= (int)$job['id']; ?></td>
@@ -390,24 +379,29 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
<td><?= $badge; ?></td> <td><?= $badge; ?></td>
<td class="fw-semibold"><?= $areaMm2; ?></td> <td class="fw-semibold"><?= $areaMm2; ?></td>
<td class="fw-semibold"><?= $areaCm2; ?></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> <td>
<div class="action-buttons"> <div class="action-buttons">
<?php if (!empty($job['file_url'])): ?> <?php if (!empty($job['file_url'])): ?>
<a href="<?= $fileUrl; ?>" target="_blank" class="btn btn-sm btn-outline-dark"> <a href="<?= $fileUrl; ?>" target="_blank" class="btn btn-sm btn-outline-dark" title="Apri PDF">
📄 Apri 📄
</a> </a>
<button <button
class="btn btn-sm btn-outline-success manual-trace" class="btn btn-sm btn-outline-success manual-trace"
data-id="<?= (int)$job['id']; ?>" data-id="<?= (int)$job['id']; ?>"
data-url="<?= $fileUrl; ?>"> data-url="<?= $fileUrl; ?>"
✏️ Traccia Manuale title="Traccia Manuale">
✏️
</button> </button>
<?php endif; ?> <?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 class="btn btn-sm btn-outline-danger delete-one" data-id="<?= (int)$job['id']; ?>">
🗑️ 🗑️
</button> </button>
@@ -540,6 +534,10 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
Chiudi Chiudi
</button> </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> <button type="button" id="saveManualAreaBtn" class="btn btn-add" disabled>
💾 Salva area 💾 Salva area
</button> </button>
@@ -607,9 +605,15 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
let edgeSnapEnabled = false; let edgeSnapEnabled = false;
let edgeSnapPreviewPoint = null; let edgeSnapPreviewPoint = null;
const EDGE_SNAP_RADIUS = 45; const EDGE_SNAP_RADIUS = 50;
const EDGE_DARK_THRESHOLD = 245; const EDGE_DARK_THRESHOLD = 200;
const EDGE_GRADIENT_THRESHOLD = 8;
let edgeGray = null;
let edgeGrayW = 0;
let edgeGrayH = 0;
let snapRafPending = false;
let lastSnapMousePos = null;
let edgeCacheDirty = true;
let lastManualResult = null; let lastManualResult = null;
@@ -863,6 +867,8 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
manualCurrentView = 'full'; manualCurrentView = 'full';
manualRoiPageRect = null; manualRoiPageRect = null;
edgeCacheDirty = true;
const viewport = manualPdfPage.getViewport({ const viewport = manualPdfPage.getViewport({
scale: manualBaseScale scale: manualBaseScale
}); });
@@ -880,6 +886,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
canvasContext: manualPdfCtx, canvasContext: manualPdfCtx,
viewport: viewport viewport: viewport
}).promise.then(() => { }).promise.then(() => {
rebuildEdgeCache();
updateManualViewBadge(); updateManualViewBadge();
redrawManualOverlay(); redrawManualOverlay();
}); });
@@ -953,6 +960,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
} }
manualCurrentView = 'roi'; manualCurrentView = 'roi';
edgeCacheDirty = true;
const scale = manualBaseScale * MANUAL_ROI_ZOOM_FACTOR; const scale = manualBaseScale * MANUAL_ROI_ZOOM_FACTOR;
const viewport = manualPdfPage.getViewport({ const viewport = manualPdfPage.getViewport({
@@ -983,6 +991,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
viewport: viewport viewport: viewport
}).promise.then(() => { }).promise.then(() => {
manualPdfCtx.restore(); manualPdfCtx.restore();
rebuildEdgeCache();
updateManualViewBadge(); updateManualViewBadge();
redrawManualOverlay(); redrawManualOverlay();
}).catch(error => { }).catch(error => {
@@ -1067,6 +1076,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
document.getElementById('saveManualAreaBtn').disabled = true; document.getElementById('saveManualAreaBtn').disabled = true;
document.getElementById('manualCoordsPreview').innerText = 'Nessun dato calcolato.'; document.getElementById('manualCoordsPreview').innerText = 'Nessun dato calcolato.';
document.getElementById('exportDxfBtn').disabled = true;
edgeSnapEnabled = false; edgeSnapEnabled = false;
@@ -1171,8 +1181,18 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
return; return;
} }
if (edgeSnapEnabled && ['polygon', 'hole', 'edit'].includes(currentTool)) { if (edgeSnapEnabled && ['polygon', 'hole', 'edit'].includes(currentTool)) {
edgeSnapPreviewPoint = snapToNearestEdge(pos, EDGE_SNAP_RADIUS); lastSnapMousePos = pos;
if (!snapRafPending) {
snapRafPending = true;
requestAnimationFrame(() => {
snapRafPending = false;
edgeSnapPreviewPoint = snapToNearestEdge(lastSnapMousePos, EDGE_SNAP_RADIUS);
redrawManualOverlay(); redrawManualOverlay();
});
}
return; return;
} }
@@ -1318,96 +1338,119 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
return pos; 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) { function snapToNearestEdge(pos, radius) {
const canvasWidth = manualPdfCanvas.width; if (edgeCacheDirty || !edgeGray || edgeGrayW !== manualPdfCanvas.width || edgeGrayH !== manualPdfCanvas.height) {
const canvasHeight = manualPdfCanvas.height; rebuildEdgeCache();
}
if (!edgeGray) {
return null;
}
const w = edgeGrayW;
const h = edgeGrayH;
const cx = Math.round(pos.x); const cx = Math.round(pos.x);
const cy = Math.round(pos.y); const cy = Math.round(pos.y);
const x0 = Math.max(1, cx - radius); const x0 = Math.max(0, cx - radius);
const y0 = Math.max(1, cy - radius); const y0 = Math.max(0, cy - radius);
const x1 = Math.min(canvasWidth - 2, cx + radius); const x1 = Math.min(w - 1, cx + radius);
const y1 = Math.min(canvasHeight - 2, cy + radius); const y1 = Math.min(h - 1, 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;
}
let best = null; let best = null;
let bestScore = -Infinity; let bestScore = Infinity;
const r2 = radius * radius;
for (let yy = 1; yy < h - 1; yy++) { for (let y = y0; y <= y1; y++) {
for (let xx = 1; xx < w - 1; xx++) { const rowOffset = y * w;
const globalX = x0 + xx;
const globalY = y0 + yy;
const dx = globalX - pos.x; for (let x = x0; x <= x1; x++) {
const dy = globalY - pos.y; const v = edgeGray[rowOffset + x];
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist > radius) { if (v > EDGE_DARK_THRESHOLD) {
continue; 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)); if (d2 > r2) {
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) {
continue; continue;
} }
/* const score = Math.sqrt(d2) - (EDGE_DARK_THRESHOLD - v) * 0.03;
* 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 = darknessScore + gradientScore - distancePenalty; if (score < bestScore) {
if (score > bestScore) {
bestScore = score; bestScore = score;
best = { best = {
x: globalX, x: x,
y: globalY 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; return best;
} }
@@ -1756,6 +1799,12 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (saveBtn) { if (saveBtn) {
saveBtn.disabled = true; saveBtn.disabled = true;
} }
const dxfBtn = document.getElementById('exportDxfBtn');
if (dxfBtn) {
dxfBtn.disabled = true;
}
} }
function isSelectedPoint(type, holeIndex, pointIndex) { function isSelectedPoint(type, holeIndex, pointIndex) {
@@ -2315,6 +2364,7 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
}; };
document.getElementById('saveManualAreaBtn').disabled = false; document.getElementById('saveManualAreaBtn').disabled = false;
document.getElementById('exportDxfBtn').disabled = false;
setManualStatus( setManualStatus(
`Area finale: ${finalAreaMm2.toFixed(3)} mm² = ${finalAreaCm2.toFixed(4)} cm². ` + `Area finale: ${finalAreaMm2.toFixed(3)} mm² = ${finalAreaCm2.toFixed(4)} cm². ` +
@@ -2335,8 +2385,17 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
return; return;
} }
let dxfContent = '';
try {
dxfContent = buildDxfString();
} catch (e) {
console.warn('DXF build failed:', e);
}
const payload = { const payload = {
id: currentManualJobId, id: currentManualJobId,
dxf_content: dxfContent,
area_mm2: +lastManualResult.area_mm2.toFixed(6), area_mm2: +lastManualResult.area_mm2.toFixed(6),
area_cm2: +lastManualResult.area_cm2.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) { function polygonAreaPx2(points) {
let area = 0; let area = 0;
@@ -2634,6 +2756,8 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
btn.classList.add('btn-warning', 'active'); btn.classList.add('btn-warning', 'active');
btn.innerText = '🧲 Magnete bordo ON'; btn.innerText = '🧲 Magnete bordo ON';
rebuildEdgeCache();
setManualStatus( setManualStatus(
'Magnete bordo attivo: quando disegni o sposti punti, il sistema prova ad agganciarli al bordo scuro più vicino.' 'Magnete bordo attivo: quando disegni o sposti punti, il sistema prova ad agganciarli al bordo scuro più vicino.'
); );
+52 -1
View File
@@ -50,6 +50,7 @@ try {
$outerPolygon = $input['manual_polygon'] ?? null; $outerPolygon = $input['manual_polygon'] ?? null;
$holes = $input['manual_holes'] ?? []; $holes = $input['manual_holes'] ?? [];
$roi = $input['roi'] ?? null; $roi = $input['roi'] ?? null;
$dxfContent = isset($input['dxf_content']) ? (string)$input['dxf_content'] : '';
if ($areaMm2 <= 0) { if ($areaMm2 <= 0) {
jsonResponse([ jsonResponse([
@@ -106,6 +107,43 @@ try {
$manualHolesJson = json_encode($holes); $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; $roiX = null;
$roiY = null; $roiY = null;
$roiW = null; $roiW = null;
@@ -152,6 +190,9 @@ try {
manual_holes_json = ?, manual_holes_json = ?,
manual_status = 'completed', manual_status = 'completed',
dxf_filename = COALESCE(?, dxf_filename),
dxf_url = COALESCE(?, dxf_url),
scale_used = ?, scale_used = ?,
scale_detected = ?, scale_detected = ?,
confidence = 'manual_validated', confidence = 'manual_validated',
@@ -188,6 +229,9 @@ try {
$manualPolygonJson, $manualPolygonJson,
$manualHolesJson, $manualHolesJson,
$dxfFilename,
$dxfUrl,
$mmPerPx, $mmPerPx,
'manual', 'manual',
@@ -227,6 +271,9 @@ try {
manual_holes_json = ?, manual_holes_json = ?,
manual_status = 'completed', manual_status = 'completed',
dxf_filename = COALESCE(?, dxf_filename),
dxf_url = COALESCE(?, dxf_url),
scale_used = ?, scale_used = ?,
scale_detected = ?, scale_detected = ?,
confidence = 'manual_validated', confidence = 'manual_validated',
@@ -264,6 +311,9 @@ try {
$manualPolygonJson, $manualPolygonJson,
$manualHolesJson, $manualHolesJson,
$dxfFilename,
$dxfUrl,
$mmPerPx, $mmPerPx,
'manual', 'manual',
@@ -285,7 +335,8 @@ try {
'area_mm2' => $areaMm2, 'area_mm2' => $areaMm2,
'area_cm2' => $areaCm2, 'area_cm2' => $areaCm2,
'outer_area_mm2' => $outerAreaMm2, 'outer_area_mm2' => $outerAreaMm2,
'holes_area_mm2' => $holesAreaMm2 'holes_area_mm2' => $holesAreaMm2,
'dxf_url' => $dxfUrl
]); ]);
} catch (Throwable $e) { } catch (Throwable $e) {
error_log('CAD manual area save error: ' . $e->getMessage()); 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