#include "fiko/strafor/strafor.hpp" #include #include #include #include #include #include #include #include namespace fiko::strafor { namespace { // Kalınlık (mm) → renk paleti (web KALINLIK_RENKLERI ikizi). Vec3f hex(u8 r, u8 g, u8 b) { return {r / 255.0f, g / 255.0f, b / 255.0f}; } // Geçici havuzdan en uygun kütüğü bul (6 oryantasyon). Geçici kütükler yatay // (X/Y kenar dolgusu) olarak da kullanılabilir; sadece hacim yeterli olmalı. struct GeciciSecim { StraforKutuk* ref = nullptr; f64 en = 0, boy = 0, yuk = 0; }; const std::array, 6> ORYANTASYON = {{ {{0, 1, 2}}, {{0, 2, 1}}, {{1, 0, 2}}, {{1, 2, 0}}, {{2, 0, 1}}, {{2, 1, 0}}}}; bool enUygunGeciciBul(std::vector& havuz, f64 nEn, f64 nBoy, f64 nYuk, GeciciSecim& secim) { bool bulundu = false; f64 enAzFire = 0; for (auto& g : havuz) { if (g.adet <= 0) continue; const f64 d[3] = {g.en, g.boy, g.yukseklik}; for (const auto& o : ORYANTASYON) { if (d[o[0]] >= nEn && d[o[1]] >= nBoy && d[o[2]] >= nYuk) { const f64 fire = d[o[0]] * d[o[1]] * d[o[2]] - nEn * nBoy * nYuk; if (!bulundu || fire < enAzFire) { bulundu = true; enAzFire = fire; secim = {&g, d[o[0]], d[o[1]], d[o[2]]}; } } } } return bulundu; } // Kenar parçaları için en uygun kütük (tüm oryantasyonlar). Örn: 40mm şerit için // 50mm kütük döndürülerek. Maliyet öncelikli, sonra fire. Sadece gerçek iyileşmede. struct KenarSecim { f64 en = 0, boy = 0, yuk = 0, maliyet = 0, kalinlik = 0; }; bool kenarIcinEnUygunKutuk(const std::map& kutukMap, f64 nEn, f64 nBoy, f64 nYuk, f64 stdFire, f64 stdMaliyet, KenarSecim& secim) { bool bulundu = false; f64 enFire = 0, enMaliyet = 0; for (const auto& [k, kutuk] : kutukMap) { const f64 d[3] = {kutuk.en, kutuk.boy, kutuk.yukseklik}; for (const auto& o : ORYANTASYON) { if (d[o[0]] >= nEn && d[o[1]] >= nBoy && d[o[2]] >= nYuk) { const f64 fire = d[o[0]] * d[o[1]] * d[o[2]] - nEn * nBoy * nYuk; const f64 maliyet = kutuk.birimFiyat; if (!bulundu || maliyet < enMaliyet || (maliyet == enMaliyet && fire < enFire)) { bulundu = true; enFire = fire; enMaliyet = maliyet; secim = {d[o[0]], d[o[1]], d[o[2]], maliyet, kutuk.yukseklik}; } } } } if (bulundu && (enMaliyet < stdMaliyet || enFire < stdFire * 0.7)) return true; return false; } // Kalınlık kombinasyonlarını bul (DFS). Hedef yüksekliğe TAM ulaşanlar öncelikli. std::vector> kalinlikKombinasyonlariBul(f64 hedef, const std::vector& kalinliklar, size_t enFazla) { std::vector sorted = kalinliklar; std::sort(sorted.begin(), sorted.end(), std::greater()); struct Aday { std::vector katmanlar; f64 fazla; }; std::vector sonuclar; const f64 enInce = sorted.back(); const int maxKatman = std::min((int)std::ceil(hedef / enInce), 10); std::vector mevcut; std::function ara = [&](f64 kalan, int idx) { if (kalan == 0) { sonuclar.push_back({mevcut, 0}); return; } if (kalan < 0) return; if ((int)mevcut.size() >= maxKatman || sonuclar.size() >= 500) return; const int kalanKatman = maxKatman - (int)mevcut.size(); if (kalan > kalanKatman * sorted[idx]) return; for (int i = idx; i < (int)sorted.size(); ++i) { mevcut.push_back(sorted[i]); ara(kalan - sorted[i], i); mevcut.pop_back(); } }; ara(hedef, 0); // Tam eşleşme yoksa en yakın aşanları dene. if (sonuclar.empty()) { std::function araFazlali = [&](f64 kalan, int idx) { if (kalan <= 0) { sonuclar.push_back({mevcut, -kalan}); return; } if ((int)mevcut.size() >= maxKatman || sonuclar.size() >= 300) return; const int kalanK = maxKatman - (int)mevcut.size(); if (kalan > kalanK * sorted[idx]) return; for (int i = idx; i < (int)sorted.size(); ++i) { mevcut.push_back(sorted[i]); araFazlali(kalan - sorted[i], i); mevcut.pop_back(); } }; araFazlali(hedef, 0); } std::stable_sort(sonuclar.begin(), sonuclar.end(), [](const Aday& a, const Aday& b) { if (a.fazla != b.fazla) return a.fazla < b.fazla; return a.katmanlar.size() < b.katmanlar.size(); }); std::set seen; std::vector> unique; for (const auto& s : sonuclar) { std::string key; for (f64 v : s.katmanlar) { key += std::to_string((i64)v); key += '+'; } if (seen.insert(key).second) { unique.push_back(s.katmanlar); if (unique.size() >= enFazla) break; } } return unique; } // Ortak plan skoru (düşük = iyi). İşçilik-öncelikli: yapıştırıcıyı en az uğraştıran // (az blok + az yapıştırma) plan öne gelir. iscilikAgirlik büyüdükçe işçilik ağır basar // (nesting'in çok-parça bedeli); 0 = malzeme-öncelikli (yalnız fire/maliyet). f64 planSkoru(f64 fireOrani, size_t bloklar, size_t katmanlar, f64 maliyet, f64 fazlaKalinlik, f64 yapistamaAlani, bool nesting, f64 iscilikAgirlik, i32 geciciKullanilan, bool geciciOncelikli) { // İşçilik = kesilecek/yapıştırılacak parça sayısı + yapıştırma alanı (m²). const f64 iscilik = ((f64)bloklar * 2.0 + yapistamaAlani / 1.0e6 * 3.0) * iscilikAgirlik; const f64 nestingCeza = nesting ? 8.0 * iscilikAgirlik : 0.0; // çok küçük parça = çok emek const f64 geciciBonus = geciciOncelikli ? 50.0 : 10.0; const f64 stdBlokCeza = geciciOncelikli ? ((f64)bloklar - geciciKullanilan) * 5.0 : 0.0; return std::max(0.0, fireOrani) * 3.0 + iscilik + nestingCeza + (f64)katmanlar * 2.0 + maliyet * 0.005 + fazlaKalinlik * 0.5 - geciciKullanilan * geciciBonus + stdBlokCeza; } std::string kalinlikAciklamaYap(const std::vector& kalinliklar) { std::string s; for (size_t i = 0; i < kalinliklar.size(); ++i) { if (i) s += '+'; s += std::to_string((i64)kalinliklar[i]); } s += "mm"; return s; } // Tek montaj planı oluştur. Geçici kütükler her pozisyonda standart bloklardan // ÖNCE kontrol edilir. bool planOlustur(const HedefParca& hedef, bool dondurulmus, f64 kutukEn, f64 kutukBoy, i32 adetX, i32 adetY, const std::vector& kalinliklar, const std::map& kutukMap, const std::vector& geciciKutukler, bool geciciOncelikli, f64 iscilikAgirlik, MontajPlani& out) { std::vector katmanlar; f64 zPoz = 0; for (f64 k : kalinliklar) { auto it = kutukMap.find(k); if (it == kutukMap.end()) return false; const StraforKutuk& kutuk = it->second; katmanlar.push_back({kutuk.id, kutuk.isim, k, k, false, 0, zPoz, kutuk.birimFiyat, kutuk.renk}); zPoz += k; } f64 toplamKalinlik = 0; for (f64 v : kalinliklar) toplamKalinlik += v; const f64 fazlaKalinlik = toplamKalinlik - hedef.yukseklik; std::vector geciciHavuz = geciciKutukler; // adet takibi için kopya std::vector bloklar; f64 toplamMaliyet = 0, yapistamaAlani = 0, kullanilanHacim = 0, geciciTasarruf = 0; i32 kesimSayisi = 0, tamBlokSay = 0, geciciKullanilanAdet = 0; auto blokAd = [](i32 ix, i32 iy, i32 iz) { char b[32]; std::snprintf(b, sizeof(b), "b-%d-%d-%d", ix, iy, iz); return std::string(b); }; for (i32 iz = 0; iz < (i32)katmanlar.size(); ++iz) { const KalinlikKatman& kat = katmanlar[iz]; for (i32 ix = 0; ix < adetX; ++ix) { for (i32 iy = 0; iy < adetY; ++iy) { const f64 pozX = ix * kutukEn; const f64 pozY = iy * kutukBoy; const f64 gercekEn = std::min(kutukEn, hedef.en - pozX); const f64 gercekBoy = std::min(kutukBoy, hedef.boy - pozY); const f64 gercekYuk = kat.kalinlik; std::vector yapistama; if (ix > 0) { const f64 a = gercekBoy * gercekYuk; yapistama.push_back({Eksen::X, false, a}); yapistamaAlani += a; } if (iy > 0) { const f64 a = gercekEn * gercekYuk; yapistama.push_back({Eksen::Y, false, a}); yapistamaAlani += a; } if (iz > 0) { const f64 a = gercekEn * gercekBoy; yapistama.push_back({Eksen::Z, false, a}); yapistamaAlani += a; } GeciciSecim gs; if (enUygunGeciciBul(geciciHavuz, gercekEn, gercekBoy, gercekYuk, gs)) { gs.ref->adet--; geciciKullanilanAdet++; geciciTasarruf += kat.birimFiyat; const bool kesX = gs.en > gercekEn, kesY = gs.boy > gercekBoy, kesZ = gs.yuk > gercekYuk; const bool kesilecek = kesX || kesY || kesZ; if (kesilecek) kesimSayisi++; else tamBlokSay++; kullanilanHacim += gs.en * gs.boy * gs.yuk; BlokYerlestirme b; b.id = blokAd(ix, iy, iz); b.katmanIdx = iz; b.gridPoz = {ix, iy, iz}; b.pozisyon = {pozX, pozY, kat.zPozisyon}; b.tamBoyut = {gs.en, gs.boy, gs.yuk}; b.gercekBoyut = {gercekEn, gercekBoy, gercekYuk}; b.kesilecekMi = kesilecek; b.kesimMiktari = {kesX ? gs.en - gercekEn : 0, kesY ? gs.boy - gercekBoy : 0, kesZ ? gs.yuk - gercekYuk : 0}; b.yapistamaYuzleri = yapistama; b.katmanRenk = kat.renk; b.kaynakKalinlik = gs.ref->yukseklik; b.geciciKutukId = gs.ref->id; b.geciciKutukIsim = gs.ref->isim; bloklar.push_back(std::move(b)); } else { f64 bEn = kutukEn, bBoy = kutukBoy, bYuk = kat.kalinlik, bMaliyet = kat.birimFiyat; f64 bKaynakKalinlik = kat.kalinlik; const bool stdKesX = gercekEn < kutukEn, stdKesY = gercekBoy < kutukBoy; if (stdKesX || stdKesY) { const f64 stdFire = kutukEn * kutukBoy * kat.kalinlik - gercekEn * gercekBoy * gercekYuk; KenarSecim alt; if (kenarIcinEnUygunKutuk(kutukMap, gercekEn, gercekBoy, gercekYuk, stdFire, kat.birimFiyat, alt)) { bEn = alt.en; bBoy = alt.boy; bYuk = alt.yuk; bMaliyet = alt.maliyet; bKaynakKalinlik = alt.kalinlik; } } const bool kesX = gercekEn < bEn, kesY = gercekBoy < bBoy, kesZ = gercekYuk < bYuk; const bool kesilecek = kesX || kesY || kesZ; if (kesilecek) kesimSayisi++; else tamBlokSay++; kullanilanHacim += bEn * bBoy * bYuk; toplamMaliyet += bMaliyet; BlokYerlestirme b; b.id = blokAd(ix, iy, iz); b.katmanIdx = iz; b.gridPoz = {ix, iy, iz}; b.pozisyon = {pozX, pozY, kat.zPozisyon}; b.tamBoyut = {bEn, bBoy, bYuk}; b.gercekBoyut = {gercekEn, gercekBoy, gercekYuk}; b.kesilecekMi = kesilecek; b.kesimMiktari = {kesX ? bEn - gercekEn : 0, kesY ? bBoy - gercekBoy : 0, kesZ ? bYuk - gercekYuk : 0}; b.yapistamaYuzleri = yapistama; b.katmanRenk = kat.renk; b.kaynakKalinlik = bKaynakKalinlik; bloklar.push_back(std::move(b)); } } } } const f64 hedefHacim = hedef.en * hedef.boy * hedef.yukseklik; const f64 fireHacim = kullanilanHacim - hedefHacim; const f64 fireOrani = kullanilanHacim > 0 ? (fireHacim / kullanilanHacim) * 100.0 : 0; const f64 skor = planSkoru(fireOrani, bloklar.size(), katmanlar.size(), toplamMaliyet, fazlaKalinlik, yapistamaAlani, /*nesting*/ false, iscilikAgirlik, geciciKullanilanAdet, geciciOncelikli); out.hedef = hedef; out.dondurulmus = dondurulmus; out.kutukEn = kutukEn; out.kutukBoy = kutukBoy; out.adetX = adetX; out.adetY = adetY; out.katmanlar = std::move(katmanlar); out.bloklar = std::move(bloklar); out.toplamBlok = (i32)out.bloklar.size(); out.fireOrani = std::round(std::max(0.0, fireOrani) * 100.0) / 100.0; out.hedefHacim = hedefHacim; out.kullanilanHacim = kullanilanHacim; out.fireHacim = std::max(0.0, fireHacim); out.toplamMaliyet = toplamMaliyet; out.toplamKesimSayisi = kesimSayisi; out.tamBlokSayisi = tamBlokSay; out.toplamYapistamaAlani = yapistamaAlani; out.kalinlikAciklama = kalinlikAciklamaYap(kalinliklar); out.skor = skor; out.kullanilanGeciciAdet = geciciKullanilanAdet; out.geciciTasarruf = geciciTasarruf; return true; } // ── Offcut-nesting (giyotin 2D cutting-stock, blok-içi artık yeniden kullanım) ── struct SerbestDikdortgen { f64 en, boy; }; struct AltBlok { f64 x, y, en, boy; bool yeniKutuk; }; // [x,x+w]×[y,y+h] bölgesini doldur: önce artık (offcut) havuzundan en çok kaplayan // parçayı al, yoksa YENİ kütük satın al (kutukSayac++). Kaynak artığını + bölge // kalanını giyotin kesimle bankalar/özyineler. Parça büyükse bölge küçüldüğü için // özyineleme sonlanır (parça her zaman >0). void giyotinDoldur(std::vector& havuz, f64 x, f64 y, f64 w, f64 h, f64 kutukEn, f64 kutukBoy, int& kutukSayac, std::vector& out) { const f64 eps = 1e-6; if (w <= eps || h <= eps) return; int best = -1; f64 bestKapsam = -1; for (size_t i = 0; i < havuz.size(); ++i) { const f64 pw = std::min(w, havuz[i].en), ph = std::min(h, havuz[i].boy); if (pw <= eps || ph <= eps) continue; const f64 kap = pw * ph; if (kap > bestKapsam) { bestKapsam = kap; best = (int)i; } } f64 kEn, kBoy; bool yeni; if (best >= 0) { kEn = havuz[best].en; kBoy = havuz[best].boy; havuz.erase(havuz.begin() + best); yeni = false; } else { kEn = kutukEn; kBoy = kutukBoy; kutukSayac++; yeni = true; } const f64 pw = std::min(w, kEn), ph = std::min(h, kBoy); out.push_back({x, y, pw, ph, yeni}); // Kaynak (kütük/artık) kalanını giyotinle bankala: sağ (kEn-pw)×ph + üst kEn×(kBoy-ph). if (kEn - pw > eps) havuz.push_back({kEn - pw, ph}); if (kBoy - ph > eps) havuz.push_back({kEn, kBoy - ph}); // Bölge kalanı (giyotin): sağ (w-pw)×ph @(x+pw,y) + alt w×(h-ph) @(x,y+ph). giyotinDoldur(havuz, x + pw, y, w - pw, ph, kutukEn, kutukBoy, kutukSayac, out); giyotinDoldur(havuz, x, y + ph, w, h - ph, kutukEn, kutukBoy, kutukSayac, out); } // İki eksen-hizalı bloğun paylaştığı yüz alanı (yapıştırma). Temas yoksa 0. f64 temasAlani(const BlokYerlestirme& a, const BlokYerlestirme& b) { const f64 eps = 1e-4; const f64 axmin = a.pozisyon.x, axmax = a.pozisyon.x + a.gercekBoyut.en; const f64 aymin = a.pozisyon.y, aymax = a.pozisyon.y + a.gercekBoyut.boy; const f64 azmin = a.pozisyon.z, azmax = a.pozisyon.z + a.gercekBoyut.yukseklik; const f64 bxmin = b.pozisyon.x, bxmax = b.pozisyon.x + b.gercekBoyut.en; const f64 bymin = b.pozisyon.y, bymax = b.pozisyon.y + b.gercekBoyut.boy; const f64 bzmin = b.pozisyon.z, bzmax = b.pozisyon.z + b.gercekBoyut.yukseklik; auto ort = [](f64 a0, f64 a1, f64 b0, f64 b1) { return std::max(0.0, std::min(a1, b1) - std::max(a0, b0)); }; const f64 ox = ort(axmin, axmax, bxmin, bxmax); const f64 oy = ort(aymin, aymax, bymin, bymax); const f64 oz = ort(azmin, azmax, bzmin, bzmax); // X'te yüz-yüze temas → paylaşılan alan = Y×Z örtüşmesi (diğerleri benzer). if (std::abs(axmax - bxmin) < eps || std::abs(bxmax - axmin) < eps) if (oy > eps && oz > eps) return oy * oz; if (std::abs(aymax - bymin) < eps || std::abs(bymax - aymin) < eps) if (ox > eps && oz > eps) return ox * oz; if (std::abs(azmax - bzmin) < eps || std::abs(bzmax - azmin) < eps) if (ox > eps && oy > eps) return ox * oy; return 0; } // Bir footprint × kalınlık kombinasyonu için offcut-nesting planı üret. bool planOlusturNesting(const HedefParca& hedef, bool dondurulmus, f64 kutukEn, f64 kutukBoy, const std::vector& kalinliklar, const std::map& kutukMap, f64 iscilikAgirlik, MontajPlani& out) { std::vector katmanlar; f64 zPoz = 0; for (f64 k : kalinliklar) { auto it = kutukMap.find(k); if (it == kutukMap.end()) return false; const StraforKutuk& kutuk = it->second; katmanlar.push_back({kutuk.id, kutuk.isim, k, k, false, 0, zPoz, kutuk.birimFiyat, kutuk.renk}); zPoz += k; } // Artık havuzu kalınlık başına (Z'de kesim yok → farklı kalınlıklar paylaşamaz). std::map> havuz; std::map kutukSayac; std::vector bloklar; for (i32 iz = 0; iz < (i32)katmanlar.size(); ++iz) { const KalinlikKatman& kat = katmanlar[iz]; std::vector altlar; giyotinDoldur(havuz[kat.kalinlik], 0, 0, hedef.en, hedef.boy, kutukEn, kutukBoy, kutukSayac[kat.kalinlik], altlar); for (size_t j = 0; j < altlar.size(); ++j) { const AltBlok& a = altlar[j]; BlokYerlestirme b; char id[32]; std::snprintf(id, sizeof(id), "n-%d-%zu", iz, j); b.id = id; b.katmanIdx = iz; b.gridPoz = {(i32)j, 0, iz}; b.pozisyon = {a.x, a.y, kat.zPozisyon}; b.tamBoyut = {a.en, a.boy, kat.kalinlik}; // parça zaten kesilmiş boyutta b.gercekBoyut = {a.en, a.boy, kat.kalinlik}; b.kesilecekMi = !(a.yeniKutuk && a.en >= kutukEn - 1e-6 && a.boy >= kutukBoy - 1e-6); b.kesimMiktari = {0, 0, 0}; b.katmanRenk = kat.renk; b.kaynakKalinlik = kat.kalinlik; bloklar.push_back(std::move(b)); } } // Yapıştırma alanı: komşu blok çiftleri (O(n²); nesting blok sayısı ılımlı). f64 yapistamaAlani = 0; for (size_t i = 0; i < bloklar.size(); ++i) for (size_t j = i + 1; j < bloklar.size(); ++j) yapistamaAlani += temasAlani(bloklar[i], bloklar[j]); f64 toplamKalinlik = 0; for (f64 v : kalinliklar) toplamKalinlik += v; const f64 fazlaKalinlik = toplamKalinlik - hedef.yukseklik; // Malzeme = satın alınan kütük hacmi (artık yeniden kullanım fire'ı düşürür). f64 kullanilanHacim = 0, toplamMaliyet = 0; i32 kesimSayisi = 0, tamBlokSay = 0; for (const auto& [k, adet] : kutukSayac) { auto it = kutukMap.find(k); const f64 fiyat = it != kutukMap.end() ? it->second.birimFiyat : 0; kullanilanHacim += adet * (kutukEn * kutukBoy * k); toplamMaliyet += adet * fiyat; } for (const auto& b : bloklar) (b.kesilecekMi ? kesimSayisi : tamBlokSay)++; const f64 hedefHacim = hedef.en * hedef.boy * hedef.yukseklik; const f64 fireHacim = kullanilanHacim - hedefHacim; const f64 fireOrani = kullanilanHacim > 0 ? (fireHacim / kullanilanHacim) * 100.0 : 0; out.hedef = hedef; out.dondurulmus = dondurulmus; out.kutukEn = kutukEn; out.kutukBoy = kutukBoy; out.adetX = (i32)std::ceil(hedef.en / kutukEn); out.adetY = (i32)std::ceil(hedef.boy / kutukBoy); out.katmanlar = std::move(katmanlar); out.bloklar = std::move(bloklar); out.toplamBlok = (i32)out.bloklar.size(); out.fireOrani = std::round(std::max(0.0, fireOrani) * 100.0) / 100.0; out.hedefHacim = hedefHacim; out.kullanilanHacim = kullanilanHacim; out.fireHacim = std::max(0.0, fireHacim); out.toplamMaliyet = toplamMaliyet; out.toplamKesimSayisi = kesimSayisi; out.tamBlokSayisi = tamBlokSay; out.toplamYapistamaAlani = yapistamaAlani; out.kalinlikAciklama = kalinlikAciklamaYap(kalinliklar); out.nesting = true; out.skor = planSkoru(fireOrani, out.bloklar.size(), out.katmanlar.size(), toplamMaliyet, fazlaKalinlik, yapistamaAlani, /*nesting*/ true, iscilikAgirlik, /*geciciKullanilan*/ 0, /*geciciOncelikli*/ false); return true; } } // namespace Vec3f kalinlikRengi(f64 kalinlik) { switch ((i64)kalinlik) { case 50: return hex(0xA5, 0xD6, 0xA7); case 100: return hex(0xFF, 0xB7, 0x4D); case 150: return hex(0x4F, 0xC3, 0xF7); case 200: return hex(0x81, 0xC7, 0x84); case 250: return hex(0xF0, 0x62, 0x92); case 300: return hex(0x95, 0x75, 0xCD); case 350: return hex(0xFF, 0xD5, 0x4F); default: return hex(0xCC, 0xCC, 0xCC); } } std::vector standartKutukler() { return { {"eps-50", "EPS 50mm", 1230, 2000, 50, 99, kalinlikRengi(50), 30, false}, {"eps-100", "EPS 100mm", 1230, 2000, 100, 99, kalinlikRengi(100), 50, false}, {"eps-150", "EPS 150mm", 1230, 2000, 150, 99, kalinlikRengi(150), 70, false}, {"eps-200", "EPS 200mm", 1230, 2000, 200, 99, kalinlikRengi(200), 90, false}, {"eps-250", "EPS 250mm", 1230, 2000, 250, 99, kalinlikRengi(250), 110, false}, {"eps-300", "EPS 300mm", 1230, 2000, 300, 99, kalinlikRengi(300), 130, false}, {"eps-350", "EPS 350mm", 1230, 2000, 350, 99, kalinlikRengi(350), 150, false}, }; } std::vector montajHesapla(const HedefParca& hedef, const std::vector& kutukler, const std::vector& geciciKutukler, bool geciciOncelikli, int varyantLimit, bool offcutNesting, f64 iscilikAgirlik) { if (kutukler.empty() || hedef.en <= 0 || hedef.boy <= 0 || hedef.yukseklik <= 0) return {}; if (varyantLimit < 1) varyantLimit = 1; // Kalınlık → en ucuz kütük eşleştirmesi. std::map kutukMap; for (const auto& k : kutukler) { auto it = kutukMap.find(k.yukseklik); if (it == kutukMap.end() || k.birimFiyat < it->second.birimFiyat) kutukMap[k.yukseklik] = k; } if (kutukMap.empty()) return {}; std::vector kalinliklar; for (const auto& [k, v] : kutukMap) kalinliklar.push_back(k); std::sort(kalinliklar.begin(), kalinliklar.end(), std::greater()); // Kalınlık varyantları: limitle orantılı geniş küme (ince-üst dahil tüm mantıklılar). const size_t comboMax = std::max(24, (size_t)varyantLimit); const auto kombinasyonlar = kalinlikKombinasyonlariBul(hedef.yukseklik, kalinliklar, comboMax); // TABAN (footprint) varyantları: tek yön değil — her AYRIK kütük ayak izi taban // olarak denensin (standartlar aynı ise tek taban; özel/geçici farklı ise çeşitlenir). std::vector> tabanlar; std::set tabanSeen; auto tabanEkle = [&](f64 e, f64 b) { if (e < 10 || b < 10) return; const std::string key = std::to_string((i64)e) + "x" + std::to_string((i64)b); if (tabanSeen.insert(key).second) tabanlar.push_back({e, b}); }; for (const auto& k : kutukler) tabanEkle(k.en, k.boy); if (tabanlar.empty()) tabanEkle(kutukler[0].en, kutukler[0].boy); std::vector secenekler; for (const auto& [tEn, tBoy] : tabanlar) { for (bool dondurulmus : {false, true}) { const f64 kEn = dondurulmus ? tBoy : tEn; const f64 kBoy = dondurulmus ? tEn : tBoy; const i32 adetX = (i32)std::ceil(hedef.en / kEn); const i32 adetY = (i32)std::ceil(hedef.boy / kBoy); for (const auto& kombi : kombinasyonlar) { MontajPlani p; if (planOlustur(hedef, dondurulmus, kEn, kBoy, adetX, adetY, kombi, kutukMap, geciciKutukler, geciciOncelikli, iscilikAgirlik, p)) secenekler.push_back(std::move(p)); if (offcutNesting) { MontajPlani np; if (planOlusturNesting(hedef, dondurulmus, kEn, kBoy, kombi, kutukMap, iscilikAgirlik, np)) secenekler.push_back(std::move(np)); } } } } // Geçici öncelikli: geçici kütük boyutlarına göre ek grid denemeleri. if (geciciOncelikli && !geciciKutukler.empty()) { std::set denenmis; const std::array, 6> ciftler = {{ {{0, 1}}, {{1, 0}}, {{0, 2}}, {{2, 0}}, {{1, 2}}, {{2, 1}}}}; for (const auto& g : geciciKutukler) { if (g.adet <= 0) continue; const f64 dims[3] = {g.en, g.boy, g.yukseklik}; for (const auto& c : ciftler) { const f64 kEn = dims[c[0]], kBoy = dims[c[1]]; if (kEn < 10 || kBoy < 10) continue; const std::string key = std::to_string((i64)kEn) + "_" + std::to_string((i64)kBoy); if (!denenmis.insert(key).second) continue; const i32 adetX = (i32)std::ceil(hedef.en / kEn); const i32 adetY = (i32)std::ceil(hedef.boy / kBoy); for (const auto& kombi : kombinasyonlar) { MontajPlani p; if (planOlustur(hedef, false, kEn, kBoy, adetX, adetY, kombi, kutukMap, geciciKutukler, true, iscilikAgirlik, p)) secenekler.push_back(std::move(p)); } } } } // Benzersizleştir (aynı hücre boyutu + adetX×adetY + kalınlık = aynı sonuç), min skor tut. // Hücre boyutu anahtara girer → farklı ayak izleri aynı grid sayısında çakışıp elenmesin. std::unordered_map benzersiz; for (auto& s : secenekler) { const std::string key = (s.nesting ? "N" : "G") + std::to_string((i64)s.kutukEn) + "x" + std::to_string((i64)s.kutukBoy) + "@" + std::to_string(s.adetX) + "x" + std::to_string(s.adetY) + "_" + s.kalinlikAciklama; auto it = benzersiz.find(key); if (it == benzersiz.end() || s.skor < it->second.skor) benzersiz[key] = std::move(s); } std::vector sonuc; sonuc.reserve(benzersiz.size()); for (auto& [k, v] : benzersiz) sonuc.push_back(std::move(v)); std::sort(sonuc.begin(), sonuc.end(), [](const MontajPlani& a, const MontajPlani& b) { return a.skor < b.skor; }); if ((int)sonuc.size() > varyantLimit) sonuc.resize(varyantLimit); return sonuc; } } // namespace fiko::strafor