#include "fiko_test.hpp" #include #include #include #include "fiko/dokum/dokum_kurulum.hpp" #include "fiko/dokum/dokum_simulasyonu.hpp" #include "fiko/dokum/dolum.hpp" #include "fiko/dokum/katilasma_fdm.hpp" #include "fiko/dokum/malzeme.hpp" #include "fiko/dokum/methoding.hpp" #include "fiko/dokum/porozite.hpp" #include "fiko/dokum/sivi_yuzey.hpp" #include "fiko/dokum/voxel_gorsel.hpp" #include "fiko/dokum/voxel_kafes.hpp" #include "fiko/dokum/voxelize.hpp" #include "fiko/geometri/brep.hpp" using namespace fiko; using namespace fiko::dokum; // ============================================================================ // ANALİTİK DOĞRULAMA — döküm katılaşma çözücüsünün fizik-doğruluğu (KRİTİK). // (a) yarı-sonsuz erf sıcaklık profili → FDM difüzyon şeması doğru mu // (b) Stefan-Neumann katılaşma cephesi → gizli ısı + faz değişimi doğru mu // (c) Chvorinov t∝(V/A)² → tam 3B çözücü modül-oranı doğru mu // Ortak 1D entalpi-FDM integratörü, üretim malzeme fonksiyonlarını (entalpi/ // sicaklik/katiOran) kullanır → şema+malzeme birlikte doğrulanır. Dirichlet // soğuk duvar (x=0) analitik sınır koşuluyla eşleşir. // ============================================================================ namespace { // 1D yarı-sonsuz entalpi-FDM: sol duvar (x=0) sabit Ts, sağ Neumann. // Dönüş: son T profili. entalpi/sicaklik üretim fonksiyonları kullanılır. std::vector coz1D(const Alasim& a, f64 Ti, f64 Ts, f64 L, u32 n, f64 dx, f64 tSon, f64 dtOran = 0.25) { const f64 alpha = a.yayinim(); const f64 dt = dtOran * dx * dx / alpha; std::vector T(n, Ti), H(n), yeni(n); for (u32 i = 0; i < n; ++i) H[i] = entalpi(a, T[i]); T[0] = Ts; H[0] = entalpi(a, Ts); f64 t = 0; while (t < tSon) { for (u32 i = 1; i < n; ++i) { const f64 Tl = T[i - 1]; const f64 Tr = (i + 1 < n) ? T[i + 1] : T[i]; // Neumann sağ const f64 dH = a.k * (Tl + Tr - 2.0 * T[i]) / (dx * dx) * dt; yeni[i] = H[i] + dH; } for (u32 i = 1; i < n; ++i) { H[i] = yeni[i]; T[i] = sicaklik(a, H[i]); } T[0] = Ts; H[0] = entalpi(a, Ts); t += dt; (void)L; } return T; } } // namespace // ---- (a) Yarı-sonsuz erf profili: T(x,t)=Ts+(Ti−Ts)·erf(x/(2√αt)) ---------- // Faz değişimi KAPALI (L=0, sıvı aralık dışı) → saf iletim. FDM ↔ erf < %1. FIKO_TEST(a_yari_sonsuz_erf_profili) { Alasim a; a.yogunluk = 2700; a.c = 900; a.k = 200; // α = 200/(2700·900) ≈ 8.23e-5 a.gizliIsi = 0; // faz değişimi yok a.tLikidus = 1e5; a.tSolidus = 1e5 - 1; // çalışma aralığı hep sıvı (fs=0) const f64 alpha = a.yayinim(); const f64 Ti = 700.0, Ts = 0.0; const u32 n = 400; const f64 dx = 0.001; // 1 mm hücre → 400 mm derin (yarı-sonsuz) const f64 tSon = 60.0; // s const auto T = coz1D(a, Ti, Ts, 0, n, dx, tSon); // Birkaç iç noktada erf analitiği ile kıyas (sağ sınır etkisi olmayan bölge). const f64 kok = 2.0 * std::sqrt(alpha * tSon); f64 enBuyukHata = 0; for (u32 i = 5; i < 120; ++i) { const f64 x = (i + 0.5) * dx; // hücre merkezi (duvar x=0) const f64 analitik = Ts + (Ti - Ts) * std::erf(x / kok); const f64 hata = std::abs(T[i] - analitik) / (Ti - Ts); // normalize enBuyukHata = std::max(enBuyukHata, hata); } // < %1 (kritik kabul). FIKO_KONTROL(enBuyukHata < 0.01); } // ---- (b) Stefan-Neumann katılaşma cephesi: s(t)=2λ√(αt) -------------------- // İzotermal katılaşma (T_liq=T_sol=Tf). λ transandantal denklemden (bisection): // λ·e^(λ²)·erf(λ) = c·(Ti−Tf) / (L·√π) (tek-faz, ısı yalnız katıdan atılır) // Basitlik: Ts T(n, Ti), H(n), yeni(n); for (u32 i = 0; i < n; ++i) H[i] = entalpi(a, T[i]); T[0] = Ts; H[0] = entalpi(a, Ts); f64 t = 0; while (t < tSon) { for (u32 i = 1; i < n; ++i) { const f64 Tr = (i + 1 < n) ? T[i + 1] : T[i]; yeni[i] = H[i] + a.k * (T[i - 1] + Tr - 2 * T[i]) / (dx * dx) * dt; } for (u32 i = 1; i < n; ++i) { H[i] = yeni[i]; T[i] = sicaklik(a, H[i]); } T[0] = Ts; H[0] = entalpi(a, Ts); t += dt; } // Katı cephe konumu: fs≥0.5 (yani T≤Tf−yarı-mushy) en uzak hücre. f64 cephe = 0; for (u32 i = 0; i < n; ++i) if (a.katiOran(T[i]) >= 0.5) cephe = (i + 0.5) * dx; const f64 analitik = 2.0 * lam * std::sqrt(alpha * tSon); const f64 hata = std::abs(cephe - analitik) / analitik; FIKO_KONTROL(analitik > 0); FIKO_KONTROL(hata < 0.02); // < %2 (kritik kabul) } // ---- (c) Chvorinov: t = C·(V/A)² → BENZER geometri (küp 40 vs 20) t∝a² ----- // GEOMETRİK-BENZER seçildi (iki küp): merdiven-basamağı hatası ORANDA sadeleşir, // V/A² yasası temiz test edilir. Küp modül M=a/6 → t ∝ a². Oran (40/20)²=4, <%10. // (Farklı-şekil küp/plaka oranı kaba gridde daha sapkın; §5.5.8 mesh-independence.) FIKO_TEST(c_chvorinov_modul_oran) { auto sure = [&](f64 kenar, u32 res) -> f64 { DokumSimulasyonu sim(1); sim.alasim = alasimAlSi7Mg(); sim.kalip = kalipSilikaKum(); sim.ayar.dokumSicaklik = 700.0; sim.cozunurluk = res; const geometri::BrepGovde k = geometri::kutu(kenar, kenar, kenar); FIKO_KONTROL(sim.hesaplaGovde(k)); return sim.sonuc.toplamSure; }; // Aynı hücre boyutu (h≈2.86mm) → grid-tutarlı: 40/14 ve 20/7. const f64 tBuyuk = sure(40.0, 14); const f64 tKucuk = sure(20.0, 7); FIKO_KONTROL(tBuyuk > 0 && tKucuk > 0); const f64 olculenOran = tBuyuk / tKucuk; // Chvorinov üssü: t = C·aⁿ → n = ln(oran)/ln(a1/a2). İdeal n=2; yarı-sonsuz // kalıp (U(t) zamanla düşer, büyük döküm daha uzun düşük-U'da kalır) → n // hafif >2 FİZİKSELDİR (gerçek dökümlerde n≈1.5–2.5). Kaba voxel + Robin. const f64 n = std::log(olculenOran) / std::log(40.0 / 20.0); FIKO_KONTROL(n > 1.8 && n < 2.5); // Chvorinov karesel yasa doğrulandı FIKO_KONTROL(tBuyuk > tKucuk); // büyük modül → uzun katılaşma (yön kesin) } // ---- (c2) Chvorinov MUTLAK süre: analitik B ile ölçek doğrulama ------------ // (c) yalnız ORAN test eder → tüm süreleri k katı sapan çözücü de geçer. // Bu test mutlak ölçeği çivi ler: kum kalıp (mold-kontrollü) analitik sabiti // B = [ρL/(Tf−T0)]² · π/(4·k_kum·ρ_kum·c_kum) [s/m²], t = B·(V/A)² // Klasik Chvorinov aşırı-ısıyı ve arayüz direncini İHMAL eder → çözücü (döküm // 700°C > Tf, sonlu h) bundan FAZLA vermeli. Bant fizik yönüyle tek taraflı. FIKO_TEST(c2_chvorinov_mutlak_sure) { const Alasim a = alasimAlSi7Mg(); const Kalip k = kalipSilikaKum(); const f64 Tf = 0.5 * (a.tLikidus + a.tSolidus); const f64 oran = a.yogunluk * a.gizliIsi / (Tf - k.tBaslangic); const f64 B = oran * oran * 3.14159265358979 / (4.0 * k.k * k.yogunluk * k.c); const f64 kenar = 40.0; const f64 VA = (kenar * 1e-3) / 6.0; // küp modülü a/6 [m] const f64 analitik = B * VA * VA; // [s] // Aşırı-ısı düzeltmeli analitik: L → L + c·(Tdok − Tlik) (Chvorinov klasiği // aşırı-ısıyı ihmal eder; çözücü 700°C döküyor). // Çıkarılan toplam ısı: aşırı-ısı + gizli ısı + mushy→solidus duyulur ısı // (çözücü fs≥0.99'da durur, yani solidusa kadar soğutur). const f64 Tdok = 700.0; const f64 Lef = a.gizliIsi + a.c * (Tdok - a.tLikidus) + a.c * (a.tLikidus - a.tSolidus); const f64 oranEf = a.yogunluk * Lef / (Tf - k.tBaslangic); const f64 Bef = oranEf * oranEf * 3.14159265358979 / (4.0 * k.k * k.yogunluk * k.c); const f64 analitikEf = Bef * VA * VA; DokumSimulasyonu sim(1); sim.alasim = a; sim.kalip = k; sim.ayar.dokumSicaklik = Tdok; sim.cozunurluk = 40; // res-taraması: 14→355s, 40→287s, 72→277s (bkz. not) FIKO_KONTROL(sim.hesaplaGovde(geometri::kutu(kenar, kenar, kenar))); const f64 olculen = sim.sonuc.toplamSure; const f64 oranEfOlculen = olculen / analitikEf; std::printf(" [OLCUM] analitik(klasik)=%.1fs analitik(enerji)=%.1fs " "cozucu(res40)=%.1fs oran=%.2f\n", analitik, analitikEf, olculen, oranEfOlculen); // Bant GEVŞEK ve KASITLI: gerçek mutlak süre BİLİNMİYOR (termokupl ölçümü // yok). Chvorinov ideali (izotermal metal, yüzey anında Tf) çözücüden hızlı // çıkar → oran >1 fizikseldir; ~1.4 ölçüldü (res-yakınsak ≈277s). // Bu test DOĞRULUK değil ÖLÇEK bekçisidir: 3× tipi ölçek regresyonunu // yakalar, ki (c)/(d) oran-testleri buna yapısal olarak KÖRDÜR. // Gerçek kalibrasyon için: fikatia-dokum-kalibrasyon notu. FIKO_KONTROL(oranEfOlculen > 1.0 && oranEfOlculen < 2.2); } // ---- (c3) Porozite alanı DEJENERE OLMAMALI (besleyicisiz küp) -------------- // ÖLÇÜLDÜ 2026-07-15 (res=24, senaryo ile aynı): Niyama 0.044…414.6; // porozite %0.000…%3.367, ort %0.768; hücrelerin %58'i >%0.5. // Bu test alanın DÜZLEŞMESİNİ yakalar (tek değere çökerse görselleştirme // anlamsızlaşır — senaryo 04_porozite.png'de "her yer kırmızı" görüntüsünün // kökü budur: alan sağlam ama %0.5 eşiği bu vaka için çok düşük, izo-yüzey // parçanın ~%58'ini kaplıyor). Eşik varsayılanı ayrı iş. FIKO_TEST(c3_porozite_alani_dejenere_degil) { DokumSimulasyonu sim(1); sim.alasim = alasimAlSi7Mg(); sim.kalip = kalipSilikaKum(); sim.ayar.dokumSicaklik = 700.0; sim.cozunurluk = 24; // senaryo ile aynı FIKO_KONTROL(sim.hesaplaGovde(geometri::kutu(40, 40, 40))); const auto& ny = sim.sonuc.niyama; const auto& tip = sim.kafes.tip; f64 nyMin = 1e30, nyMaks = 0, nyTop = 0; u32 n = 0; for (u32 i = 0; i < ny.size(); ++i) { if (tip[i] == HucreTip::Dis) continue; nyMin = std::min(nyMin, ny[i]); nyMaks = std::max(nyMaks, ny[i]); nyTop += ny[i]; ++n; } std::printf(" [OLCUM] Niyama metal hucre=%u min=%.4f ort=%.4f maks=%.4f\n", n, nyMin, n ? nyTop / n : 0.0, nyMaks); // Porozite: aynı Niyama'dan cekmePorozitesi ile (poroziteHesapla'nın çekirdeği) f64 pMin = 1e30, pMaks = 0, pTop = 0; u32 esikUstu = 0; for (u32 i = 0; i < ny.size(); ++i) { if (tip[i] == HucreTip::Dis) continue; const f64 p = cekmePorozitesi(ny[i], sim.alasim, 1.0); pMin = std::min(pMin, p); pMaks = std::max(pMaks, p); pTop += p; if (p > 0.5) ++esikUstu; // senaryo baloncuk eşiği } std::printf(" [OLCUM] Porozite%% min=%.3f ort=%.3f maks=%.3f | >%%0.5 hucre: %u/%u (%%%.1f)\n", pMin, n ? pTop / n : 0.0, pMaks, esikUstu, n, n ? 100.0 * esikUstu / n : 0.0); FIKO_KONTROL(n > 1000); // Niyama gerçekten değişmeli (düz alan = çözücü/örnekleme bozuk). FIKO_KONTROL(nyMaks > 100.0 * std::max(nyMin, 1e-6)); // Porozite dağılımı dejenere değil: hem temiz hem gözenekli hücre olmalı. FIKO_KONTROL(pMin < 0.1); // yüzeye yakın hücreler temiz FIKO_KONTROL(pMaks > 2.0); // merkez sıcak-nokta gözenekli FIKO_KONTROL(pMaks < 6.2); // tavan = 100·β/(1+β) = %6.10 aşılmamalı // Eşik üstü oran ne %0 ne %100 (ikisi de görselleştirmeyi anlamsızlaştırır). FIKO_KONTROL(esikUstu > 0 && esikUstu < n); } // ---- (d) Niyama monotonluk: çözünürlük↑ + soğuk kalıp → mantık tutarlı ------ // Aynı parça, çelik kalıp (yüksek h) → daha hızlı katılaşma (t↓), G↑ eğilimi. FIKO_TEST(d_niyama_ve_katilasma_tutarli) { const geometri::BrepGovde kup = geometri::kutu(30, 30, 30); FIKO_KONTROL(kup.gecerliMi()); auto sureVeNiyama = [&](const Kalip& kal, f64& ny) -> f64 { DokumSimulasyonu sim(2); sim.alasim = alasimAlSi7Mg(); sim.kalip = kal; sim.ayar.dokumSicaklik = 700.0; sim.cozunurluk = 14; sim.hesaplaGovde(kup); ny = sim.sonuc.niyamaMax; return sim.sonuc.toplamSure; }; f64 nyKum = 0, nyCelik = 0; const f64 tKum = sureVeNiyama(kalipSilikaKum(), nyKum); const f64 tCelik = sureVeNiyama(kalipCelikKalip(), nyCelik); FIKO_KONTROL(tKum > 0 && tCelik > 0); // Çelik kalıp (yüksek h + yüksek effusivite) → daha hızlı katılaşma. FIKO_KONTROL(tCelik < tKum); // Niyama alanı üretildi (post-process çalıştı). FIKO_KONTROL(nyKum > 0); } // ---- (e) Voxelize iç/dış: küp merkezi içeride, uzak nokta dışarıda ---------- FIKO_TEST(e_voxelize_ic_dis) { const geometri::BrepGovde kup = geometri::kutu(20, 20, 20); const UcgenCorba c = corbaBrepten(kup, 0.01); FIKO_KONTROL(c.ucgenSayi() > 0); FIKO_KONTROL(noktaIcinde(c, {0, 0, 0})); // merkez içeride FIKO_KONTROL(!noktaIcinde(c, {100, 0, 0})); // uzak dış FIKO_KONTROL(!noktaIcinde(c, {0, 100, 0})); VoxelKafes kafes; FIKO_KONTROL(kafes.kur(c.sinir, 20, 0.4)); kafesiSinifla(kafes, c); // İç hücre sayısı > 0 ve toplam hücrenin bir kısmı (kalıp payı → çoğu dış). u32 ic = 0; for (auto t : kafes.tip) if (t == HucreTip::Ic) ++ic; FIKO_KONTROL(ic > 0); } // ---- (f) Malzeme: fs monotonluğu + penetrasyon + entalpi tersinirliği ------ FIKO_TEST(f_malzeme_temel) { Alasim a = alasimAlSi7Mg(); FIKO_YAKIN(a.katiOran(a.tLikidus + 10), 0.0, 1e-9); // sıvı FIKO_YAKIN(a.katiOran(a.tSolidus - 10), 1.0, 1e-9); // katı // Ortada 0 0.0 && a.katiOran(mid) < 1.0); // entalpi ↔ sicaklik tersinir. const f64 T0 = 600.0; FIKO_YAKIN(sicaklik(a, entalpi(a, T0)), T0, 0.5); // Kalıp penetrasyon: kum < çelik (effusivite). FIKO_KONTROL(kalipSilikaKum().penetrasyon() < kalipCelikKalip().penetrasyon()); // Efektif h zamanla azalır (yarı-sonsuz emilim). Kalip k = kalipSilikaKum(); FIKO_KONTROL(k.etkinH(1.0) > k.etkinH(100.0)); } // ---- (f2) Scheil fs(T): BAĞIMSIZ ileri yasa ile tur-tur doğrulama --------- // Aynı formülü tekrar yazmak TAUTOLOJİ olurdu. Bunun yerine Scheil'in // KOMPOZİSYON türevinden gelen ileri yasası kullanılır: // C_L = C₀·(1−fs)^(k−1), T = Tm + m·C_L → T(fs) = Tm + (T_liq−Tm)(1−fs)^(k−1) // Kod bunun TERSİNİ hesaplar → fs → T → katiOran(T) turu fs'i geri vermeli. FIKO_TEST(f2_scheil_ileri_yasa) { Alasim a = alasimAlSi7Mg(); a.fsModel = FsModel::Scheil; const f64 kp = a.kPartisyon; // 0.13 FIKO_KONTROL(kp > 0.0 && kp < 1.0); // Tm lineer faz diyagramından (kodun tm türetiminden BAĞIMSIZ yazım). const f64 tm = (a.tLikidus - kp * a.tSolidus) / (1.0 - kp); FIKO_KONTROL(tm > a.tLikidus); // saf çözücü ergimesi likidusun üstünde auto tScheil = [&](f64 fs) { return tm + (a.tLikidus - tm) * std::pow(1.0 - fs, kp - 1.0); }; FIKO_YAKIN(tScheil(0.0), a.tLikidus, 1e-9); // fs=0 → tam likidus u32 denenen = 0; for (f64 fs : {0.1, 0.25, 0.5, 0.75, 0.9}) { const f64 T = tScheil(fs); if (T <= a.tSolidus) continue; // kısayol bölgesi (aşağıda ayrıca test) FIKO_YAKIN(a.katiOran(T), fs, 1e-6); ++denenen; } FIKO_KONTROL(denenen >= 3); // tur gerçekten koştu (boş geçmedi) // Solidus kesikliğini BELGELE (bug değil, bilinçli kısayol — malzeme.cpp notu): // Scheil solidusta fs=1'e ulaşmaz, artık ötektik sıvı kalır. const f64 fsUst = a.katiOran(a.tSolidus + 1e-6); FIKO_KONTROL(fsUst > 0.85 && fsUst < 0.95); // k=0.13 → ≈0.904 FIKO_YAKIN(a.katiOran(a.tSolidus), 1.0, 1e-12); // kısayol 1.0'a atlar // Monotonluk: T düştükçe fs artmalı (mushy boyunca). f64 onceki = -1.0; for (f64 T = a.tLikidus - 1e-6; T > a.tSolidus; T -= 1.0) { const f64 fs = a.katiOran(T); FIKO_KONTROL(fs >= onceki); onceki = fs; } } // ---- (g0) VOXEL MESH PARÇADAN BÜYÜK ÇIKMAMALI ------------------------------ // Fikri (07-15): "voxel küpler çok büyük gözüküyor". Senaryo PNG'lerinde piksel // ölçümü doğruladı: 03_katilasma_sicaklik gövdeden %4.6 büyük (410x472 vs // 392x452 px, merkezler aynı → saf ölçek şişmesi). Burada geometriden ölçülür. FIKO_TEST(g0_voxel_mesh_parcayi_asmamali) { const f64 kenar = 40.0; const geometri::BrepGovde kup = geometri::kutu(kenar, kenar, kenar); DokumSimulasyonu sim(1); sim.alasim = alasimAlSi7Mg(); sim.kalip = kalipSilikaKum(); sim.cozunurluk = 24; // senaryo ile aynı FIKO_KONTROL(sim.hesaplaGovde(kup)); // Önce ESKİ yol (kirpParcaya=false) hâlâ taşıyor mu — davranış korundu mu? VoxelGorselAyar eski; eski.alan = VoxelAlan::Sicaklik; const VoxelGorselSonuc vgEski = voxelMeshKur(sim.kafes, sim.sonuc, eski); FIKO_KONTROL(vgEski.gecerli); const f64 eskiGx = vgEski.mesh.sinir.azami.x - vgEski.mesh.sinir.asgari.x; FIKO_KONTROL(eskiGx > kenar + 0.5); // eski davranış AYNEN duruyor (42mm) VoxelGorselAyar ga; ga.alan = VoxelAlan::Sicaklik; ga.kirpParcaya = true; // ← fikocad'in geçirdiği yol (dokum.voxel_kirp) const VoxelGorselSonuc vg = voxelMeshKur(sim.kafes, sim.sonuc, ga); FIKO_KONTROL(vg.gecerli); FIKO_KONTROL(vg.hucreSayi > 0); // Mesh AABB (mesh birimi = gövde birimi mm). f32 mnX = 3e38f, mxX = -3e38f, mnY = 3e38f, mxY = -3e38f, mnZ = 3e38f, mxZ = -3e38f; for (const auto& v : vg.mesh.vertexler) { mnX = std::min(mnX, v.konum[0]); mxX = std::max(mxX, v.konum[0]); mnY = std::min(mnY, v.konum[1]); mxY = std::max(mxY, v.konum[1]); mnZ = std::min(mnZ, v.konum[2]); mxZ = std::max(mxZ, v.konum[2]); } const f64 gx = mxX - mnX, gy = mxY - mnY, gz = mxZ - mnZ; const f64 h = sim.kafes.h() * 1000.0; // m → mm std::printf(" [OLCUM] voxel mesh %.3f x %.3f x %.3f mm (parca %.1f, h=%.3f mm)" " tasma=%.3f mm = %.2f h oran=%.4f\n", gx, gy, gz, kenar, h, gx - kenar, h > 0 ? (gx - kenar) / h : 0.0, gx / kenar); // Voxel mesh parçayı AŞMAMALI. Hücre merkezine oturan TAM küp çizilirse en // dıştaki hücreler yüzeyi h/2 aşar → toplam kenar L+h olur. FIKO_KONTROL(gx <= kenar + 1e-3); FIKO_KONTROL(gy <= kenar + 1e-3); FIKO_KONTROL(gz <= kenar + 1e-3); } // ---- (g0b) ÖLÇÜM: Marching-Cubes sıvı yüzeyi parçayı ne kadar aşıyor? ----- // Fikri (07-15): "voxellerde taşıyor buda taşıyor... köşeleri de doldurmuyor". // g0 voxel-KÜP yolunu (kupEkle) çözdü; bu MC yolu (siviYuzeyKur) AYRI. // parcaSiniriKirp ZATEN varsayılan true → kırpma açık, yine de taşıyor. Neden: // psi = parcaSdfKur(kafes) sıfır seviyesini en dıştaki Ic merkezi ile ilk Dis // merkezi ARASINA (±0.5 hücre) koyuyor → GERÇEK BREP yüzeyi değil, VOXELLEŞMİŞ // parçanın sınırı. Köşe yuvarlaması da chamfer345'in çapraz komşudan ölçmesi. // Alan "her yer sıvı" → Φ=max(φ,ψ)=ψ → çıkan yüzey SAF parça-sınırı SDF'si. FIKO_TEST(g0b_probe_mc_sivi_yuzey_tasmasi) { const f64 kenar = 40.0; DokumSimulasyonu sim(1); sim.alasim = alasimAlSi7Mg(); sim.kalip = kalipSilikaKum(); sim.cozunurluk = 24; FIKO_KONTROL(sim.hesaplaGovde(geometri::kutu(kenar, kenar, kenar))); const u32 N = sim.kafes.hucreSayi(); std::vector hepsiSivi(N, -1.0); // izoAltiIc=true → v kenar + 1.0); // eski yol HALA tasiyor → davranis korundu } const SiviYuzeySonuc sy = siviYuzeyKur(sim.kafes, hepsiSivi, 0.0f, sa); FIKO_KONTROL(sy.gecerli); FIKO_KONTROL(sy.ucgenSayi > 0); f32 mn[3] = {3e38f, 3e38f, 3e38f}, mx[3] = {-3e38f, -3e38f, -3e38f}; for (const auto& v : sy.mesh.vertexler) for (int d = 0; d < 3; ++d) { mn[d] = std::min(mn[d], v.konum[d]); mx[d] = std::max(mx[d], v.konum[d]); } const f64 gx = mx[0] - mn[0]; const f64 h = sim.kafes.h() * 1000.0; // m → mm std::printf(" [OLCUM] MC sivi yuzey X = %.3f mm (parca %.1f, h=%.3f)" " tasma=%.3f mm = %.2f h oran=%.4f\n", gx, kenar, h, gx - kenar, h > 0 ? (gx - kenar) / h : 0.0, gx / kenar); FIKO_KONTROL(gx > 0); // KÖŞE ÖLÇÜMÜ ("köşeleri doldurmuyor"): kusursuz küpte köşe, merkezden // (kenar/2)·√3 = 34.641mm uzakta. MC hücre kenarında DOĞRUSAL interpolasyon // yapar; keskin 90° köşe hücrenin İÇİNE düşer → MC oradan ancak düzlemsel // kesit geçirebilir. Bu MC'nin DOĞASI: gerçek SDF ile bile köşe ~1 hücre // pahlanır, sıfırlanmaz. Çözünürlük artırmak oransal küçültür. olc(sy, "YENI(exact)"); // Köşe keskinliği ÇÖZÜNÜRLÜKLE nasıl değişiyor? ("sivri köşeleri boş kalıyor") // MC köşeyi hücre İÇİNE düşürüp düzlemsel kesiyor → kayıp ∝ h. Tek kaldıraç // çözünürlük; keskinlik 1.0'a yaklaşır ama ASLA ulaşmaz. for (u32 res : {40u, 60u}) { DokumSimulasyonu s2(1); s2.alasim = alasimAlSi7Mg(); s2.kalip = kalipSilikaKum(); s2.cozunurluk = res; if (!s2.hesaplaGovde(geometri::kutu(kenar, kenar, kenar))) continue; std::vector sivi2(s2.kafes.hucreSayi(), -1.0); const SiviYuzeySonuc y2 = siviYuzeyKur(s2.kafes, sivi2, 0.0f, sa); if (!y2.gecerli) continue; f32 a[3] = {3e38f, 3e38f, 3e38f}, b[3] = {-3e38f, -3e38f, -3e38f}; for (const auto& v : y2.mesh.vertexler) for (int d = 0; d < 3; ++d) { a[d] = std::min(a[d], v.konum[d]); b[d] = std::max(b[d], v.konum[d]); } const f64 g = b[0] - a[0]; const Vec3f m{0.5f * (a[0] + b[0]), 0.5f * (a[1] + b[1]), 0.5f * (a[2] + b[2])}; f64 uz = 0; for (const auto& v : y2.mesh.vertexler) { const f64 dx = v.konum[0] - m.x, dy = v.konum[1] - m.y, dz = v.konum[2] - m.z; uz = std::max(uz, std::sqrt(dx * dx + dy * dy + dz * dz)); } std::printf(" [OLCUM] res=%2u h=%.3fmm genislik=%.3f (tasma %+.3f)" " keskinlik=%.4f\n", res, s2.kafes.h() * 1000.0, g, g - kenar, uz / (0.5 * g * std::sqrt(3.0))); FIKO_KONTROL(g <= kenar + 1e-3); // her çözünürlükte taşma YOK } // ÖLÇÜLDÜ 2026-07-15 (40mm küp, h=3mm, res=24): // ESKI(chamfer): genislik 42.000 (+0.67h) kose 34.139 keskinlik 0.9386 // YENI(exact) : genislik 40.000 ( 0.00h) kose 33.550 keskinlik 0.9685 // → Taşma BİTTİ; köşe de kendi boyutuna göre İYİLEŞTİ (0.939 → 0.969). // Kalan ~%3 köşe yumuşaması MC'nin DOĞASI (hücre kenarında doğrusal interp; // keskin köşe hücre içine düşer) — çözünürlükle oransal küçülür, sıfırlanmaz. FIKO_KONTROL(gx <= kenar + 1e-3); // ASIL İDDİA: parçayı AŞMIYOR } // ---- (g0c) ÖLÇÜM: KUTU-OLMAYAN parçada voxel taşması (AABB kırpması yeter mi?) // Fikri (07-15, yakınlaştırılmış ekran): "voxeller büyük". Parça L şeklinde. // kirpParcaya AABB tabanlı → L'nin İÇ yüzeyleri bounding box'ın İÇİNDE kaldığı // için orada kırpma HİÇBİR ŞEY yapmaz. Kutuda 40.000mm çıkması yanılttı. // Ölçüt: parcaSdf (gerçek yüzeye işaretli mesafe, hücre birimi). Merkezi içeride // ama |sdf| < 0.5 hücre olan her Ic hücrenin TAM küpü yüzeyi (0.5−|sdf|) kadar // aşar. Bu testin işi o hücreleri saymak — AABB'nin yetmediğini SAYIYLA göstermek. FIKO_TEST(g0c_probe_kutu_olmayan_parcada_voxel_tasmasi) { // L profili: 40mm küpten 20x20 köşe blok çıkar (resimdeki parça). const geometri::BrepGovde tam = geometri::kutu(40, 40, 40); geometri::BrepGovde kesici = geometri::kutu(20, 20, 40); const geometri::BrepGovde L = geometri::cikarma(tam, kesici); FIKO_KONTROL(L.gecerliMi()); for (int sekil = 0; sekil < 2; ++sekil) { DokumSimulasyonu sim(1); sim.alasim = alasimAlSi7Mg(); sim.kalip = kalipSilikaKum(); sim.cozunurluk = 24; const bool ok = sekil == 0 ? sim.hesaplaGovde(geometri::kutu(40, 40, 40)) : sim.hesaplaGovde(L); if (!ok) { FIKO_KONTROL(sekil == 1); continue; } // L kurulamazsa atla FIKO_KONTROL(sim.kafes.parcaSdf.size() == sim.kafes.hucreSayi()); u32 icHucre = 0, tasan = 0; f64 enKotuTasma = 0; // hücre birimi for (u32 i = 0; i < sim.kafes.hucreSayi(); ++i) { if (sim.kafes.tip[i] != HucreTip::Ic) continue; ++icHucre; const f64 d = std::fabs((f64)sim.kafes.parcaSdf[i]); if (d < 0.5) { ++tasan; enKotuTasma = std::max(enKotuTasma, 0.5 - d); } } const f64 h = sim.kafes.h() * 1000.0; std::printf(" [OLCUM] %-4s ic hucre=%4u tam-kupu TASAN=%4u (%%%.1f)" " en kotu tasma=%.3f h = %.3f mm\n", sekil == 0 ? "KUTU" : "L", icHucre, tasan, icHucre ? 100.0 * tasan / icHucre : 0.0, enKotuTasma, enKotuTasma * h); FIKO_KONTROL(icHucre > 0); // ASIL ÖLÇÜM: üretilen voxel MESH'inin vertex'leri parçayı aşıyor mu? // Ölçüt = parcaSdf(vertex) > 0 → o vertex gerçek yüzeyin DIŞINDA. VoxelGorselAyar ga; ga.alan = VoxelAlan::Sicaklik; ga.kirpParcaya = true; // PERF: bu mesh scrubber/eşik sürüklemede yeniden kurulur (dinamikMesh) → // süre KULLANICI HİSSEDER. Köşe itmesi köşe başına exact noktaIcinde çağırıyor. const auto t0 = std::chrono::steady_clock::now(); const VoxelGorselSonuc vg = voxelMeshKur(sim.kafes, sim.sonuc, ga); const f64 kurMs = std::chrono::duration( std::chrono::steady_clock::now() - t0).count(); std::printf(" [OLCUM] %-4s voxelMeshKur SURE = %.1f ms (ucgen=%zu)\n", sekil == 0 ? "KUTU" : "L", kurMs, sim.kafes.parcaUcgen.size() / 3); FIKO_KONTROL(vg.gecerli); // ⚠️ ÖLÇÜM ARACI SEÇİMİ KRİTİK: parcaSdfOrnek (trilineer SDF) burada KULLANILAMAZ // — köşede yanılan alan odur; küp köşesi GERÇEK parça köşesine tam otursa bile // trilineer alan onu "dışarıda 0.415h" sayar (yaşandı: keskinlik 1.0'a çıktı, // taşma testi kırmızıya döndü — geometri değil ARAÇ hatalıydı). // Doğrusu: çorbaya EXACT içeride/dışarıda + exact mesafe. UcgenCorba corba; corba.vertler = sim.kafes.parcaUcgen; for (const Vec3d& p : corba.vertler) corba.sinir.genislet(p); FIKO_KONTROL(corba.ucgenSayi() > 0); f64 enDis = 0; // hücre birimi u32 disVertex = 0; for (const auto& v : vg.mesh.vertexler) { const Vec3d p{v.konum[0], v.konum[1], v.konum[2]}; if (noktaIcinde(corba, p)) continue; // içeride → taşma yok f64 en = 1e300; for (u32 t = 0; t < corba.ucgenSayi(); ++t) en = std::min(en, noktaUcgenMesafe(p, corba.vertler[3 * t], corba.vertler[3 * t + 1], corba.vertler[3 * t + 2])); const f64 d = en / h; // mm → hücre birimi if (d > 1e-3) { ++disVertex; enDis = std::max(enDis, d); } } // KÖŞE KESKİNLİĞİ: köşe itmesi taşmayı bitirdi ama köşeyi PAHLIYOR mu? // (Fikri: "voxeller duvarlara oturdu ama o da sıvı gibi köşeleri pahlı".) // Kutuda ideal köşe merkezden (kenar/2)·√3 = 34.641mm. if (sekil == 0) { f32 a2[3] = {3e38f, 3e38f, 3e38f}, b2[3] = {-3e38f, -3e38f, -3e38f}; for (const auto& v : vg.mesh.vertexler) for (int d = 0; d < 3; ++d) { a2[d] = std::min(a2[d], v.konum[d]); b2[d] = std::max(b2[d], v.konum[d]); } const Vec3f mrk{0.5f * (a2[0] + b2[0]), 0.5f * (a2[1] + b2[1]), 0.5f * (a2[2] + b2[2])}; f64 uz = 0; for (const auto& v : vg.mesh.vertexler) { const f64 dx = v.konum[0] - mrk.x, dy = v.konum[1] - mrk.y, dz = v.konum[2] - mrk.z; uz = std::max(uz, std::sqrt(dx * dx + dy * dy + dz * dz)); } const f64 g = b2[0] - a2[0]; std::printf(" [OLCUM] KUTU voxel KOSE: en uzak=%.3f ideal=%.3f" " keskinlik=%.4f (1.0=sivri)\n", uz, 0.5 * g * std::sqrt(3.0), uz / (0.5 * g * std::sqrt(3.0))); } std::printf(" [OLCUM] %-4s mesh vertex=%6zu parca DISINDA=%5u" " en dis=%.3f h = %.3f mm\n", sekil == 0 ? "KUTU" : "L", vg.mesh.vertexler.size(), disVertex, enDis, enDis * h); // Küp köşeleri yüzeye itiliyor → hiçbir vertex parçanın belirgin dışında olmamalı. FIKO_KONTROL(enDis < 0.10); // <%10 hücre = sayısal artık payı // dolumMeshKur DA test edilmeli: UI'daki "Voxel küpler (hızlı)" dolum görünümü // BU yoldan çizilir ve döngü değişkeni `hc` olduğu için toplu düzenlemelerde // ATLANMIŞTI (kırpmasız taşıyordu, ekranda görüldü). Üç yolun ÜÇÜ de kapsanmalı. DolumSonuc dl; dl.hazir = true; dl.varisZamani.assign(sim.kafes.hucreSayi(), -1.0); for (u32 i = 0; i < sim.kafes.hucreSayi(); ++i) if (sim.kafes.tip[i] == HucreTip::Ic) dl.varisZamani[i] = 0.0; // hepsi dolu const VoxelGorselSonuc vd = dolumMeshKur(sim.kafes, dl, ga, 1.0); FIKO_KONTROL(vd.gecerli); f64 enDisD = 0; for (const auto& v : vd.mesh.vertexler) { const Vec3d p{v.konum[0], v.konum[1], v.konum[2]}; if (noktaIcinde(corba, p)) continue; f64 en = 1e300; for (u32 t = 0; t < corba.ucgenSayi(); ++t) en = std::min(en, noktaUcgenMesafe(p, corba.vertler[3 * t], corba.vertler[3 * t + 1], corba.vertler[3 * t + 2])); enDisD = std::max(enDisD, en / h); } std::printf(" [OLCUM] %-4s dolumMeshKur vertex=%6zu en dis=%.3f h = %.3f mm\n", sekil == 0 ? "KUTU" : "L", vd.mesh.vertexler.size(), enDisD, enDisD * h); FIKO_KONTROL(enDisD < 0.10); } } // ---- (g0d) PERF: köşe itmesi üçgen sayısıyla nasıl ölçekleniyor? ----------- // Köşe itmesi köşe-başına EXACT noktaIcindeHam + üçgen taraması yapıyor; ikisi de // O(üçgen). Bu mesh scrubber/eşik sürüklemede yeniden kurulur → kullanıcı hisseder. // Kutu (12 üçgen) 5.1ms, L (32 üçgen) 15.2ms ölçüldü → ~doğrusal. Küre gerçek // parçaya yakın üçgen sayısı verir; kabul edilemez bir yere gidiyor mu, ÖLÇ. FIKO_TEST(g0d_probe_kose_itme_perf) { DokumSimulasyonu sim(1); sim.alasim = alasimAlSi7Mg(); sim.kalip = kalipSilikaKum(); sim.cozunurluk = 24; if (!sim.hesaplaGovde(geometri::kure(20.0))) { FIKO_KONTROL(true); return; } VoxelGorselAyar ga; ga.alan = VoxelAlan::Sicaklik; ga.kirpParcaya = true; const auto t0 = std::chrono::steady_clock::now(); const VoxelGorselSonuc vg = voxelMeshKur(sim.kafes, sim.sonuc, ga); const f64 ms = std::chrono::duration( std::chrono::steady_clock::now() - t0).count(); const size_t nUc = sim.kafes.parcaUcgen.size() / 3; std::printf(" [OLCUM] KURE ucgen=%zu voxelMeshKur SURE = %.1f ms" " (%.3f ms/ucgen)\n", nUc, ms, nUc ? ms / (f64)nUc : 0.0); FIKO_KONTROL(vg.gecerli); } // ---- (g) Renk-rampa: uçlar/orta beklenen renk; clamp güvenli -------------- FIKO_TEST(g_voxel_rampa_renk) { // Turbo: 0 koyu-mavi bölge (b>r), 1 koyu-kırmızı (r>b). Clamp dışı güvenli. const Vec3f t0 = voxelRampaRenk(VoxelRampa::Turbo, 0.0f); const Vec3f t1 = voxelRampaRenk(VoxelRampa::Turbo, 1.0f); FIKO_KONTROL(t0.z > t0.x); // düşük → mavi baskın FIKO_KONTROL(t1.x > t1.z); // yüksek → kırmızı baskın const Vec3f alt = voxelRampaRenk(VoxelRampa::Turbo, -5.0f); // clamp<0 const Vec3f ust = voxelRampaRenk(VoxelRampa::Turbo, 5.0f); // clamp>1 FIKO_YAKIN(alt.x, t0.x, 1e-5); FIKO_YAKIN(ust.x, t1.x, 1e-5); // Jet orta yeşil-ağırlıklı (g > r, g > b civarı). const Vec3f j = voxelRampaRenk(VoxelRampa::Jet, 0.5f); FIKO_KONTROL(j.y > 0.9f); } // ---- (g2) İnferno: uçlar + algısal-düzgün monoton parlaklık + clamp -------- FIKO_TEST(g2_inferno_rampa) { // Uçlar: 0 → siyaha yakın; 1 → parlak sarı-beyaz (akkor metal). const Vec3f i0 = voxelRampaRenk(VoxelRampa::Inferno, 0.0f); const Vec3f i1 = voxelRampaRenk(VoxelRampa::Inferno, 1.0f); FIKO_KONTROL(i0.x < 0.1f && i0.y < 0.1f && i0.z < 0.15f); FIKO_KONTROL(i1.x > 0.9f && i1.y > 0.9f); // Clamp güvenli (aralık dışı → uç renk). const Vec3f alt = voxelRampaRenk(VoxelRampa::Inferno, -2.0f); const Vec3f ust = voxelRampaRenk(VoxelRampa::Inferno, 2.0f); FIKO_YAKIN(alt.x, i0.x, 1e-5); FIKO_YAKIN(ust.y, i1.y, 1e-5); // Algısal düzgünlük vekili: rölatif parlaklık (Rec.709) t boyunca MONOTON // artar (jet/turbo'nun aksine yapay bant yok) — 64 örnek, küçük tolerans. f64 onceki = -1.0; for (int i = 0; i <= 64; ++i) { const Vec3f c = voxelRampaRenk(VoxelRampa::Inferno, (f32)i / 64.0f); const f64 L = 0.2126 * c.x + 0.7152 * c.y + 0.0722 * c.z; FIKO_KONTROL(L >= onceki - 1e-3); onceki = L; } } // ---- (i) Methoding: analitik modül (küp M=a/6, silindir kapalı-form) ------- FIKO_TEST(i_methoding_analitik_modul) { // Küp modülü M = a/6 (tüm 6 yüz açık) — analitik yardımcı. FIKO_YAKIN(kupModul(60.0), 10.0, 1e-9); FIKO_YAKIN(kupModul(30.0), 5.0, 1e-9); // Silindir modülü M = V/A, A = yan + üst (alt beslenen yüz sayılmaz). // d=60, h=90 → r=30, V=π·900·90=254469, A=π·60·90+π·900=16964+2827=19792 // M = V/A ≈ 12.857 mm (kapalı-form kontrol). const f64 M = silindirModul(60.0, 90.0); const f64 r = 30.0, V = 3.14159265358979 * r * r * 90.0; const f64 A = 3.14159265358979 * 60.0 * 90.0 + 3.14159265358979 * r * r; FIKO_YAKIN(M, V / A, 1e-6); // Silindir hacmi analitik. FIKO_YAKIN(silindirHacim(60.0, 90.0), V, 1e-3); } // ---- (j) Methoding: besleyici boyut kuralı M_r ≥ 1.2·M_parça (Chvorinov) ---- // Kapalı-form d = M_r·(4k+1)/k, h=k·d ⇒ silindirModul(d,h) tam M_r vermeli. FIKO_TEST(j_besleyici_modul_kurali) { ParcaModul parca; parca.gecerli = true; parca.modul = 10.0; // parça modülü 10 mm parca.hacim = 1e6; // 1000 cm³ parca.kesitKalinlik = 2.0 * 10.0; // T ≈ 2M const f64 carpan = 1.2; const BesleyiciOneri o = besleyiciOner(parca, {0, 0, 0}, carpan, 1.5); FIKO_KONTROL(o.gecerli); // Hedef modül = 1.2 × parça = 12 mm. FIKO_YAKIN(o.hedefModul, 12.0, 1e-9); // Boyutlandırılan silindirin GERÇEK modülü hedefi karşılamalı (≥, kapalı-form eşit). const f64 gercekModul = silindirModul(o.capMm, o.yukseklikMm); FIKO_YAKIN(gercekModul, o.hedefModul, 1e-6); FIKO_KONTROL(o.modulYeterli); FIKO_KONTROL(o.modul >= 1.2 * parca.modul - 1e-6); // Yükseklik = 1.5×çap oranı korunmalı. FIKO_YAKIN(o.yukseklikMm, 1.5 * o.capMm, 1e-9); // 4.5T besleme mesafesi = 4.5 × kesit kalınlık. FIKO_YAKIN(o.beslemeMesafe, 4.5 * parca.kesitKalinlik, 1e-9); // Daha yüksek çarpan → daha büyük besleyici (modül monoton). const BesleyiciOneri o2 = besleyiciOner(parca, {0, 0, 0}, 1.5, 1.5); FIKO_KONTROL(o2.capMm > o.capMm); // Verim: besleyici eklendikçe verim düşer, 0 0.0 && y < 100.0); } // ---- (k) Methoding: voxel parça modülü küp analitiğe yakınsar -------------- // Küp gövde → parcaModulu(kafes) ≈ a/6 (kaba gridde merdiven hatası ±%20 pay). FIKO_TEST(k_voxel_parca_modul) { DokumSimulasyonu sim(11); sim.alasim = alasimAlSi7Mg(); sim.kalip = kalipSilikaKum(); sim.cozunurluk = 24; const geometri::BrepGovde kup = geometri::kutu(60, 60, 60); FIKO_KONTROL(sim.hesaplaGovde(kup)); const ParcaModul pm = parcaModulu(sim.kafes); FIKO_KONTROL(pm.gecerli); FIKO_KONTROL(pm.hacim > 0 && pm.yuzeyAlan > 0); // Analitik küp modülü 60/6 = 10 mm. Kaba voxel + pay katmanı → gevşek tolerans. const f64 analitik = kupModul(60.0); const f64 hata = std::abs(pm.modul - analitik) / analitik; FIKO_KONTROL(hata < 0.25); // < %25 (grid çözünürlüğü kaba) } // ---- (h) voxelMeshKur: küp mesh üretir, geçersiz/kapalı → boş ------------- FIKO_TEST(h_voxel_mesh_kur) { DokumSimulasyonu sim(9); sim.alasim = alasimAlSi7Mg(); sim.kalip = kalipSilikaKum(); sim.ayar.dokumSicaklik = 700.0; sim.cozunurluk = 12; const geometri::BrepGovde kup = geometri::kutu(30, 30, 30); FIKO_KONTROL(sim.hesaplaGovde(kup)); FIKO_KONTROL(sim.sonuc.hazir); // Kapalı alan → boş (viz kapalı). VoxelGorselAyar kapali; kapali.alan = VoxelAlan::Kapali; FIKO_KONTROL(!voxelMeshKur(sim.kafes, sim.sonuc, kapali).gecerli); // Katılaşma süresi → geçerli blok mesh. Yüz-ayıklama: iç yüzler üretilmez → // toplam ≤ 24 vertex/hücre, quad bütünlüğü (4 vertex ↔ 6 indeks) korunur. VoxelGorselAyar ay; ay.alan = VoxelAlan::KatilasmaSuresi; ay.rampa = VoxelRampa::Turbo; const VoxelGorselSonuc vg = voxelMeshKur(sim.kafes, sim.sonuc, ay); FIKO_KONTROL(vg.gecerli); FIKO_KONTROL(vg.hucreSayi > 0); FIKO_KONTROL(!vg.mesh.vertexler.empty()); FIKO_KONTROL(vg.mesh.vertexler.size() < (size_t)vg.hucreSayi * 24); // iç yüzler ayıklandı FIKO_KONTROL(vg.mesh.vertexler.size() % 4 == 0); FIKO_KONTROL(vg.mesh.indeksler.size() == vg.mesh.vertexler.size() / 4 * 6); FIKO_KONTROL(vg.mesh.sinir.gecerliMi()); FIKO_KONTROL(vg.alanMax >= vg.alanMin); // Yalnız sınır → dolgudan az (veya eşit) hücre. VoxelGorselAyar snr = ay; snr.yalnizSinir = true; const VoxelGorselSonuc vs = voxelMeshKur(sim.kafes, sim.sonuc, snr); FIKO_KONTROL(vs.hucreSayi <= vg.hucreSayi); // Geçersiz kafes → boş. VoxelKafes bos; FIKO_KONTROL(!voxelMeshKur(bos, sim.sonuc, ay).gecerli); } // ============================================================================ // DOLUM (kalıp doldurma D3) — kütle korunumu + varış monotonluğu + analitik // ============================================================================ // Sadece kafesi kur + sınıfla (katılaşmayı ÇÖZME — dolum testi hızlı kalsın). static void kafesKurSinifla(VoxelKafes& kafes, const geometri::BrepGovde& g, u32 res) { const UcgenCorba c = corbaBrepten(g, 0.01); kafes.kur(c.sinir, res, 0.4, 0.001); kafesiSinifla(kafes, c); } // Küçük kafesli döküm sim kur + kafesi sınıfla (dolum testleri için ortak). // Katılaşma ÇÖZÜLMEZ (yalnız kafes); dolum testleri saf dolumu ölçer (hızlı). static DokumSimulasyonu dolumKup(u32 res, f64 kenar) { DokumSimulasyonu sim(100); sim.alasim = alasimAlSi7Mg(); sim.kalip = kalipSilikaKum(); sim.cozunurluk = res; const geometri::BrepGovde kup = geometri::kutu(kenar, kenar, kenar); kafesKurSinifla(sim.kafes, kup, res); return sim; } // ---- (l) Dolum kütle korunumu: TÜM iç metal hücre dolar (misrun yok) ------- FIKO_TEST(l_dolum_kutle_korunumu) { DokumSimulasyonu sim = dolumKup(14, 40.0); FIKO_KONTROL(sim.kafes.gecerliMi()); FIKO_KONTROL(sim.dolumCoz()); FIKO_KONTROL(sim.dolum.hazir); // Bağlı tek gövde → tüm metal hücreler girişe erişir (eksik dolum yok). FIKO_KONTROL(!sim.dolum.misrun); FIKO_KONTROL(sim.dolum.dolanHucre == sim.dolum.toplamMetal); FIKO_KONTROL(sim.dolum.toplamMetal > 0); // Dolum sırası tüm dolan hücreleri kapsar. FIKO_KONTROL(sim.dolum.sira.size() == (size_t)sim.dolum.dolanHucre); // Dolan hacim > 0 ve sonlu. FIKO_KONTROL(sim.dolum.dolanHacim > 0); } // ---- (l2) Giriş kütlesi kaviteden az → kalıp FİZİK gereği dolamaz (misrun) - // "Büyük kalıbı bir türlü dolduramadım" kök nedeni: kurulum girişli dolum // KÜTLE KORUNUMLU (debi × pencere kadar hacim teslim edilir) ama panel giriş // varsayılanı 5 kg — 400mm küp AlSi7Mg ≈ 170 kg ister. Denetim yoktu; büyük // kalıp sessizce misrun oluyordu. kafesMetalHacimM3 + panel uyarısı bunu açar. FIKO_TEST(l2_dolum_giris_kutle_yetersiz_misrun) { DokumSimulasyonu sim = dolumKup(14, 400.0); // 400mm küp = büyük kalıp FIKO_KONTROL(sim.kafes.gecerliMi()); const f64 kaviteKg = kafesMetalHacimM3(sim.kafes) * sim.alasim.yogunluk; FIKO_KONTROL(kaviteKg > 100.0); // gerçekten büyük (≈170 kg) dokum::DokumGirisi g; // panel varsayılanı: 5 kg — kaviteyi karşılamıyor const auto& sn = sim.kafes.sinir(); g.konum = {(f32)(0.5 * (sn.asgari.x + sn.azami.x)), (f32)(0.5 * (sn.asgari.y + sn.azami.y)), (f32)sn.azami.z}; g.yon = {0, 0, -1}; g.kutleKg = 5.0; g.sureS = 5.0; sim.kurulum.girisler.push_back(g); FIKO_KONTROL(sim.dolumCoz()); FIKO_KONTROL(sim.dolum.misrun); FIKO_KONTROL(sim.dolum.dolanHucre < sim.dolum.toplamMetal); // Teslim edilen hacim kadar hücre dolar (kütle korunumu; ±kuantalama payı). const f64 beklenen = (g.kutleKg / sim.alasim.yogunluk) / sim.kafes.hucreHacim(); FIKO_KONTROL((f64)sim.dolum.dolanHucre <= beklenen + 1.5); // Kütle kaviteyi karşılayınca (×1.05 pay) AYNI kalıp TAM dolar. sim.kurulum.girisler[0].kutleKg = kaviteKg * 1.05; FIKO_KONTROL(sim.dolumCoz()); FIKO_KONTROL(!sim.dolum.misrun); FIKO_KONTROL(sim.dolum.dolanHucre == sim.dolum.toplamMetal); } // ---- (l3) Dolmayan bölge raporu: NEREsi dolmadı + hava hapsi ayrımı -------- static VoxelKafes havaTestKafes(); // ileri bildirim (tanım hava testlerinde) FIKO_TEST(l3_dolmayan_bolge_raporu) { // Kütle yetersiz büyük kalıp (l2 kalıbı) → dolmayan bölge(ler) raporlanır. DokumSimulasyonu sim = dolumKup(10, 400.0); dokum::DokumGirisi g; const auto& sn = sim.kafes.sinir(); g.konum = {(f32)(0.5 * (sn.asgari.x + sn.azami.x)), (f32)(0.5 * (sn.asgari.y + sn.azami.y)), (f32)sn.azami.z}; g.yon = {0, 0, -1}; g.kutleKg = 5.0; g.sureS = 5.0; sim.kurulum.girisler.push_back(g); FIKO_KONTROL(sim.dolumCoz() && sim.dolum.misrun); const auto bolgeler = dolmayanBolgeler(sim.kafes, sim.dolum); FIKO_KONTROL(!bolgeler.empty()); u32 toplamHucre = 0; for (const auto& b : bolgeler) { FIKO_KONTROL(b.hucreSayi > 0 && b.hacimMm3 > 0); FIKO_KONTROL(!b.havaHapsi); // kütle yetersizliği — hava hapsi değil toplamHucre += b.hucreSayi; } FIKO_KONTROL(toplamHucre == sim.dolum.toplamMetal - sim.dolum.dolanHucre); // Büyükten küçüğe sıralı. for (size_t i = 1; i < bolgeler.size(); ++i) FIKO_KONTROL(bolgeler[i - 1].hucreSayi >= bolgeler[i].hucreSayi); // Kokil hava hapsi (v kalıbı) → bölge havaHapsi=true işaretli. const VoxelKafes hk = havaTestKafes(); DolumAyar ayar; const std::vector kaynak = {{hk.indeks(2, 4, 0), 0.0, 0.0, 0.0}}; const DolumSonuc dKokil = dolumCoz(hk, alasimAlSi7Mg(), kalipCelikKalip(), ayar, kaynak, {0, 0, -1}); const auto hapisBolge = dolmayanBolgeler(hk, dKokil); FIKO_KONTROL(!hapisBolge.empty()); bool hapisVar = false; for (const auto& b : hapisBolge) if (b.havaHapsi) hapisVar = true; FIKO_KONTROL(hapisVar); } // ---- (m) Dolum varış monotonluğu: girişten uzağa varış zamanı artar ------- // En üstteki (giriş) hücre varış=0; alt hücreler >0; giriş sütun altında // serbest-düşme → aşağı hızlı ama yine de girişten uzak = zaman artar. FIKO_TEST(m_dolum_varis_monoton) { DokumSimulasyonu sim = dolumKup(16, 40.0); FIKO_KONTROL(sim.dolumCoz()); const auto& d = sim.dolum; // Giriş hücresi = en üst metal; varış zamanı 0. const u32 giris = enUstMetalHucre(sim.kafes); FIKO_KONTROL(giris != (u32)-1); FIKO_YAKIN(d.varisZamani[giris], 0.0, 1e-9); // Sıra: ilk = giriş (en erken), son = en geç. Zamanlar artan. FIKO_KONTROL(d.sira.size() > 2); FIKO_KONTROL(d.sira.front() == giris); f64 onceki = -1.0; for (u32 idx : d.sira) { FIKO_KONTROL(d.varisZamani[idx] >= onceki - 1e-9); // monoton artan onceki = d.varisZamani[idx]; } // Son hücrenin varışı = toplam süre. FIKO_YAKIN(d.varisZamani[d.sira.back()], d.toplamSure, 1e-9); FIKO_KONTROL(d.toplamSure > 0); } // ---- (n) Dolum analitik: düşey kolon dolum süresi ~ serbest-düşme --------- // İnce yüksek prizma (kolon): giriş üstte, dolum aşağı serbest-düşme. Toplam // süre kolon yüksekliği / etkin hız mertebesinde (h·N / (v0+düşme)). Kaba // kontrol: süre yükseklikle artar (uzun kolon > kısa kolon). FIKO_TEST(n_dolum_kolon_yukseklik) { auto kolonSure = [](f64 yukseklik) -> f64 { DokumSimulasyonu sim(101); sim.alasim = alasimAlSi7Mg(); sim.kalip = kalipSilikaKum(); sim.cozunurluk = 20; // Dar taban, değişken yükseklik (kolon). Katılaşma çözülmez (kafes yeter). const geometri::BrepGovde kolon = geometri::kutu(10, 10, yukseklik); kafesKurSinifla(sim.kafes, kolon, 20); sim.dolumCoz(); return sim.dolum.hazir ? sim.dolum.toplamSure : -1.0; }; const f64 tKisa = kolonSure(20.0); const f64 tUzun = kolonSure(80.0); FIKO_KONTROL(tKisa > 0 && tUzun > 0); // Yüksek kolon → daha uzun dolum (serbest-düşme yol uzunluğu). FIKO_KONTROL(tUzun > tKisa); } // ---- (o) Dolum→katılaşma köprüsü: köprülü çözüm çalışır + geç-dolan soğur -- FIKO_TEST(o_dolum_katilasma_kopru) { DokumSimulasyonu sim(102); sim.alasim = alasimAlSi7Mg(); sim.kalip = kalipSilikaKum(); sim.cozunurluk = 10; // küçük grid (köprü zinciri katılaşma çözer — hızlı) sim.dolumAyar.soguma = 60.0; // belirgin soğuma (köprü etkisi görünür) const geometri::BrepGovde kup = geometri::kutu(40, 40, 40); FIKO_KONTROL(sim.hesaplaGovdeDolumlu(kup)); FIKO_KONTROL(sim.dolum.hazir && sim.sonuc.hazir); // Köprü: en geç dolan hücrenin varış sıcaklığı, giriş sıcaklığından düşük. const u32 sonHucre = sim.dolum.sira.back(); const u32 ilkHucre = sim.dolum.sira.front(); FIKO_KONTROL(sim.dolum.varisT[sonHucre] < sim.dolum.varisT[ilkHucre] + 1e-9); // Katılaşma sonucu hâlâ geçerli (köprü çözümü bozmadı). FIKO_KONTROL(sim.sonuc.toplamSure > 0); } // ---- (p) Dolum mesh + animasyon kesim: kesim ilerledikçe hücre artar ------ FIKO_TEST(p_dolum_mesh_animasyon) { DokumSimulasyonu sim = dolumKup(14, 40.0); FIKO_KONTROL(sim.dolumCoz()); VoxelGorselAyar ay; ay.alan = VoxelAlan::KatilasmaSuresi; // dolumMeshKur alan bağımsız (varış kullanır) ay.rampa = VoxelRampa::Turbo; // Tam dolum (kesim<0) → GÖRSEL sözleşme: yalnız dolan Ic hücreler çizilir // (Sinir kabuk parça-DIŞI — çizilirse küpler parça yüzeyinden taşar). u32 icDolan = 0; for (u32 c = 0; c < sim.kafes.hucreSayi(); ++c) if (sim.kafes.tip[c] == HucreTip::Ic && sim.dolum.varisZamani[c] >= 0.0) ++icDolan; FIKO_KONTROL(icDolan > 0 && icDolan < sim.dolum.dolanHucre); // kabuk dışarıda const VoxelGorselSonuc tam = dolumMeshKur(sim.kafes, sim.dolum, ay, -1.0); FIKO_KONTROL(tam.gecerli); FIKO_KONTROL(tam.hucreSayi == icDolan); FIKO_KONTROL(!tam.mesh.vertexler.empty()); FIKO_KONTROL(tam.mesh.vertexler.size() < (size_t)tam.hucreSayi * 24); // TAŞMA YOK: küp mesh sınırı parça AABB'sini yarım hücreden fazla aşamaz. { const UcgenCorba corba = corbaBrepten(geometri::kutu(40.0, 40.0, 40.0), 0.01); const f64 h = sim.kafes.merkez(1, 0, 0).x - sim.kafes.merkez(0, 0, 0).x; const f64 pay = 0.5 * h + 1e-6; FIKO_KONTROL(tam.mesh.sinir.asgari.x >= corba.sinir.asgari.x - pay); FIKO_KONTROL(tam.mesh.sinir.asgari.y >= corba.sinir.asgari.y - pay); FIKO_KONTROL(tam.mesh.sinir.asgari.z >= corba.sinir.asgari.z - pay); FIKO_KONTROL(tam.mesh.sinir.azami.x <= corba.sinir.azami.x + pay); FIKO_KONTROL(tam.mesh.sinir.azami.y <= corba.sinir.azami.y + pay); FIKO_KONTROL(tam.mesh.sinir.azami.z <= corba.sinir.azami.z + pay); } // Yarı zaman kesimi → daha az (veya eşit) hücre. const VoxelGorselSonuc yari = dolumMeshKur(sim.kafes, sim.dolum, ay, sim.dolum.toplamSure * 0.5); FIKO_KONTROL(yari.hucreSayi <= tam.hucreSayi); // t=0 kesimi → en fazla giriş hücreleri (giriş Sinir kabuğunda ise 0 olabilir). const VoxelGorselSonuc bas = dolumMeshKur(sim.kafes, sim.dolum, ay, 0.0); FIKO_KONTROL(bas.hucreSayi <= yari.hucreSayi); // Geçersiz kafes → boş. VoxelKafes bos; FIKO_KONTROL(!dolumMeshKur(bos, sim.dolum, ay, -1.0).gecerli); } // ============================================================================ // DÖKÜM KURULUMU (Dalga A) — çoklu zamanlamalı giriş + chill + besleyici + // gravite yönü. Küçük gridler (≤12) — hızlı. // ============================================================================ // ---- (q) Çoklu giriş zamanlama: geç girişli dolum, tek girişten uzun sürer -- // Debi kısıtı: n. hücre ancak teslim edilen hacim n·V_h olunca dolabilir. FIKO_TEST(q_kurulum_coklu_giris_zamanlama) { DokumSimulasyonu sim = dolumKup(10, 40.0); FIKO_KONTROL(sim.kafes.gecerliMi()); u32 metal = 0; for (auto t : sim.kafes.tip) if (t != HucreTip::Dis) ++metal; const f64 M = metal * sim.kafes.hucreHacim() * sim.alasim.yogunluk; // kavite kütlesi [kg] // Tek giriş: tüm kütle (pay ile) 1 saniyede üstten → süre ≈ 1/1.2 s. DokumGirisi a; a.ad = "A"; a.konum = {0, 0, 25}; a.kutleKg = 1.2 * M; a.sureS = 1.0; sim.kurulum.girisler = {a}; FIKO_KONTROL(sim.dolumCoz()); FIKO_KONTROL(sim.dolum.hazir && !sim.dolum.misrun); const f64 tek = sim.dolum.toplamSure; FIKO_KONTROL(tek > 0.5); // debi kısıtı çalışıyor (geometrik cepheden uzun) // İki giriş: yarısı hemen üstten, yarısı 4 s gecikmeli alttan. DokumGirisi b = a; b.ad = "B"; b.konum = {0, 0, -25}; b.gecikmeS = 4.0; a.kutleKg = 0.62 * M; b.kutleKg = 0.62 * M; sim.kurulum.girisler = {a, b}; FIKO_KONTROL(sim.dolumCoz()); FIKO_KONTROL(sim.dolum.hazir && !sim.dolum.misrun); const f64 cift = sim.dolum.toplamSure; FIKO_KONTROL(cift >= 4.0); // kavite ancak geç girişin penceresinde dolar FIKO_KONTROL(cift > tek); // gecikme toplam dolumu uzattı // Kütle yetmezse → misrun (eksik dolum). a.kutleKg = 0.3 * M; b.kutleKg = 0.3 * M; sim.kurulum.girisler = {a, b}; FIKO_KONTROL(sim.dolumCoz()); FIKO_KONTROL(sim.dolum.misrun); FIKO_KONTROL(sim.dolum.dolanHucre < sim.dolum.toplamMetal); } // ---- (q2) Material Trace (giriş-ID): etiket ilk-varış cephesiyle yayılır ---- // İki eş-zamanlı kaynak (sol/sağ uç, aynı kot) → sol yarı 0, sağ yarı 1 etiketi; // tüm dolan hücrelerde geçerli etiket, her iki kaynak da temsil edilir. FIKO_TEST(q2_dolum_giris_id_yayilimi) { DokumSimulasyonu sim = dolumKup(10, 40.0); FIKO_KONTROL(sim.kafes.gecerliMi()); const VoxelKafes& k = sim.kafes; const u32 nx = k.nx(), ny = k.ny(), nz = k.nz(); // Aynı kotta sol/sağ uç metal hücreler (orta y/z hattı). u32 sol = (u32)-1, sag = (u32)-1; for (u32 i = 0; i < nx; ++i) { const u32 idx = k.indeks(i, ny / 2, nz / 2); if (k.tip[idx] == HucreTip::Dis) continue; if (sol == (u32)-1) sol = idx; sag = idx; } FIKO_KONTROL(sol != (u32)-1 && sag != (u32)-1 && sol != sag); const std::vector kaynaklar = {{sol, 0.0, 0.0, 0.0}, {sag, 0.0, 0.0, 0.0}}; const DolumSonuc d = dokum::dolumCoz(k, sim.alasim, sim.dolumAyar, kaynaklar, {0, 0, -1}); FIKO_KONTROL(d.hazir && !d.misrun); FIKO_KONTROL(d.girisId.size() == k.hucreSayi()); FIKO_KONTROL(d.girisId[sol] == 0 && d.girisId[sag] == 1); u32 say0 = 0, say1 = 0; for (u32 idx = 0; idx < k.hucreSayi(); ++idx) { if (d.varisZamani[idx] < 0.0) { FIKO_KONTROL(d.girisId[idx] == kGirisYok); // dolmayan = etiketsiz continue; } FIKO_KONTROL(d.girisId[idx] != kGirisYok); // dolan her hücre etiketli d.girisId[idx] == 0 ? ++say0 : ++say1; } FIKO_KONTROL(say0 > 0 && say1 > 0); // iki kaynak da bölge besledi // Kaynak komşusu kendi etiketini taşır (simetrik yarılar). FIKO_KONTROL(d.girisId[k.indeks(sol % nx + 1, ny / 2, nz / 2)] == 0); FIKO_KONTROL(d.girisId[k.indeks(sag % nx - 1, ny / 2, nz / 2)] == 1); } // ---- (r) Chill etkisi: chill yakınındaki hücre daha erken katılaşır -------- FIKO_TEST(r_kurulum_chill_erken_katilastirir) { const geometri::BrepGovde kup = geometri::kutu(30, 30, 30); auto coz = [&](bool chillVar, u32& disHucre) -> f64 { DokumSimulasyonu sim(200); sim.alasim = alasimAlSi7Mg(); sim.kalip = kalipSilikaKum(); sim.cozunurluk = 10; if (chillVar) { Sogutucu c; c.konum = {18, 0, 0}; // +x yüzü dışı (yüz x=15'te) c.yaricapMm = 12; c.htc = 3500; // bakır chill (arastirma.md 4000–8000 bandı alt ucu) sim.kurulum.sogutucular = {c}; } FIKO_KONTROL(sim.hesaplaGovde(kup)); // +x yüzü ortasındaki metal hücre (en büyük i; j,k orta). const u32 nx = sim.kafes.nx(), ny = sim.kafes.ny(), nz = sim.kafes.nz(); disHucre = (u32)-1; for (u32 i = nx; i-- > 0;) { const u32 idx = sim.kafes.indeks(i, ny / 2, nz / 2); if (sim.kafes.tip[idx] != HucreTip::Dis) { disHucre = idx; break; } } FIKO_KONTROL(disHucre != (u32)-1); return sim.sonuc.katilasmaSuresi[disHucre]; }; u32 h1 = 0, h2 = 0; const f64 tYok = coz(false, h1); const f64 tChill = coz(true, h2); FIKO_KONTROL(h1 == h2); // aynı grid/hücre (chill kafesi değiştirmez) FIKO_KONTROL(tYok > 0 && tChill > 0); FIKO_KONTROL(tChill < tYok); // chill yüzeye yakın hücreyi erken katılaştırır } // ---- (s) Besleyici: kafese metal ekler + hot-spot besleyiciye taşınır ------ FIKO_TEST(s_kurulum_besleyici_hotspot_tasir) { const geometri::BrepGovde kup = geometri::kutu(20, 20, 20); auto hotZ = [&](bool besle, f64& metalHacim) -> f64 { DokumSimulasyonu sim(201); sim.alasim = alasimAlSi7Mg(); sim.kalip = kalipSilikaKum(); sim.cozunurluk = 12; if (besle) { Besleyici b; b.konum = {0, 0, 10}; // küp üstü, taban merkezi b.capMm = 24; // M_riser = 0.1875·24 = 4.5 > 1.2·M_küp(3.33) b.yukseklikMm = 30; sim.kurulum.besleyiciler = {b}; } FIKO_KONTROL(sim.hesaplaGovde(kup)); // Grid çözünürlüğü iki koşuda farklı (besleyici AABB'yi büyütür) → // hücre SAYISI değil metal HACMİ karşılaştırılır. u32 metalSayi = 0; for (auto t : sim.kafes.tip) if (t != HucreTip::Dis) ++metalSayi; metalHacim = metalSayi * sim.kafes.hucreHacim(); // [m³] const u32 idx = sim.sonuc.hotSpotIndeks; const u32 nx = sim.kafes.nx(), ny = sim.kafes.ny(); const u32 i = idx % nx, j = (idx / nx) % ny, k = idx / (nx * ny); return sim.kafes.merkez(i, j, k).z; }; f64 vYok = 0, vVar = 0; const f64 zYok = hotZ(false, vYok); const f64 zVar = hotZ(true, vVar); FIKO_KONTROL(vVar > vYok * 1.5); // besleyici GERÇEKTEN kafese metal ekledi // (küp 8000 mm³ + silindir ~13500 mm³) FIKO_KONTROL(zVar > zYok + 3.0); // hot-spot yukarı (besleyiciye) taşındı FIKO_KONTROL(zVar > 8.0); // besleyici bölgesinde (küp üstü z=10 civarı+) } // ---- (t) Gravite yönü: ters çevrilince dolum varış sırası tersine döner ---- FIKO_TEST(t_kurulum_gravite_ters_dolum) { auto varislar = [&](f32 gz, f64& ustT, f64& altT) { DokumSimulasyonu sim(202); sim.alasim = alasimAlSi7Mg(); sim.kalip = kalipSilikaKum(); sim.cozunurluk = 12; const geometri::BrepGovde kolon = geometri::kutu(10, 10, 60); kafesKurSinifla(sim.kafes, kolon, 12); sim.kurulum.graviteYon = {0, 0, gz}; FIKO_KONTROL(sim.dolumCoz()); const auto& d = sim.dolum; const u32 nx = sim.kafes.nx(), ny = sim.kafes.ny(), nz = sim.kafes.nz(); ustT = altT = -1; for (u32 k = nz; k-- > 0;) { // en üst metal (orta kolon) const u32 idx = sim.kafes.indeks(nx / 2, ny / 2, k); if (sim.kafes.tip[idx] != HucreTip::Dis && d.varisZamani[idx] >= 0) { ustT = d.varisZamani[idx]; break; } } for (u32 k = 0; k < nz; ++k) { // en alt metal (orta kolon) const u32 idx = sim.kafes.indeks(nx / 2, ny / 2, k); if (sim.kafes.tip[idx] != HucreTip::Dis && d.varisZamani[idx] >= 0) { altT = d.varisZamani[idx]; break; } } FIKO_KONTROL(ustT >= 0 && altT >= 0); }; f64 u1 = 0, a1 = 0, u2 = 0, a2 = 0; varislar(-1.0f, u1, a1); // normal gravite: üstten dökülür → üst erken varislar(+1.0f, u2, a2); // ters gravite: "üst" = −z → alt erken FIKO_KONTROL(u1 < a1); FIKO_KONTROL(a2 < u2); } // ============================================================================ // HAVA GERİ-BASINCI — ideal gaz sıkışması + kalıp geçirgenliği (Darcy kaçışı) // + metalostatik ezme. Test geometrisi: alttan girişli dik kolon (2,4,0..7, // tepesi açık) + yan kol (3,4,1) + üstü kalıpla KAPALI kör cep (4,4,1..3). // Metal (2,4,1)'i geçince cep mühürlenir (V0=4 hücre) ve gaz sıkışmaya başlar. // h = 0.25 m (gerçekçi metalostatik basınç ölçeği için büyük hücre). // ============================================================================ static VoxelKafes havaTestKafes() { VoxelKafes k; Aabbd s; s.genislet({-1, -1, -1}); s.genislet({1, 1, 1}); k.kur(s, 8, 0.0, 1.0); for (auto& t : k.tip) t = HucreTip::Dis; for (u32 kk = 0; kk < 8; ++kk) k.tip[k.indeks(2, 4, kk)] = HucreTip::Ic; k.tip[k.indeks(3, 4, 1)] = HucreTip::Ic; for (u32 kk = 1; kk <= 3; ++kk) k.tip[k.indeks(4, 4, kk)] = HucreTip::Ic; return k; } // ---- (v) Kokil hapseder, kum (geçirgenlik) doldurur ------------------------- FIKO_TEST(v_hava_geri_basinc_kokil_kum) { const VoxelKafes k = havaTestKafes(); const Alasim al = alasimAlSi7Mg(); DolumAyar ayar; const std::vector kaynak = {{k.indeks(2, 4, 0), 0.0, 0.0, 0.0}}; // Kokil (κ=0): cep gazı kaçamaz → Boyle sıkışması basıncı yükseltir, // P > P0+ρgh olunca cephe DURUR → cep DOLMAZ (gerçek hava hapsi). const DolumSonuc dKokil = dolumCoz(k, al, kalipCelikKalip(), ayar, kaynak, {0, 0, -1}); FIKO_KONTROL(dKokil.hazir); FIKO_KONTROL(dKokil.hapsolanHucre > 0); FIKO_KONTROL(dKokil.dolanHucre < dKokil.toplamMetal); FIKO_KONTROL(!dKokil.misrun); // hava hapsi misrun DEĞİL (ayrı rapor) FIKO_KONTROL(dKokil.maksCepBasinc > ayar.atmBasinc * 1.05); // basınç yükseldi const u32 tepe = k.indeks(4, 4, 3); // cebin tepesi: dolamaz + basınç kayıtlı FIKO_KONTROL(dKokil.varisZamani[tepe] < 0.0); FIKO_KONTROL(dKokil.hapsoldu[tepe] == 1); FIKO_KONTROL(dKokil.cepBasinc[tepe] > f32(ayar.atmBasinc)); // Kum (κ≈3.7e-11 m², AFS 120): gaz Darcy ile kalıba kaçar → cep söner, dolar. const DolumSonuc dKum = dolumCoz(k, al, kalipSilikaKum(), ayar, kaynak, {0, 0, -1}); FIKO_KONTROL(dKum.hazir && !dKum.misrun); FIKO_KONTROL(dKum.hapsolanHucre == 0); FIKO_KONTROL(dKum.dolanHucre == dKum.toplamMetal); FIKO_KONTROL(dKum.dolanHucre > dKokil.dolanHucre); // Eski arayüz (Kalip'siz): model kapalı → alanlar boş, tam dolum (geriye uyum). const DolumSonuc dEski = dolumCoz(k, al, ayar, kaynak, {0, 0, -1}); FIKO_KONTROL(dEski.hazir && dEski.cepBasinc.empty() && dEski.hapsoldu.empty()); FIKO_KONTROL(dEski.dolanHucre == dEski.toplamMetal); FIKO_KONTROL(dEski.hapsolanHucre == 0); } // ---- (v2) İdeal gaz: κ=0'da P·V = P0·V0 (izotermal Boyle) ±%5 --------------- FIKO_TEST(v2_hava_ideal_gaz_boyle) { const VoxelKafes k = havaTestKafes(); DolumAyar ayar; const std::vector kaynak = {{k.indeks(2, 4, 0), 0.0, 0.0, 0.0}}; const DolumSonuc d = dolumCoz(k, alasimAlSi7Mg(), kalipCelikKalip(), ayar, kaynak, {0, 0, -1}); FIKO_KONTROL(d.hazir); // Cep V0 = 4 hücre; (3,4,1) dolunca V = 3 hücre → P₁·V₁ = P0·V0 sabiti. const f64 P0 = ayar.atmBasinc, vH = k.hucreHacim(); const f64 P1 = f64(d.cepBasinc[k.indeks(3, 4, 1)]); FIKO_KONTROL(P1 > P0); // sıkışma basıncı yükseltti const f64 sol = P1 * (3.0 * vH), sag = P0 * (4.0 * vH); FIKO_KONTROL(std::abs(sol - sag) < 0.05 * sag); // Boyle ±%5 } // ---- (v3) Metalostatik ezme: yoğun metal (büyük ρgh) cebi daha çok doldurur - FIKO_TEST(v3_hava_metalostatik_ezme) { const VoxelKafes k = havaTestKafes(); DolumAyar ayar; const std::vector kaynak = {{k.indeks(2, 4, 0), 0.0, 0.0, 0.0}}; const Kalip kokil = kalipCelikKalip(); const DolumSonuc dAl = dolumCoz(k, alasimAlSi7Mg(), kokil, ayar, kaynak, {0, 0, -1}); const DolumSonuc dFe = dolumCoz(k, alasimCelik(), kokil, ayar, kaynak, {0, 0, -1}); FIKO_KONTROL(dAl.hapsolanHucre > 0 && dFe.hapsolanHucre > 0); // ρ_çelik ≈ 2.9·ρ_Al → metalostatik itki P0+ρgh cebi daha derin ezer. FIKO_KONTROL(dFe.hapsolanHucre < dAl.hapsolanHucre); FIKO_KONTROL(dFe.dolanHucre > dAl.dolanHucre); } // ---- (v4) Vent: kullanıcı vent'i cebin tepesinde → kokilde bile tam dolum --- FIKO_TEST(v4_hava_vent_kacisi) { const VoxelKafes k = havaTestKafes(); DolumAyar ayar; const std::vector kaynak = {{k.indeks(2, 4, 0), 0.0, 0.0, 0.0}}; const std::vector vent = {k.indeks(4, 4, 3)}; const DolumSonuc d = dolumCoz(k, alasimAlSi7Mg(), kalipCelikKalip(), ayar, kaynak, {0, 0, -1}, vent); FIKO_KONTROL(d.hazir && !d.misrun); FIKO_KONTROL(d.hapsolanHucre == 0); FIKO_KONTROL(d.dolanHucre == d.toplamMetal); } // ---- (v5) Malzeme DB geçirgenliği: AFS→SI dönüşümü + kum ≫ kokil ------------ FIKO_TEST(v5_hava_gecirgenlik_db) { FIKO_KONTROL(kalipSilikaKum().gecirgenlik > 1e-11); // yeşil kum ~3.7e-11 m² FIKO_YAKIN(kalipCelikKalip().gecirgenlik, 0.0, 1e-30); // kokil gaz geçirmez // AFS→SI: κ ≈ 3.06e-13·AFS (AFS 100 ≈ 31 darcy mertebesi). FIKO_YAKIN(afsGecirgenlikSI(100.0), 3.06e-11, 1e-14); FIKO_KONTROL(afsGecirgenlikSI(200.0) > afsGecirgenlikSI(50.0)); FIKO_YAKIN(afsGecirgenlikSI(-5.0), 0.0, 1e-30); // negatif girdi güvenli } // ---- (u) Kurulum mühendislik yardımcıları (arastirma.md kalibrasyonu) ------ FIKO_TEST(u_kurulum_muhendislik_yardimcilari) { // Döküm süresi: çelik formülü t=(2.4335−0.3953·log₁₀W)√W; W=100 → ~16.6 s. const Alasim celik = alasimCelik(); const f64 tCelik = dokumSuresiOner(celik, 100.0, 20.0); FIKO_YAKIN(tCelik, (2.4335 - 0.3953 * 2.0) * 10.0, 1e-6); // Hafif alaşım t=2.4√W; W=25 → 12 s. FIKO_YAKIN(dokumSuresiOner(alasimAlSi7Mg(), 25.0, 10.0), 12.0, 1e-6); // Ağır parça daha uzun süre. FIKO_KONTROL(dokumSuresiOner(alasimGG25(), 400.0, 20.0) > dokumSuresiOner(alasimGG25(), 50.0, 20.0)); // Giriş hızı: 5 kg Al, 5 s, ⌀20 → Q=(5/2680)/5≈3.73e-4 m³/s, A≈3.14e-4 m² // → v≈1.19 m/s > kritik 0.5 (uyarı bölgesi). DokumGirisi g; g.kutleKg = 5; g.sureS = 5; g.capMm = 20; const f64 v = girisOrtalamaHiz(g, 2680.0); FIKO_KONTROL(v > 1.0 && v < 1.4); FIKO_YAKIN(kritikGirisHiz(alasimAlSi7Mg()), 0.5, 1e-9); FIKO_YAKIN(kritikGirisHiz(alasimGG25()), 1.0, 1e-9); // Döküm sıcaklığı bandı: Al-Si 680–750. f64 mn = 0, mx = 0; dokumSicaklikBandi(alasimAlSi7Mg(), mn, mx); FIKO_YAKIN(mn, 680.0, 1e-9); FIKO_YAKIN(mx, 750.0, 1e-9); // Besleme hacmi şartı: V_r·η ≥ β(V_p+V_r). η=0.14, β=0.06 (Al): // V_p=1e6 → V_r=8e5: 1.12e5 ≥ 0.06·1.8e6=1.08e5 ✓; V_r=5e5: 7e4 < 9e4 ✗. FIKO_KONTROL(besleyiciHacimYeterli(1e6, 8e5, 0.14, 0.06)); FIKO_KONTROL(!besleyiciHacimYeterli(1e6, 5e5, 0.14, 0.06)); } // ---- (u2) Derece mesh üreticileri: kum bloğu kutusu + çerçeve -------------- // Kum döküm profesyonel görseli: alt/üst derece çerçevesi + kum blokları. // GÖRSEL — fizik etkilenmez (kum bölgesi kafeste zaten kalıp payı olarak var). FIKO_TEST(u2_kurulum_derece_mesh) { const Vec3f mn{-10, -20, -5}, mx{30, 40, 25}; const MeshVeri kutu = kurulumKutuMesh(mn, mx, {1, 1, 1}); FIKO_KONTROL(kutu.vertexler.size() == 36); // 6 yüz × 2 üçgen × 3 vertex FIKO_YAKIN((f64)kutu.sinir.asgari.x, -10.0, 1e-4); FIKO_YAKIN((f64)kutu.sinir.azami.z, 25.0, 1e-4); // Dejenere kutu (mx mn.x + 1e-4f && v.konum[0] < mx.x - 1e-4f && v.konum[1] > mn.y + 1e-4f && v.konum[1] < mx.y - 1e-4f) icteVar = true; FIKO_KONTROL(!icteVar); } // ---- Partikül bulutu mesh'i (çalkantı görseli) ----------------------------- // SphDolumKare.partikul → oktahedron blob. Hücre binning'i jeti/sıçramayı // eziyordu; bu yol partikülün KENDİ konumunu çizer (bkz. test_sph (i)). FIKO_TEST(g1_partikul_mesh_blob) { Aabbd s; s.genislet({0, 0, 0}); s.genislet({40, 40, 40}); VoxelKafes kafes; kafes.kur(s, 10, 0.0, 0.001); const Vec3d m0 = kafes.merkez(0, 0, 0); const f64 h = kafes.merkez(1, 0, 0).x - m0.x; std::vector ps; for (int i = 0; i < 5; ++i) { SphPartikul p; p.konum = {(f32)(10.0 + i), 20.0f, 20.0f}; p.hiz = (f32)i; // 0..4 m/s p.sicaklik = 700.0f; ps.push_back(p); } PartikulGorselAyar a; const VoxelGorselSonuc r = partikulMeshKur(kafes, ps, a); FIKO_KONTROL(r.gecerli); FIKO_KONTROL(r.hucreSayi == 5); // Oktahedron: partikül başına 6 vertex / 8 üçgen (24 indeks). FIKO_KONTROL(r.mesh.vertexler.size() == 5 * 6); FIKO_KONTROL(r.mesh.indeksler.size() == 5 * 24); // Oto yarıçap = hücre boyu × oran → blob hücreden küçük (bulut tıkanmasın). const f64 yari = h * a.yariCapOran; FIKO_KONTROL(yari > 0 && yari < h); // Sınır: ilk partikül merkezi ∓ yarıçap. FIKO_KONTROL(std::abs(r.mesh.sinir.asgari.x - (10.0 - yari)) < 1e-3); FIKO_KONTROL(std::abs(r.mesh.sinir.azami.x - (14.0 + yari)) < 1e-3); // Renk = hız rampası → oto aralık hız min/max (0..4). FIKO_KONTROL(std::abs(r.alanMin - 0.0f) < 1e-4f); FIKO_KONTROL(std::abs(r.alanMax - 4.0f) < 1e-4f); // Hız farklı → renk farklı (çalkantı okunabilsin; yavaş≠hızlı). const Vertex& v0 = r.mesh.vertexler[0]; const Vertex& v4 = r.mesh.vertexler[4 * 6]; FIKO_KONTROL(std::abs(v0.renk[0] - v4.renk[0]) + std::abs(v0.renk[1] - v4.renk[1]) + std::abs(v0.renk[2] - v4.renk[2]) > 0.1f); // Normaller birim ve dışa bakar (küre izlenimi; kırık gölge olmasın). for (const Vertex& v : r.mesh.vertexler) { const f64 n = std::sqrt((f64)v.normal[0] * v.normal[0] + (f64)v.normal[1] * v.normal[1] + (f64)v.normal[2] * v.normal[2]); FIKO_KONTROL(std::abs(n - 1.0) < 1e-5); } // Alt-örnek: n'de bir çizilir (kayıt DEĞİL, yalnız mesh hafifler). PartikulGorselAyar a2 = a; a2.altOrnek = 2; const VoxelGorselSonuc r2 = partikulMeshKur(kafes, ps, a2); FIKO_KONTROL(r2.hucreSayi == 3); // 0,2,4 FIKO_KONTROL(r2.mesh.vertexler.size() == 3 * 6); // Sıcaklık kipi: tüm partiküller 700°C → dejenere aralık güvenli genişler. PartikulGorselAyar a3 = a; a3.hizRenk = false; const VoxelGorselSonuc r3 = partikulMeshKur(kafes, ps, a3); FIKO_KONTROL(r3.gecerli); FIKO_KONTROL(r3.alanMax > r3.alanMin); // Boş liste → geçersiz (çağıran görseli kaldırır, boş düğüm bırakmaz). const VoxelGorselSonuc bos = partikulMeshKur(kafes, {}, a); FIKO_KONTROL(!bos.gecerli); FIKO_KONTROL(bos.mesh.vertexler.empty()); } // ---- İnce cidar tuzağı: kafes parçayı görmeyince "%100 doldu" YALAN -------- // Fikri 07-16: "kalıbın sadece burasını dolduruyor" — panel 12/12 hücre %100 ✓ // diyordu. Kök: kafesiSinifla MERKEZ tabanlı → hücre ancak MERKEZİ katının // içindeyse metal olur. Cidar h'den inceyse hücre merkezi içeri düşmez → // bölge kafeste TAMAMEN yok olur; geriye tek kalın göbek kalır ve dolum onu // gerçekten doldurur → "%100" teknik olarak doğru, fiilen anlamsız. // // ⚠️ ÖLÇÜLEREK ÖĞRENİLDİ (hipotez çürüdü): kaba kafes ince cidarı yalnız // KAYBETMİYOR, hizalanma tutarsa ŞİŞİRİYOR da — 8mm cidar h=18mm'de tek hücre // katmanına oturunca hacim %213.8 okundu (2mm cidar aynı h'de %0.0). Hata yönü // hizalanmaya bağlı (±h/2 per yüzey). Bu yüzden kafesParcaKapsama HASSAS ÖLÇEK // DEĞİL, yalnız FELAKET DEDEKTÖRÜdür: ~0'ı yakalar, şişmeyi yakalayamaz // (yanlış negatif). Gerçek çözüm konservatif voxelize — bu yalnız uyarı. FIKO_TEST(g2_ince_cidar_kapsama_kaybi) { auto kutuCorba = [](Vec3d a, Vec3d b) { UcgenCorba c; const Vec3d v[8] = {{a.x,a.y,a.z},{b.x,a.y,a.z},{b.x,b.y,a.z},{a.x,b.y,a.z}, {a.x,a.y,b.z},{b.x,a.y,b.z},{b.x,b.y,b.z},{a.x,b.y,b.z}}; const int f[12][3] = {{0,2,1},{0,3,2},{4,5,6},{4,6,7},{0,1,5},{0,5,4}, {2,3,7},{2,7,6},{1,2,6},{1,6,5},{0,4,7},{0,7,3}}; for (auto& t : f) c.ekle(v[t[0]], v[t[1]], v[t[2]]); return c; }; // NOT: FIKO_KONTROL test gövdesinin yerel durumunu kullanır → lambda içinde // çağrılamaz. Ölçüm lambda'sı yalnız DEĞER döndürür; kontrol dışarıda. auto kapsamaOlc = [](const UcgenCorba& c, u32 cz, const char* et) { VoxelKafes k; if (!k.kur(c.sinir, cz, 0.4, 0.001)) return -2.0; // kurulum hatası (dışarıda yakalanır) kafesiSinifla(k, c); parcaSdfHesapla(k, c); // parcaUcgen'i doldurur (kapsama bunu ister) const f64 kap = kafesParcaKapsama(k); std::printf(" [OLCUM] %-14s cozunurluk=%3u h=%6.2fmm grid=%ux%ux%u kapsama=%%%.1f\n", et, cz, k.hMesh(), k.nx(), k.ny(), k.nz(), kap * 100.0); return kap; }; // corbaHacim doğrulaması (analitik kutu). const UcgenCorba kup = kutuCorba({0, 0, 0}, {40, 40, 40}); FIKO_KONTROL(std::abs(corbaHacim(kup.vertler) - 64000.0) < 1e-6 * 64000.0); // (1) ⭐ FELAKET: 400x300 gövde, 2mm cidar. pay=0.4 → h=18mm; hiçbir hücre // MERKEZİ 2mm plakanın içine düşmez → parça kafeste YOK. Kullanıcının // gördüğü durum bu ("sadece burasını dolduruyor" = yalnız kalın göbek kalmış). const UcgenCorba ince = kutuCorba({0, 0, 0}, {400, 300, 2}); const f64 kayip = kapsamaOlc(ince, 40, "2mm cidar"); FIKO_KONTROL(kayip >= 0.0 && kayip < 0.01); // ÖLÇÜLDÜ: %0.0 — parça yok // Çözünürlük artınca cidar geri gelir (kullanıcıya verilen çözüm gerçekten işe yarıyor). FIKO_KONTROL(kapsamaOlc(ince, 640, "2mm cidar") > 0.5); // (2) ⚠️ YANLIŞ NEGATİF (dürüstlük kaydı): 8mm cidar h=18mm'de KAYBOLMAZ, // tek hücre katmanına oturup ŞİŞER → kapsama 1'i AŞAR. Metrik bunu yakalamaz. const UcgenCorba orta = kutuCorba({0, 0, 0}, {400, 300, 8}); FIKO_KONTROL(kapsamaOlc(orta, 40, "8mm cidar") > 1.5); // ÖLÇÜLDÜ: %213.8 // (3) YANLIŞ ALARM OLMAMALI: tıknaz parça makul çözünürlükte eşiğin üstünde. FIKO_KONTROL(kapsamaOlc(kup, 40, "40mm kup") > 0.5); // (4) pay=0.4 → h = (en uzun eksen × 1.8) / çözünürlük. Kullanıcı "40" yazınca // parça en uzun eksende 40 DEĞİL ~22 hücre alır. ÖLÇÜLDÜ: 400mm + res40 → 18.00mm. VoxelKafes kaba; kaba.kur(ince.sinir, 40, 0.4, 0.001); FIKO_KONTROL(std::abs(kaba.hMesh() - 18.0) < 0.01); // (5) parcaUcgen yoksa teşhis "bilinmiyor" der (uydurma sayı ÜRETMEZ). VoxelKafes bos; bos.kur(kup.sinir, 40, 0.4, 0.001); kafesiSinifla(bos, kup); FIKO_KONTROL(kafesParcaKapsama(bos) < 0.0); } // ---- Çözünürlük ↔ h: kur()'un gizli pay çarpanı (Fikri'nin 18mm'si) -------- // "voxeller kocaman ve taşıyor" + "sadece burasını dolduruyor" ikisi de h'den. // Bu test formülü DEĞİL gerçek kur()'u ölçer: ikisi ayrışırsa öneri sessizce // yanlış olur (formül ile kur() ayrı yerlerde — tek kaynak zorunlu). FIKO_TEST(g3_cozunurluk_h_donusumu) { Aabbd p; // 400 x 300 x 8 mm gerçekçi döküm gövdesi p.genislet({0, 0, 0}); p.genislet({400, 300, 8}); // (1) İLERİ YÖN: formül gerçek kafesle birebir uyuşmalı (pay=0.4 → ×1.8). for (u32 cz : {10u, 40u, 100u, 640u}) { VoxelKafes k; FIKO_KONTROL(k.kur(p, cz, 0.4, 0.001)); const f64 formul = VoxelKafes::hCozunurlukten(p, cz, 0.4); FIKO_KONTROL(std::abs(formul - k.hMesh()) < 1e-9); } // ÖLÇÜLDÜ: Fikri'nin durumu — 400mm parça, çözünürlük 40 → h = 18.00mm. // Kullanıcı "40 hücre" sanıyor; parça en uzun eksende 400/18 = 22.2 hücre alıyor. FIKO_KONTROL(std::abs(VoxelKafes::hCozunurlukten(p, 40, 0.4) - 18.0) < 1e-9); // (2) TERS YÖN: hedef h veren çözünürlük GERÇEKTEN o h'yi (veya daha incesini) // üretmeli — yuvarlama yanlış tarafa kaçmasın (h > hedef = cidar yine kaybolur). for (f64 hHedef : {0.5, 1.0, 4.0, 9.0, 18.0}) { const u32 cz = VoxelKafes::cozunurlukHedefH(p, hHedef, 0.4); FIKO_KONTROL(cz > 0); VoxelKafes k; FIKO_KONTROL(k.kur(p, cz, 0.4, 0.001)); FIKO_KONTROL(k.hMesh() <= hHedef + 1e-9); // hedefi AŞMAZ FIKO_KONTROL(k.hMesh() > hHedef * 0.90); // gereksiz ince de değil (maliyet ∝ res³) } // Döküm kuralı h ≤ cidar/2: 8mm cidar → h ≤ 4mm → çözünürlük ≥ 180. // (Fikri'nin 40'ı ile arada 4.5× → hücre sayısı ~91× — maliyet ∝ res³, uyarılmalı.) const u32 gerekli = VoxelKafes::cozunurlukHedefH(p, 8.0 / 2.0, 0.4); FIKO_KONTROL(gerekli == 180); std::printf(" [OLCUM] 400x300x8mm: res40 -> h=%.2fmm | cidar 8mm icin h<=4mm -> res=%u\n", VoxelKafes::hCozunurlukten(p, 40, 0.4), gerekli); // (3) pay=0 → çarpan yok (kullanıcının sandığı davranış: res = parça hücresi). FIKO_KONTROL(std::abs(VoxelKafes::hCozunurlukten(p, 40, 0.0) - 10.0) < 1e-9); // (4) Bozuk girdi uydurma sayı üretmez. FIKO_KONTROL(VoxelKafes::cozunurlukHedefH(p, 0.0, 0.4) == 0); FIKO_KONTROL(VoxelKafes::cozunurlukHedefH(p, -1.0, 0.4) == 0); FIKO_KONTROL(VoxelKafes::hCozunurlukten(p, 0, 0.4) == 0.0); FIKO_KONTROL(VoxelKafes::cozunurlukHedefH(Aabbd{}, 4.0, 0.4) == 0); FIKO_KONTROL(VoxelKafes::hCozunurlukten(Aabbd{}, 40, 0.4) == 0.0); }