feat(brain): Knapsack DP replacement for optimal bar nesting

This commit is contained in:
2026-08-27 10:44:13 +00:00
parent 80464b9187
commit 261ccd0927
+37 -41
View File
@@ -495,14 +495,13 @@ function showFeedback(message, type) {
showToast(message, type); showToast(message, type);
} }
// ===== ALGORITMO FFD AVANÇADO ===== // ===== ALGORITMO OTIMIZADO AVANÇADO (Programação Dinâmica / Knapsack) =====
function optimizeCutting() { function optimizeCutting() {
if (availableBars.length === 0 || demandPieces.length === 0) { if (availableBars.length === 0 || demandPieces.length === 0) {
showToast('Adicione barras e peças', 'warning'); showToast("Adicione barras e peças", "warning");
return; return;
} }
// Expandir peças com ID único global
const expandedPieces = []; const expandedPieces = [];
let uniqueIdCounter = 0; let uniqueIdCounter = 0;
demandPieces.forEach(p => { demandPieces.forEach(p => {
@@ -511,14 +510,12 @@ function optimizeCutting() {
} }
}); });
// FFD em cada barra disponível
const usedBars = []; const usedBars = [];
const unusedPieces = [...expandedPieces]; let unusedPieces = [...expandedPieces];
// Ordenar barras por comprimento (maior para menor) para tentar usar as maiores primeiro? const sortedBarTypes = [...availableBars].sort((a, b) => b.length - a.length);
// O código original não ordenava, seguia a ordem de inserção. Manteremos assim por enquanto.
for (let barType of availableBars) { for (let barType of sortedBarTypes) {
for (let barCopy = 0; barCopy < barType.qty; barCopy++) { for (let barCopy = 0; barCopy < barType.qty; barCopy++) {
if (unusedPieces.length === 0) break; if (unusedPieces.length === 0) break;
@@ -531,48 +528,47 @@ function optimizeCutting() {
isSimulated: barType.isSimulated || false isSimulated: barType.isSimulated || false
}; };
// Tentar encaixar peças (FFD) unusedPieces.sort((a, b) => b.length - a.length);
// Ordenar peças restantes por tamanho decrescente
const sorted = [...unusedPieces].sort((a, b) => b.length - a.length);
const toRemoveIds = [];
for (let piece of sorted) { const N = unusedPieces.length;
// Check if piece fits considering kerf loss for this cut const capacity = bar.remaining;
// We assume each piece consumes its length + kerf
// Exception: The very last piece in a bar might not strictly need a kerf if it's the end,
// but usually in cutting processes, you cut the piece out, so kerf is consumed.
// User requirement: "consumo adicional de cada corte... sera de mais 2mm para cada corte"
const requiredSpace = piece.length + kerfSize; const dp = new Int32Array(capacity + 1);
const keep = new Array(N);
// However, we need to be careful. If remaining is EXACTLY piece.length, can we cut it? for (let i = 0; i < N; i++) {
// If we cut it, we lose the kerf. So we need remaining >= piece.length + kerf? keep[i] = new Uint8Array(capacity + 1);
// Or does the kerf come from the "waste"? const w = unusedPieces[i].length + kerfSize;
// Usually: Bar 6000. Piece 1000. Kerf 3.
// Cut 1: Consumes 1003. Remaining 4997.
// So yes, we treat piece length as (length + kerf).
if (bar.remaining >= requiredSpace) { for (let j = capacity; j >= 0; j--) {
bar.pieces.push(piece); if (j >= w && dp[j - w] + w > dp[j]) {
bar.remaining -= requiredSpace; dp[j] = dp[j - w] + w;
toRemoveIds.push(piece.uniqueId); keep[i][j] = 1;
} else if (bar.remaining >= piece.length && bar.remaining < requiredSpace) { }
// Edge case: Fits exactly or with less than kerf remaining?
// If I have 1000mm remaining and need 1000mm piece.
// If I cut, I destroy 3mm. So I need 1003mm to get a 1000mm piece?
// Yes, usually. Unless it's the raw end of the bar, but we can't assume that.
// Let's stick to the rule: consumption = length + kerf.
// If bar.remaining < length + kerf, we can't cut it.
} }
} }
// Remover peças colocadas usando ID único let bestW = 0;
for (let uid of toRemoveIds) { for(let j = 0; j <= capacity; j++) {
const idx = unusedPieces.findIndex(p => p.uniqueId === uid); if(dp[j] > dp[bestW]) bestW = j;
if (idx !== -1) unusedPieces.splice(idx, 1); }
let currW = bestW;
const toRemoveIds = [];
for (let i = N - 1; i >= 0; i--) {
if (keep[i][currW] === 1) {
const selectedPiece = unusedPieces[i];
bar.pieces.push(selectedPiece);
const w = selectedPiece.length + kerfSize;
bar.remaining -= w;
currW -= w;
toRemoveIds.push(selectedPiece.uniqueId);
}
} }
if (bar.pieces.length > 0) { if (toRemoveIds.length > 0) {
const removeSet = new Set(toRemoveIds);
unusedPieces = unusedPieces.filter(p => !removeSet.has(p.uniqueId));
usedBars.push(bar); usedBars.push(bar);
} }
} }