#include "kafa_penceresi.hpp" #include "fiko/cekirdek/arac_kipi.hpp" // araç kipi: bu kafa ANA pencere mi #include "arac_kayitlari.hpp" // aracGostericiOran — orantılı 3B gösterici #include // CreateProcessW — "diğer kafayı yeni pencerede aç" butonu #include #include #include #include #include #include "fiko/cekirdek/cekirdek.hpp" #include "fiko/core/kare_istek.hpp" #include "fiko/render/vulkan_baglam.hpp" #include "fiko/sahne/sahne.hpp" #include "fiko/ui/bilesen.hpp" // neonAyrac / hudKoseleri — sim penceresi kimliği #include "fiko/ui/form.hpp" #include "fiko/ui/imgui_katman.hpp" #include "fiko/ui/pencere_cerceve.hpp" #include "fiko/ui/tema.hpp" #include "fiko/ui/web_skin.hpp" // CAM Simülasyon penceresiyle ortak teal cam skin #include "ayar_kalici.hpp" // ayarOku/ayarYaz — kamera duruşu oturumlar arası kalıcı #include "imgui.h" #include "surec_kopru.hpp" // g_surecKopru — süreçler-arası "3d Seç" yayını #include "uygulama_baglam.hpp" // UygulamaBaglam::bekleyenIjkHedef tüketimi #include "yardim.hpp" namespace fiko { // belge_penceresi.cpp'de tanımlı app-global IPC köprüsü (bkz. surec_kopru.hpp). extern SurecKopru g_surecKopru; namespace { constexpr f64 kPi = 3.14159265358979323846; // Importer VRML'i 0.001 ölçekler (mesh mm→birim); pivot da bu ölçekte olmalı. constexpr f64 kMmOlcek = 0.001; // Kafa VRML modellerinin kökü. Exe-yanı assets/3d varsa onu tercih eder (dağıtım — // CMakeLists.txt POST_BUILD adımı bunu exe yanına kopyalar → proje başka PC'ye/klasöre // taşınsa da bulunur), yoksa depo köküne düşer (geliştirme). yardim.cpp'deki // yardimKok() ile BİREBİR aynı kalıp. std::filesystem::path kafaAssetKok() { wchar_t exeYol[MAX_PATH]{}; GetModuleFileNameW(nullptr, exeYol, MAX_PATH); const std::filesystem::path yaniAssets = std::filesystem::path(exeYol).parent_path() / L"assets" / L"3d"; std::error_code ec; if (std::filesystem::exists(yaniAssets, ec)) return yaniAssets; return std::filesystem::path( L"D:/__sistem__/Masaustu/FikoYT.Yeni.Cag/fikatia-local/assets/3d"); } // Duruşu metne çevir: quat(x y z w) + odak(x y z) + mesafe + fov + ortografik. std::string durusMetni(const OrbitKamera& k) { const Quatf q = k.yonelim(); const Vec3f o = k.odak(); char b[192]; std::snprintf(b, sizeof(b), "%.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %d", q.x, q.y, q.z, q.w, o.x, o.y, o.z, k.mesafe(), k.gorusAcisiDeg(), k.ortografik() ? 1 : 0); return b; } // Kayıtlı duruşu uygula. false = kayıt yok/bozuk (çağıran belgeSigdir'a düşer). bool durusYukle(OrbitKamera& k, const char* anahtar) { const std::string s = ayarOku(anahtar, ""); if (s.empty()) return false; float qx, qy, qz, qw, ox, oy, oz, me, fov; int ortho = 0; if (std::sscanf(s.c_str(), "%f %f %f %f %f %f %f %f %f %d", &qx, &qy, &qz, &qw, &ox, &oy, &oz, &me, &fov, &ortho) != 10) return false; const Quatf q{qx, qy, qz, qw}; if (!(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w > 1e-6f)) return false; // dejenere if (!(me > 0.0f)) return false; k.durumAta(q, {ox, oy, oz}, me, fov, ortho != 0); return true; } // Duruş sabitlendi mi? (kare-başı string kurmadan sayısal karşılaştırma — tahsis yok) bool durusAyni(const OrbitKamera& k, const KafaDurum& d) { const Quatf q = k.yonelim(); const Vec3f o = k.odak(); const auto e = [](f32 a, f32 b) { return std::abs(a - b) < 1e-5f; }; return e(q.x, d.sonYon.x) && e(q.y, d.sonYon.y) && e(q.z, d.sonYon.z) && e(q.w, d.sonYon.w) && e(o.x, d.sonOdak.x) && e(o.y, d.sonOdak.y) && e(o.z, d.sonOdak.z) && e(k.mesafe(), d.sonMesafe); } // Kamera duruşu kalıcılık anahtarı. Araç kipinde AYRI anahtar: pencere tam ekran // olduğundan kadraj tam CAD içindeki küçük panelden farklıdır — aynı anahtarı // paylaşsalardı biri diğerinin duruşunu bozar, model kadrajı taşardı. std::string kameraAnahtari(const KafaDurum& durum) { std::string a = durum.tanim->kameraAnahtar; if (aracSistemi().aktifKimlik() == durum.tanim->aracKimlik) a += ".arac"; return a; } // Duruş değişimini izle; hareket durup kayitGecikme kadar sabit kalınca TEK sefer yaz. void kameraKayitIzle(KafaDurum& durum, f32 dt) { if (!durum.belge) return; const OrbitKamera& k = durum.belge->kamera; if (!durusAyni(k, durum)) { // hâlâ oynuyor → sayacı sıfırla, yazma durum.sonYon = k.yonelim(); durum.sonOdak = k.odak(); durum.sonMesafe = k.mesafe(); durum.kayitBekle = 0.0f; durum.kayitKirli = true; return; } if (!durum.kayitKirli) return; durum.kayitBekle += dt; if (durum.kayitBekle < durum.kayitGecikme) return; ayarYaz(kameraAnahtari(durum).c_str(), durusMetni(k)); // tek yazma (küçük dosya) durum.kayitKirli = false; } // belge_yonetici.cpp'deki belgeSigdir'ın birebir eşi (datum'lar fit dışı). void belgeSigdir(Belge& b) { Aabbf tum; for (auto& d : b.sahne.dugumler()) { if (!d.yerelSinir.gecerliMi()) continue; if (d.tip == DugumTip::Duzlem || d.tip == DugumTip::Eksen || d.tip == DugumTip::EksenSistemi || d.tip == DugumTip::Nokta) continue; const Vec3f m = d.dunyaMerkez(); const f32 r = d.dunyaYaricap(); tum.genislet(m - Vec3f{r, r, r}); tum.genislet(m + Vec3f{r, r, r}); } if (tum.gecerliMi()) b.kamera.sigdir(tum.merkez(), tum.kosegen() * 0.5f + 0.5f); } // ---- Takım ekseni ---- // kBazTakimEksen: takımın B=0,C=0'daki dünya yönü. +Z (doğrulandı: web // takim.getWorldDirection = dünya +Z; FikoTakimFrezeleme'nin kendi dönüşü yok, geometri // +Z boyunca extrude). HER İKİ kafada da takım C'nin çocuğu, kendi dönüşü yok → ortak. const Vec3d kBazTakimEksen{0.0, 0.0, 1.0}; // B (üst) kullanıcı açısı → dünya-Z dönüş açısı. İKİ MAKİNE FARKLI FORMÜL (web'den): // Diagonal (bAcisiUst.ts) : kalib + yönlü(b) + 0.5 , yönlü = yon2 ? 360-b : b // Ozmo (UstAciOzmo.ts): 180 − (kalib + yönlü'(b)) , yönlü' = yon2 ? b : 360-b (TERS) f64 bAciDeg(const KafaDurum& d, f64 b) { if (d.tanim->tip == KafaTip::Ozmo) return 180.0 - (d.bKalib + (d.bYon == 2 ? b : 360.0 - b)); return d.bKalib + (d.bYon == 2 ? (360.0 - b) : b) + 0.5; } // bAciDeg'in TERSİ: dünya-Z dönüş açısı → kullanıcı B'si (analitik çözümde şart). f64 bAciTers(const KafaDurum& d, f64 bDeg) { f64 v; if (d.tanim->tip == KafaTip::Ozmo) { v = 180.0 - bDeg - d.bKalib; // = yönlü'(b) if (d.bYon != 2) v = 360.0 - v; // yon1 → yönlü' = 360-b } else { v = bDeg - d.bKalib - 0.5; // = yönlü(b) if (d.bYon == 2) v = 360.0 - v; } return std::fmod(std::fmod(v, 360.0) + 360.0, 360.0); } // C (alt) kullanıcı açısı → kendi ekseni etrafındaki dönüş açısı. Formül İKİ kafada AYNI // (cAcisiAlt.ts ve AltAciOzmo.ts birebir); eksen/pivot/aralık tanımdan gelir. f64 cAciDeg(const KafaDurum& d, f64 c) { return d.cKalib + (d.cYon == 2 ? (360.0 - c) : c); } // B/C açılarından dönüş kuaterniyonlarını üret (saf; node'a dokunmaz). // kafaAciUygula + farkHesapla ortak kullanır (tek-kaynak mükerrer-yok). void bcKuaterniyon(const KafaDurum& durum, f64 bAci, f64 cAci, Quatd& qB, Quatd& qC) { qB = Quatd::eksenAci(Vec3d{0, 0, 1}, bAciDeg(durum, bAci) * (kPi / 180.0)); qC = Quatd::eksenAci(durum.tanim->cEksen, cAciDeg(durum, cAci) * (kPi / 180.0)); } // İLERİ yön: verilen B/C için takımın dünya yön vektörü (I/J/K). Node'lara YAZMAZ. // "Açı → IJK" sekmesi + farkHesapla ortak kullanır (tek-kaynak). Vec3d takimYonHesapla(const KafaDurum& durum, f64 bAci, f64 cAci) { Quatd qB, qC; bcKuaterniyon(durum, bAci, cAci, qB, qC); // (qB*qC).dondur(v) = qB.dondur(qC.dondur(v)) → önce C sonra B (C, B'nin çocuğu). return (qB * qC).dondur(kBazTakimEksen).birim(); } // ---- Takım gösterge profili (kacmaDegerleriHesapla + takimGostergeMeshKur ORTAK) ---- // Yerel-uzay profil noktası: h = eksen (+Z) boyunca yükseklik (0 = kesme ucu, gövde +Z'ye // uzar), r = o yükseklikteki yarıçap. Tek formül üç modu da kapsar: rb=0 → düz uç, // 0& pr) { pr.clear(); const f64 R = std::max(cap * 0.5, 1e-4); const f64 rc = std::clamp(boncukRadius, 0.0, R); constexpr int kYay = 8; pr.push_back({0.0, R - rc}); // düz taban (rc=0 ise doğrudan R) for (int i = 1; i <= kYay; ++i) { const f64 a = (kPi * 0.5) * i / kYay; pr.push_back({rc - rc * std::cos(a), (R - rc) + rc * std::sin(a)}); } pr.push_back({boy, R}); // gövde tepesi (gösterge sonu) } // Kaçma (runout) — cam_simulasyon_penceresi.cpp'deki kacmaHesapla ile AYNI analitik // yöntem: takımın TÜM profil noktaları (uç, yay, omuz, taban) üzerinden, her eksende // GERÇEK (işaretli) min/max ekstremleri hesaplanır. Her nokta (h, r) bir halkadır; // halkanın o eksendeki izdüşüm aralığı [e_i·h − r·√(1−e_i²), e_i·h + r·√(1−e_i²)]'dir. // TÜM halkalar üzerinden GLOBAL min ve max alınır (bu, takımın gerçek hacminin o // eksendeki min/max'ıdır — mesh tabanlı convex-hull ekstremiyle birebir eşleşir). // Web referansı (fiko-similasyon.component.ts, _kacmaDegerleriAl) ile AYNI seçim // sezgiselliği uygulanır: |min| > çap ise max, değilse min raporlanır — sonuç // İŞARETLİ tek bir değerdir (üç eksen için ayrı ayrı), pozitif bounding-box // yarı-genişliği DEĞİLDİR. // ÖNCEKİ SÜRÜM |e·h| + r·sin ifadesinin TÜM halkalar üzerindeki MUTLAK // MAKSİMUMUNU alıyordu — bu HER ZAMAN pozitif bir bounding-box yarı-genişliğiydi; // referansın işaretli uç-nokta koordinatıyla aynı büyüklük DEĞİLDİ, bu yüzden genel // B/C açılarında referanstan (ve dolayısıyla beklenen değerlerden) sapıyordu. Vec3d kacmaDegerleriHesapla(const KafaDurum& durum, Vec3d* outMin = nullptr, Vec3d* outMax = nullptr) { Vec3d e = takimYonHesapla(durum, durum.bAci, durum.cAci); if (e.uzunlukKare() < 1e-9) e = {0, 0, 1}; e = e.birim(); const f64 R = durum.takimCap * 0.5; // gövde yarıçapı const f64 rb = std::clamp((f64)durum.takimBoncukRadius, 0.0, R); // boncuk radüsü (≤R) std::vector pr; takimGostergeProfilKur(durum.takimCap, durum.takimBoy, durum.takimBoncukRadius, pr); // "Gövde tepesi" (h=boy, r=R) noktasını ÇIKAR — bu sap/gövde ucudur, TEMAS/KESME // yapmaz. Dahil edilirse, eğik (B/C açılı) eksende e·boy terimi (boy≫R olduğundan) // baskın hale gelir ve kaçma X/Y/Z değerlerini gerçekte olmayan biçimde şişirir // (temas her zaman kesici uç/boncuk bölgesinde olur, sap boyunca DEĞİL). if (!pr.empty() && pr.back().h > 1e-6) pr.pop_back(); // Referans noktası: varsayılan olarak kesme UCU (profil h=0). kacmaBoncukMerkezineGore // açıksa VE takım boncuklu (rb>0) ise, referans küre/boncuk merkezine (h=rb) kayar — // her profil noktasının etkin yüksekliği bu referansa göre ölçülür (CNC'de ball-nose // takımlarda ofset genelde küre merkezinden alınır — temas noktası açıya göre kayar // ama küre merkezi sabittir). // ÖNEMLİ: takımın DİBİNE (tutucuya bağlandığı taban / C ucu ofseti = takimKacma) // GÖRE DEĞİL — kesme yapan yere (uç veya boncuk merkezi) göre hesaplanır. takimKacma // salt takımın spindle/C ekseni üzerindeki BAĞLAMA konumunu ifade eder (fiziksel // montaj ofseti); tezgahçının parçaya dokunurken okuyacağı kaçma değerleriyle // İLGİSİZDİR — karışırsa (dibe göre kaydırılırsa) tezgahçı yanlış ofsetle hareket // eder. Bu yüzden h0 buraya KATILMAZ, yalnız hRef (kesme ucu/boncuk merkezi // referansı) uygulanır. NOT: hRef'in web referansında (mesh-tabanlı hull) doğrudan // karşılığı yok — web'de bu ofset zaten mesh geometrisine gömülüdür; burada ayrı // parametre olarak tutuluyor, web ile birebir sayısal karşılaştırma yapılacaksa // test senaryosunda hRef=0 (kacmaBoncukMerkezineGore=false) ile doğrulanmalı. const f64 hRef = (durum.kacmaBoncukMerkezineGore && rb > 1e-6) ? rb : 0.0; const f64 sx = std::sqrt(std::max(0.0, 1.0 - e.x * e.x)); const f64 sy = std::sqrt(std::max(0.0, 1.0 - e.y * e.y)); const f64 sz = std::sqrt(std::max(0.0, 1.0 - e.z * e.z)); f64 minX = 0, maxX = 0, minY = 0, maxY = 0, minZ = 0, maxZ = 0; bool ilk = true; for (const TakimGostergeProfilNok& p : pr) { const f64 h = p.h - hRef; // kesme ucu (veya boncuk merkezi) referanslı — taban/dip DEĞİL const f64 cx = e.x * h, cy = e.y * h, cz = e.z * h; const f64 loX = cx - p.r * sx, hiX = cx + p.r * sx; const f64 loY = cy - p.r * sy, hiY = cy + p.r * sy; const f64 loZ = cz - p.r * sz, hiZ = cz + p.r * sz; if (ilk) { minX = loX; maxX = hiX; minY = loY; maxY = hiY; minZ = loZ; maxZ = hiZ; ilk = false; } else { minX = std::min(minX, loX); maxX = std::max(maxX, hiX); minY = std::min(minY, loY); maxY = std::max(maxY, hiY); minZ = std::min(minZ, loZ); maxZ = std::max(maxZ, hiZ); } } // FİZİKSEL SINIR: kaçma, temas noktasından takım eksenine/ucuna (ya da boncuk // merkezine) olan RADYAL ofsettir — B/C açılı (eğik) takımda gövdenin uzak (h // büyük) noktaları eksene projekte edilince yarıçapı aşan (kol-boyu × açı // karışımı) değerler üretebilir; fiziksel temas noktası hiçbir zaman takım // yarıçapından (R) daha uzakta OLAMAZ. Ham ekstremler ±R'ye kenetlenir. minX = std::clamp(minX, -R, R); maxX = std::clamp(maxX, -R, R); minY = std::clamp(minY, -R, R); maxY = std::clamp(maxY, -R, R); minZ = std::clamp(minZ, -R, R); maxZ = std::clamp(maxZ, -R, R); // TEK değer, eksen başına — seçim KAFANIN (takım ekseni e'nin) o eksendeki // YÖNÜNE göre yapılır: operatör XYZ sıfırlamasını takımın o an baktığı/eğildiği // tarafa göre yapar, bu yüzden e_i'nin işareti hangi ucun (min/max) geçerli // olduğunu belirler — keyfi bir "çapla karşılaştır" sezgiselliği YOK. Vec3d k; k.x = (e.x >= 0.0) ? maxX : minX; k.y = (e.y >= 0.0) ? maxY : minY; k.z = (e.z >= 0.0) ? maxZ : minZ; if (outMin) *outMin = {minX, minY, minZ}; if (outMax) *outMax = {maxX, maxY, maxZ}; return k; } // Doğrusal nokta ofseti: mevcut (veya verilen) takım ekseni (I/J/K) boyunca "mesafeMm" // kadar ilerleyince ulaşılan NOKTANIN X/Y/Z koordinatları — kacmaDegerleriHesapla'nın // (takım gövdesi/profil ekstremleri) TAM TERSİ basitlikte: takım burada NOKTA kabul // edilir, tek işlem birim vektör ölçeklemedir. Önbellek gerekmez (O(1), her kare // güvenle çağrılabilir). "Açı → IJK" ve "IJK → Açı" sekmeleri ORTAK kullanır. Vec3d noktaOfsetiHesapla(const KafaDurum& durum, f64 mesafeMm) { Vec3d e = takimYonHesapla(durum, durum.bAci, durum.cAci); if (e.uzunlukKare() < 1e-9) e = {0, 0, 1}; return e.birim() * mesafeMm; } // kacmaDegerleriHesapla'yı ÖNBELLEKLİ çağırır — B/C açısı veya takım ölçüleri // (çap/boy/kaçma-ofseti/boncuk radüsü/boncuk-merkez bayrağı) değişmediyse önceki // sonucu döndürür, yeniden hesaplamaz. ImGui her kareyi yeniden ÇİZSE de bu ağır // analitik hesap artık kare-başı değil, yalnız DEĞİŞİKLİKTE çalışır. "Açı → IJK" // ve "IJK → Açı" sekmeleri (ikisi de aynı KafaDurum'u okur) ORTAK kullanır. const Vec3d& kacmaDegerleriOnbellekli(KafaDurum& durum) { // NOT: takimKacma (taban/bağlama ofseti) artık kacmaDegerleriHesapla sonucunu // ETKİLEMİYOR (bkz. yukarıdaki not — kesme ucu referanslı, dibe göre değil), // bu yüzden dirty-check'te YOK — değişmesi yeniden hesap gerektirmez. const bool degisti = !durum.kacmaOnbellekGecerli || durum.kacmaSonBAci != durum.bAci || durum.kacmaSonCAci != durum.cAci || durum.kacmaSonCap != durum.takimCap || durum.kacmaSonBoy != durum.takimBoy || durum.kacmaSonBoncukRadius != durum.takimBoncukRadius || durum.kacmaSonBoncukMerkez != durum.kacmaBoncukMerkezineGore; if (degisti) { durum.kacmaOnbellek = kacmaDegerleriHesapla(durum, &durum.kacmaOnbellekMin, &durum.kacmaOnbellekMax); durum.kacmaSonBAci = durum.bAci; durum.kacmaSonCAci = durum.cAci; durum.kacmaSonCap = durum.takimCap; durum.kacmaSonBoy = durum.takimBoy; durum.kacmaSonBoncukRadius = durum.takimBoncukRadius; durum.kacmaSonBoncukMerkez = durum.kacmaBoncukMerkezineGore; durum.kacmaOnbellekGecerli = true; } return durum.kacmaOnbellek; } // ---- Takım göstergesi mesh'i (görsel) ---- // Profili yerel eksende (kBazTakimEksen/+Z, orijin = kesme ucu) döndürerek (revolve) // mesh üretir — cam_simulasyon_penceresi.cpp'deki simTakimMesh ile AYNI yöntem, // yerel-uzay + tutucusuz (bu sadece bir gösterge, gerçek takım kütüphanesi değil). void takimGostergeMeshKur(f64 cap, f64 boy, f64 boncukRadius, MeshVeri& veri) { std::vector pr; takimGostergeProfilKur(cap, boy, boncukRadius, pr); constexpr int kSeg = 24; const Vec3f renk{0.60f, 0.62f, 0.58f}; // mevcut gösterge rengiyle aynı veri.vertexler.clear(); veri.indeksler.clear(); const auto ekleHalka = [&](const TakimGostergeProfilNok& p) -> u32 { const u32 taban = (u32)veri.vertexler.size(); for (int i = 0; i < kSeg; ++i) { const f64 a = 2.0 * kPi * i / kSeg; const f64 x = std::cos(a) * p.r, y = std::sin(a) * p.r; const Vec3d n = (p.r > 1e-6) ? Vec3d{x, y, 0.0}.birim() : Vec3d{0, 0, -1}; Vertex vv{}; vv.konum[0] = (f32)x; vv.konum[1] = (f32)y; vv.konum[2] = (f32)p.h; vv.normal[0] = (f32)n.x; vv.normal[1] = (f32)n.y; vv.normal[2] = (f32)n.z; vv.renk[0] = renk.x; vv.renk[1] = renk.y; vv.renk[2] = renk.z; veri.vertexler.push_back(vv); } return taban; }; u32 alt = ekleHalka(pr[0]); for (size_t k = 1; k < pr.size(); ++k) { const u32 ust = ekleHalka(pr[k]); for (int i = 0; i < kSeg; ++i) { const u32 a0 = alt + (u32)i, a1 = alt + (u32)((i + 1) % kSeg); const u32 b0 = ust + (u32)i, b1 = ust + (u32)((i + 1) % kSeg); veri.indeksler.insert(veri.indeksler.end(), {a0, b0, b1, a0, b1, a1}); } alt = ust; } veri.sinir = Aabbf{}; for (const Vertex& vv : veri.vertexler) veri.sinir.genislet({vv.konum[0], vv.konum[1], vv.konum[2]}); } // SAF açı farkı (derece): verilen B/C için takımın dünya yönü ile hedef I/J/K // arasındaki mutlak açı. Node'lara YAZMAZ → arama sıcak yolunda binlerce kez // güvenle çağrılır (allocation-yok). Web farkHesaplaSessiz() native karşılığı. f64 farkHesapla(const KafaDurum& durum, f64 bAci, f64 cAci) { const Vec3d takimYon = takimYonHesapla(durum, bAci, cAci); const Vec3d hedef{(f64)durum.iDeger, (f64)durum.jDeger, (f64)durum.kDeger}; if (hedef.uzunlukKare() < 1e-12) return 999.0; // hedef tanımsız const f64 d = std::clamp(takimYon.nokta(hedef.birim()), -1.0, 1.0); return std::acos(d) * (180.0 / kPi); } // Verilen C için hedefe en iyi hizalayan B'yi ANALİTİK çözer (arama YOK). // Neden çalışır: qB = Rz(bDeg) — dünya-Z etrafındaki dönüş, v=qC·baz'ın z bileşenini // DEĞİŞTİRMEZ, yalnız azimutunu döndürür. Dolayısıyla B, azimut farkından doğrudan gelir; // hedefe tam ulaşılıp ulaşılamayacağı ise SADECE v(c).z == hedef.z koşuluna bağlıdır. f64 bCozAnalitik(const KafaDurum& durum, f64 cAci, const Vec3d& hedefBirim) { Quatd qB, qC; bcKuaterniyon(durum, 0.0, cAci, qB, qC); // qC yalnız c'ye bağlı (qB kullanılmaz) const Vec3d v = qC.dondur(kBazTakimEksen); if (v.x * v.x + v.y * v.y < 1e-18) return durum.bAci; // v ∥ Z → B serbest (dejenere) const f64 bDeg = (std::atan2(hedefBirim.y, hedefBirim.x) - std::atan2(v.y, v.x)) * (180.0 / kPi); return bAciTers(durum, bDeg); // dünya-Z açısı → kullanıcı B'si (kafaya göre formül) } // Hedef I/J/K'yı sağlayan TÜM B/C duruşlarını bulur (genelde 2). C'yi tarar; her C için // B analitik → problem 1 boyutlu. Sapma eşiğin altına inen bitişik C aralıkları tek bir // çözüm sayılır, tepe içinde inceltilir. Node'lara dokunmaz (farkHesapla saf). void tumCozumleriBul(KafaDurum& durum) { durum.araniyor = true; durum.cozumler.clear(); durum.seciliCozum = -1; const Vec3d hedef{(f64)durum.iDeger, (f64)durum.jDeger, (f64)durum.kDeger}; if (hedef.uzunlukKare() < 1e-12) { durum.araniyor = false; return; } const Vec3d h = hedef.birim(); const auto sapmaC = [&](f64 c) { return farkHesapla(durum, bCozAnalitik(durum, c, h), c); }; constexpr f64 kTara = 0.25; // kaba C taraması constexpr f64 kKabaEsik = 3.0; // bu sapmanın altı = aday tepe constexpr f64 kKabulEsik = 0.05; // inceltme sonrası kabul eşiği (derece) bool icerde = false; f64 enIyiC = 0, enIyiF = 1e30; const auto tepeBitir = [&]() { if (!icerde) return; icerde = false; // Tepe içinde incelt (±kTara, kademeli). f64 c0 = enIyiC, f0 = enIyiF; for (f64 adim : {kTara * 0.2, kTara * 0.02, kTara * 0.002}) { for (f64 cc = c0 - adim * 10; cc <= c0 + adim * 10; cc += adim) { const f64 f = sapmaC(cc); if (f < f0) { f0 = f; c0 = cc; } } } if (f0 > kKabulEsik) return; KafaCozum cz; cz.c = (f32)c0; // tarama zaten [cMin,cMax] içinde — normalize ETME (Ozmo C<0) cz.b = (f32)bCozAnalitik(durum, c0, h); cz.sapma = (f32)f0; // Tekrarı ele (aynı duruş). for (const KafaCozum& v : durum.cozumler) { if (std::abs(v.b - cz.b) < 0.5f && std::abs(v.c - cz.c) < 0.5f) return; } durum.cozumler.push_back(cz); }; for (f64 c = durum.tanim->cMin; c <= durum.tanim->cMax; c += kTara) { const f64 f = sapmaC(c); if (f < kKabaEsik) { if (!icerde) { icerde = true; enIyiF = 1e30; } if (f < enIyiF) { enIyiF = f; enIyiC = c; } } else { tepeBitir(); } } tepeBitir(); // tarama sonunda açık tepe kaldıysa // En küçük sapma önce (eşitlikte mevcut duruşa yakın olan önce → en az hareket). std::sort(durum.cozumler.begin(), durum.cozumler.end(), [&](const KafaCozum& a, const KafaCozum& b) { if (std::abs(a.sapma - b.sapma) > 1e-4f) return a.sapma < b.sapma; const f32 da = std::abs(a.c - durum.cAci) + std::abs(a.b - durum.bAci); const f32 db = std::abs(b.c - durum.cAci) + std::abs(b.b - durum.bAci); return da < db; }); durum.araniyor = false; } // Seçilen duruşa animasyonla git (sıralı B→C tween'i kafaGuncelle uygular). void cozumeGit(KafaDurum& durum, f32 b, f32 c) { durum.bBasla = durum.bAci; durum.cBasla = durum.cAci; durum.bHedef = b; durum.cHedef = c; durum.animT = 0.0f; durum.animVar = true; } // B/C açılarını web kinematiğiyle node'lara uygula: // B = Rz(bKalib + yönlüAçı + 0.5°) @ origin // C = B ∘ (eğik-eksen pivot rotasyonu) → C, B'yi takip eder (parent hiyerarşisi) void kafaAciUygula(KafaDurum& durum) { if (!durum.belge) return; Belge& b = *durum.belge; Quatd qB, qC; bcKuaterniyon(durum, durum.bAci, durum.cAci, qB, qC); Donusumd bDon; // origin pivot (konum {0,0,0}) bDon.yonelim = qB; Donusumd cYerel; // eğik eksen etrafında pivot rotasyonu: x' = qC*(x−P)+P cYerel.yonelim = qC; const Vec3d cPivot = durum.tanim->cPivotMm * kMmOlcek; cYerel.konum = cPivot - qC.dondur(cPivot); const Donusumd cDunya = bDon * cYerel; // parent ∘ child (C, B ile döner) // Takım göstergesi: C'nin çocuğu, kendi dönüşü YOK — web'deki CAcisiAlt/AltAciOzmo'daki // "this.ekle(this.takim)" ile BİREBİR aynı yapı. takimReferansMm, web'in // "takim.position.set(...)" sabitiyle aynı — C'nin YEREL çerçevesinde sabit bir nokta // (pens/mil burnu). FikoTakimFrezeleme'nin kendi yerel orijini KESME UCUdur (gövde +Z'ye // uzar) → dünyada takım ucu = referans − (0,0,boy) − (0,0,kaçma); gösterge mesh'i artık // takimGostergeMeshKur ile doğrudan uç-hizalı (h=0 = kesme ucu) üretiliyor, bu yüzden // ek bir ortalama/kaydırma telafisi GEREKMİYOR (web'deki gibi taban DEĞİL, bu nokta // doğrudan mesh'in yerel orijini olur). const Vec3d takimRefDunya = durum.tanim->takimReferansMm * kMmOlcek; const f64 kacmaDunya = (f64)durum.takimKacma * kMmOlcek; const f64 boyDunya = (f64)durum.takimBoy * kMmOlcek; Donusumd tYerel; tYerel.konum = takimRefDunya - kBazTakimEksen * (boyDunya + kacmaDunya); const Donusumd tDunya = cDunya * tYerel; const auto ayarla = [&](u32 kimlik, const Donusumd& don) { Dugum* d = b.sahne.bul(kimlik); if (!d) return; d->donusum = don; if (DonusumBilesen* db = b.cekirdek.dunya().bul(kimlik)) db->donusum = don; b.cekirdek.olaylar().yayinla(DonusumDegisti{kimlik}, d->ad.c_str()); }; ayarla(durum.bNode, bDon); ayarla(durum.cNode, cDunya); if (durum.takimNode) ayarla(durum.takimNode, tDunya); } // Takım göstergesi geometrisini (çap/boy) yeniden üretir + görünürlüğü uygular. Yalnız // UI'da çap/boy/radüs/göster değişince çağrılır — kare-başı DEĞİL (mesh yeniden üretmek // ucuz değil). Görsel gösterge artık takimBoncukRadius'u da yansıtır (düz/bull/ball-nose). void takimGovdeGuncelle(KafaDurum& durum) { if (!durum.belge || !durum.takimNode) return; Belge& b = *durum.belge; MeshVeri mv; takimGostergeMeshKur(durum.takimCap * kMmOlcek, durum.takimBoy * kMmOlcek, durum.takimBoncukRadius * kMmOlcek, mv); b.sahne.hazirMeshGuncelle(durum.takimNode, mv); Dugum* d = b.sahne.bul(durum.takimNode); if (!d) return; if (d->gorunur != durum.takimGoster) { d->gorunur = durum.takimGoster; b.cekirdek.olaylar().yayinla(GorunurlukDegisti{durum.takimNode, durum.takimGoster}, d->ad.c_str()); } } // Komut satırından IJK hedefini oku: "--ijk=I,J,K" (digerKafayiYeniSurecteAc ekler). // kafaAc bunu araç-kipi açılışında TÜKETİR — yeni pencere BOŞ hedefle değil, gönderen // kafanın "IJK → Açı" sekmesindeki hedefle açılır. bool komutSatirindanIjk(std::string_view args, Vec3d& out) { constexpr std::string_view kAnahtar = "--ijk="; const std::size_t p = args.find(kAnahtar); if (p == std::string_view::npos) return false; const std::string kalan(args.substr(p + kAnahtar.size())); double i = 0, j = 0, k = 0; if (std::sscanf(kalan.c_str(), "%lf,%lf,%lf", &i, &j, &k) != 3) return false; out = {i, j, k}; return true; } // Diagonal ↔ Ozmo birbirini yeni bir fikocad.exe SÜRECİNDE açar (aynı süreçte iki kafa // zaten birlikte çalışabiliyor, ama "araç kipi" satışında müşteri TEK kafayla açılan bir // pencere görür — o pencereden diğer açıyı da görmek isterse ayrı bir süreç gerekir). // Desen "Yeni Belge" / aygıt-kaybı yeniden başlatmasıyla AYNI (bkz. belge_penceresi.cpp): // GetModuleFileNameW + CreateProcessW, komut satırında --arac=. // GÖNDEREN kafanın "IJK → Açı" sekmesindeki hedefi de --ijk=I,J,K ile taşınır (aşağıda // komutSatirindanIjk okur) → yeni pencere BOŞ hedefle değil, aynı yönle açılır. void digerKafayiYeniSurecteAc(const KafaDurum& durum) { const bool digeriOzmo = durum.tanim->tip == KafaTip::Diagonal; const wchar_t* bayrak = digeriOzmo ? L"--arac=ozmo" : L"--arac=diagonal"; wchar_t exeYol[MAX_PATH]{}; GetModuleFileNameW(nullptr, exeYol, MAX_PATH); char ijkAscii[80]; std::snprintf(ijkAscii, sizeof(ijkAscii), " --ijk=%.6f,%.6f,%.6f", (double)durum.iDeger, (double)durum.jDeger, (double)durum.kDeger); const std::wstring ijkGenis(ijkAscii, ijkAscii + std::strlen(ijkAscii)); // ASCII-yalnız → güvenli std::wstring komutSatiri = std::wstring(L"\"") + exeYol + L"\" " + bayrak + ijkGenis; STARTUPINFOW si{}; si.cb = sizeof(si); PROCESS_INFORMATION pi{}; if (CreateProcessW(nullptr, komutSatiri.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) { CloseHandle(pi.hThread); CloseHandle(pi.hProcess); } } } // namespace // Komut satırından sahip pencere HWND'sini oku: "--sahip=" (kafaAyriPencerdeAc // ekler). Ana süreç kendi HWND'sini yeni sürece hex string olarak aktarır → yeni // süreç PencereAyar.sahipTutamac'a atar (Win32 owned-window: z-order sahibinin // üstünde, sahibiyle minimize/restore olur — GERÇEK ayrı OS penceresi, ama ana // pencereye "bağlı" görünür). DIŞ BAĞLANTI (anonim namespace DIŞINDA) — // belge_penceresi.cpp (FikoBelgePenceresi::pencereAyarKur) buradan çağırır. bool komutSatirindanSahip(std::string_view args, void*& out) { constexpr std::string_view kAnahtar = "--sahip="; const std::size_t p = args.find(kAnahtar); if (p == std::string_view::npos) return false; const std::string kalan(args.substr(p + kAnahtar.size())); unsigned long long deger = 0; if (std::sscanf(kalan.c_str(), "%llx", °er) != 1 || deger == 0) return false; out = reinterpret_cast(static_cast(deger)); return true; } // Bu kafayı GERÇEK, bağımsız bir OS penceresinde açar: yeni bir fikocad.exe süreci // (digerKafayiYeniSurecteAc ile AYNI desen — GPU açısından sıfır risk, ayrı VkInstance/ // VkDevice) araç kipinde (--arac=) başlatılır, ama ayrıca // --sahip= ile ÇAĞIRANIN penceresi owner olarak geçirilir. Yeni süreç // PencereAyar.sahipTutamac'a bunu atar (bkz. belge_penceresi.cpp pencereAyarKur) → // Win32 owned-window: z-order çağıranın üstünde, onunla minimize/restore olur, ikinci // monitöre taşınabilir — ama kapanışı BAĞIMSIZDIR (ayrı süreç), ana uygulamayı etkilemez. // sahipHwnd: çağıranın kendi pencere tutamacı (FikoBelgePenceresi::pencere_.dogalTutamac()). void kafaAyriPencerdeAc(KafaTip t, void* sahipHwnd) { const KafaDurum& durum = kafaDurum(t); wchar_t exeYol[MAX_PATH]{}; GetModuleFileNameW(nullptr, exeYol, MAX_PATH); const wchar_t* bayrak = durum.tanim->tip == KafaTip::Diagonal ? L"--arac=diagonal" : L"--arac=ozmo"; std::wstring komutSatiri = std::wstring(L"\"") + exeYol + L"\" " + bayrak; if (durum.iDeger != 0.0f || durum.jDeger != 0.0f || durum.kDeger != 1.0f) { char ijkAscii[80]; std::snprintf(ijkAscii, sizeof(ijkAscii), " --ijk=%.6f,%.6f,%.6f", (double)durum.iDeger, (double)durum.jDeger, (double)durum.kDeger); komutSatiri += std::wstring(ijkAscii, ijkAscii + std::strlen(ijkAscii)); } if (sahipHwnd) { char sahipAscii[32]; std::snprintf(sahipAscii, sizeof(sahipAscii), " --sahip=%llx", (unsigned long long)reinterpret_cast(sahipHwnd)); komutSatiri += std::wstring(sahipAscii, sahipAscii + std::strlen(sahipAscii)); } STARTUPINFOW si{}; si.cb = sizeof(si); PROCESS_INFORMATION pi{}; if (CreateProcessW(nullptr, komutSatiri.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) { CloseHandle(pi.hThread); CloseHandle(pi.hProcess); } } // İki kafanın DEĞİŞMEZ tanımı. Kinematik değerler web kaynağından birebir alındı: // Diagonal → bAcisiUst.ts / cAcisiAlt.ts (C eğik eksen, pivot uzakta, C 0..360) // Ozmo → UstAciOzmo.ts / AltAciOzmo.ts (C dünya -X, pivot (0,0,-83.8), C -94..94) const KafaTanim& kafaTanim(KafaTip t) { static const KafaTanim kTanimlar[kKafaSayi] = { {KafaTip::Diagonal, "FikoYT Diagonal Kafa", "###kafa_diagonal", "wtec-kafa/b_ust.wrl", "wtec-kafa/c_alt.wrl", Vec3d{0.00617, 0.70708, -0.707107}, Vec3d{0.000309, 9.88942737, -279.88942765}, // Web: CAcisiAlt.ts → this.takim.position.set(3.73604353, 199.9648777, -534.98989008 - boy) Vec3d{3.73604353, 199.9648777, -534.98989008}, 32.0f, 150.0f, // Web: FikoTakimFrezeleme(sahne, 32, 16, 150, 50) → çap=32, boy=150 -90.0f, 0.0f, 0.0f, 360.0f, "kafa.diagonal.kamera", "diagonal-kafa.html", "arac.diagonal_kafa"}, {KafaTip::Ozmo, "FikoYT Ozmo Kafa", "###kafa_ozmo", "ozmo-kafa/ust.wrl", "ozmo-kafa/alt.wrl", Vec3d{-1.0, 0.0, 0.0}, Vec3d{0.0, 0.0, -83.8}, // Web: AltAciOzmo.ts → this.takim.position.set(0, 0, -(203.8 + boy)) Vec3d{0.0, 0.0, -203.8}, 20.0f, 100.0f, // Web: FikoTakimFrezeleme(sahne, 20, 10, 100, 50) → çap=20, boy=100 0.0f, 0.0f, -94.0f, 94.0f, "kafa.ozmo.kamera", "ozmo-kafa.html", "arac.ozmo_kafa"}, }; return kTanimlar[(int)t]; } KafaDurum& kafaDurum(KafaTip t) { static KafaDurum s[kKafaSayi]; KafaDurum& d = s[(int)t]; if (!d.tanim) { // ilk erişimde tanımı + varsayılan kalibrasyonu bağla d.tanim = &kafaTanim(t); d.bKalib = d.tanim->bKalibVars; d.cKalib = d.tanim->cKalibVars; d.cAci = std::clamp(d.cAci, d.tanim->cMin, d.tanim->cMax); d.takimCap = d.tanim->takimCapVars; d.takimBoy = d.tanim->takimBoyVars; } return d; } void kafaAc(KafaTip t) { KafaDurum& d = kafaDurum(t); d.acik = true; // "Diğer kafayı aç" ile yeni SÜREÇ başlatıldıysa (--ijk=I,J,K) hedefi devral. Yalnız // araç kipinde VE bu kafa AKTİF araçsa uygulanır — aksi halde tam CAD içinde menüden // normal kafaAc çağrısında eski/kalıntı komut satırı IJK'sı yanlışlıkla üstüne yazmaz. // static guard: kafaAc birden çok kez çağrılabilir (pencere kapatılıp tekrar açılırsa); // komut satırı IJK'sı YALNIZ programın bu çalıştırmasındaki İLK açılışta uygulanır. static bool ijkUygulandi[kKafaSayi] = {}; if (!ijkUygulandi[(int)t]) { ijkUygulandi[(int)t] = true; if (aracSistemi().aktifKimlik() == d.tanim->aracKimlik) { Vec3d ijk; if (komutSatirindanIjk(aracKomutSatiri(), ijk)) { d.iDeger = (f32)ijk.x; d.jDeger = (f32)ijk.y; d.kDeger = (f32)ijk.z; } } } } namespace { void kafaGuncelleTek(KafaDurum& durum, ImGuiKatman& arayuz, VulkanBaglam& vulkan, f32 dt, const std::string& shaderDizin) { if (!durum.acik) { // Panel kapandı: bekleyen duruş varsa hemen yaz (gecikmeyi bekleme — kayıp olmasın). if (durum.kayitKirli && durum.belge) { ayarYaz(kameraAnahtari(durum).c_str(), durusMetni(durum.belge->kamera)); durum.kayitKirli = false; } return; } // Lazy kurulum: belge + offscreen hedef (ilk açılışta). if (!durum.belge) { durum.belge = std::make_unique(vulkan, shaderDizin); durum.belge->sahne.originKur(); durum.hedef = std::make_unique(vulkan, durum.istG, durum.istY); } Belge& b = *durum.belge; // Kafa parçalarını içe aktar (bir kez). Son eklenen düğüm = yeni parça. if (!durum.yuklendi) { // mmCevir=false: kafa pivot/kamera CATIA 0.001 (metre) ölçeğinde kurulu — // kMmOlcek çarpanı + kayıtlı kamera duruşları bu ölçeğe göre (davranış korunur). // tanim->ustYol/altYol GÖRELİ (örn. "wtec-kafa/b_ust.wrl") — kafaAssetKok() ile // birleştirilir; böylece proje/exe hangi PC'de/klasörde olursa olsun bulunur. const std::filesystem::path assetKok = kafaAssetKok(); if (b.sahne.vrmlIceAktar((assetKok / durum.tanim->ustYol).string(), false) && !b.sahne.dugumler().empty()) durum.bNode = b.sahne.dugumler().back().kimlik; if (b.sahne.vrmlIceAktar((assetKok / durum.tanim->altYol).string(), false) && !b.sahne.dugumler().empty()) durum.cNode = b.sahne.dugumler().back().kimlik; // Uca takılı takım göstergesi: gerçek profil mesh'i (takimBoncukRadius'a göre // düz/bull-nose/ball-nose). Konumu HER kare kafaAciUygula'da hesaplanır; burada // yalnız geometri + başlangıç görünürlüğü kurulur. { MeshVeri mv; takimGostergeMeshKur(durum.takimCap * kMmOlcek, durum.takimBoy * kMmOlcek, durum.takimBoncukRadius * kMmOlcek, mv); durum.takimNode = b.sahne.hazirMeshEkle("Takım Göstergesi", VARLIK_YOK, Vec3f{0.60f, 0.62f, 0.58f}, std::move(mv)); if (Dugum* t = b.sahne.bul(durum.takimNode)) t->gorunur = durum.takimGoster; } durum.yuklendi = true; kafaAciUygula(durum); // "En son ayarlanan konum" varsa onu uygula; yoksa ilk açılış → ekrana sığdır. if (!durusYukle(b.kamera, kameraAnahtari(durum).c_str())) belgeSigdir(b); durum.sonYon = b.kamera.yonelim(); durum.sonOdak = b.kamera.odak(); durum.sonMesafe = b.kamera.mesafe(); durum.kayitKirli = false; } // Açı animasyonu: web gibi SIRALI — önce B döner, cGecikme sonra C başlar. // Her eksen kendi easeOutQuad'ıyla; sürerken kareIste(1) → animasyonda uyumaz. if (durum.animVar) { durum.animT += dt; const auto easeOut = [](f32 t) { t = std::clamp(t, 0.0f, 1.0f); return 1.0f - (1.0f - t) * (1.0f - t); }; const f32 kB = easeOut(durum.bSure > 0.0f ? durum.animT / durum.bSure : 1.0f); const f32 kC = easeOut(durum.cSure > 0.0f ? (durum.animT - durum.cGecikme) / durum.cSure : 1.0f); durum.bAci = durum.bBasla + (durum.bHedef - durum.bBasla) * kB; durum.cAci = durum.cBasla + (durum.cHedef - durum.cBasla) * kC; kafaAciUygula(durum); kareIste(1); const f32 toplam = std::max(durum.bSure, durum.cGecikme + durum.cSure); if (durum.animT >= toplam) { durum.bAci = durum.bHedef; durum.cAci = durum.cHedef; kafaAciUygula(durum); durum.animVar = false; } } // Sapma göstergesi: mevcut B/C ile hedef I/J/K arası fark (her kare tazelenir). durum.sapma = (f32)farkHesapla(durum, durum.bAci, durum.cAci); // Belge-scope kare bakımı (belgeHedefleriniGuncelle gövdesinin tek-kayıt eşi). b.kamera.guncelle(dt); kameraKayitIzle(durum, dt); // duruş oturunca "en son konum"u diske yaz (tek sefer) b.sahne.bekleyenleriYenidenInsa(); b.cekirdek.gorevler().pompala(b.cekirdek.olaylar()); if (b.cekirdek.recompute().kirliVar()) b.cekirdek.recompute().hesapla(b.cekirdek, b.cekirdek.olaylar()); durum.hedef->boyutla(durum.istG, durum.istY); b.secim.boyutAyarla(durum.hedef->boyut().width, durum.hedef->boyut().height); if (durum.kayitliGorunum != durum.hedef->renkGorunum()) { if (durum.doku) arayuz.dokuKaldir(durum.doku); durum.doku = arayuz.dokuEkle(vulkan.viewportOrnekleyici(), durum.hedef->renkGorunum()); durum.kayitliGorunum = durum.hedef->renkGorunum(); } } // "Doğrusal Nokta Ofseti" — AYRI, sürüklenebilir küçük pencere (kafa penceresinin // dikeyde uzamasını önlemek için ana pencereden çıkarıldı). Her kafa kendi mesafe // girdisini/sonucunu tutar (durum.noktaMesafe) — iki kafa da açıksa iki ayrı pencere // görünür (kimlikleri KafaTip'e göre benzersiz). Kapatma çarpısı → noktaOfsetiAcik=false. void noktaOfsetiPencereCizTek(KafaDurum& durum) { if (!durum.noktaOfsetiAcik || !durum.tanim) return; char baslik[96]; std::snprintf(baslik, sizeof(baslik), "📐 Doğrusal Nokta Ofseti — %s###noktaOfseti_%d", durum.tanim->ad, (int)durum.tanim->tip); ui::PencereSecenek sec{.boyut = ImVec2(300, 0), .minBoyut = {260, 140}, .pin = false, .bayrak = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking, .skin = ui::PencereSkin::WebCam}; ui::Pencere pen{baslik, &durum.noktaOfsetiAcik, sec}; if (pen) { ImGui::TextWrapped( "Girilen mesafe (± olabilir) kadar, takımın o anki ekseni (I/J/K) boyunca " "ilerleyince ulaşılan noktanın X/Y/Z koordinatları."); // ImGui::TextDisabled("Pozitif mesafe = uzaklaşma, negatif mesafe = yaklaşma."); ImGui::Spacing(); ui::alan("Mesafe (mm)"); ImGui::InputFloat("##noktaMesafeAyri", &durum.noktaMesafe, 0.1f, 1.0f, "%.3f"); const Vec3d nk = noktaOfsetiHesapla(durum, durum.noktaMesafe); ImGui::Spacing(); ImGui::TextColored(ImVec4(0.45f, 0.95f, 0.65f, 1.0f), "X: %.3f", nk.x); ImGui::TextColored(ImVec4(0.45f, 0.95f, 0.65f, 1.0f), "Y: %.3f", nk.y); ImGui::TextColored(ImVec4(0.45f, 0.95f, 0.65f, 1.0f), "Z: %.3f", nk.z); } } // "Kaçma Değerleri" — AYRI, sürüklenebilir küçük pencere (noktaOfsetiPencereCizTek ile // AYNI desen — ana pencereden çıkarıldı). Her kafa kendi kaçma önbelleğini/checkbox // durumunu tutar — iki kafa da açıksa iki ayrı pencere görünür (kimlikleri KafaTip'e // göre benzersiz). Kapatma çarpısı → kacmaAcik=false. void kacmaPencereCizTek(KafaDurum& durum) { if (!durum.kacmaAcik || !durum.tanim) return; char baslik[96]; std::snprintf(baslik, sizeof(baslik), "↔ Kaçma Değerleri — %s###kacma_%d", durum.tanim->ad, (int)durum.tanim->tip); ui::PencereSecenek sec{.boyut = ImVec2(300, 0), .minBoyut = {260, 120}, .pin = false, .bayrak = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking, .skin = ui::PencereSkin::WebCam}; ui::Pencere pen{baslik, &durum.kacmaAcik, sec}; if (pen) { if (durum.takimBoncukRadius > 0.0f) ImGui::Checkbox("Boncuk merkezine göre", &durum.kacmaBoncukMerkezineGore); const Vec3d& kacma = kacmaDegerleriOnbellekli(durum); ImGui::TextColored(ImVec4(0.55f, 0.85f, 1.0f, 1.0f), "X: %.3f", kacma.x); ImGui::TextColored(ImVec4(0.55f, 0.85f, 1.0f, 1.0f), "Y: %.3f", kacma.y); ImGui::TextColored(ImVec4(0.55f, 0.85f, 1.0f, 1.0f), "Z: %.3f", kacma.z); } } // "Takım Göstergesi" — AYRI, sürüklenebilir küçük pencere (noktaOfsetiPencereCizTek / // kacmaPencereCizTek ile AYNI desen — Ayarlar sekmesinden çıkarıldı). Her kafa kendi // takım geometrisini/görünürlüğünü tutar — iki kafa da açıksa iki ayrı pencere görünür // (kimlikleri KafaTip'e göre benzersiz). Kapatma çarpısı → takimGostergeAcik=false. void takimGostergePencereCizTek(KafaDurum& durum) { if (!durum.takimGostergeAcik || !durum.tanim) return; char baslik[96]; std::snprintf(baslik, sizeof(baslik), "🔩 Takım Göstergesi — %s###takimGosterge_%d", durum.tanim->ad, (int)durum.tanim->tip); ui::PencereSecenek sec{.boyut = ImVec2(300, 0), .minBoyut = {260, 200}, .pin = false, .bayrak = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking, .skin = ui::PencereSkin::WebCam}; ui::Pencere pen{baslik, &durum.takimGostergeAcik, sec}; if (pen) { ImGui::TextWrapped( "Kafa ucuna takılı basit bir takım (freze) göstergesi.\n" "Kaçma: takımın tabanını C ucu boyunca kaydırır (gauge/stickout) — " "pozitif dışarı, negatif içeri."); ImGui::Spacing(); bool degistiAyar = false; bool degistiTakimGeo = ImGui::Checkbox("Göster##takimGosterAyri", &durum.takimGoster); ui::alan("Kaçma (mm)"); degistiAyar |= ui::sliderYazilabilir("##takimKacmaAyri", &durum.takimKacma, -50.0f, 150.0f, "%.2f"); ui::alan("Çap (mm)"); degistiTakimGeo |= ImGui::InputFloat("##takimCapAyri", &durum.takimCap, 0.5f, 5.0f, "%.2f"); ui::alan("Boy (mm)"); degistiTakimGeo |= ImGui::InputFloat("##takimBoyAyri", &durum.takimBoy, 1.0f, 10.0f, "%.2f"); ui::alan("Boncuk Radüsü (mm)"); degistiTakimGeo |= ImGui::InputFloat("##takimBoncukRadiusAyri", &durum.takimBoncukRadius, 0.5f, 1.0f, "%.2f"); ImGui::TextDisabled( "0 = düz takım, 0aracKimlik; const ImVec2 kBaslangicBoyut = (durum.sonrakiBoyutX > 1.0f) ? ImVec2{durum.sonrakiBoyutX, durum.sonrakiBoyutY} : ImVec2{720, 560}; ui::PencereSecenek sec{.boyut = kBaslangicBoyut, .minBoyut = {380, 420}, .pinDurum = &durum.ustte, .skin = ui::PencereSkin::WebCam}; if (aracKipi) { sec.tamAlan = true; // konum+boyut her kare viewport'a eşitlenir (OS ile boyutlanır) // NOT: bgAlfa'ya DOKUNMA. Opak WindowBg, WebCam skin'in grid + cam zemin çizimini // örtüyor (denendi: her şey düz gri, grid kayboldu) — skin kendi zeminini çizer. sec.pin = false; // tek pencerede "üstte tut" anlamsız sec.bayrak |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; } const std::string baslik = std::string("🔧 ") + durum.tanim->ad + durum.tanim->pencereKimlik; ui::Pencere pen{baslik.c_str(), aracKipi ? nullptr : &durum.acik, sec}; if (pen) { const ImVec2 avail = ImGui::GetContentRegionAvail(); // Üst = SABİT yükseklikte 3D gösterici (pencere yeniden boyutlansa da değişmez), // alt = kontroller (kalan alanda; gerekiyorsa pencere kaydırır). // Araç kipinde sabit px YERİNE ORANTILI: gösterici yüksekliği = pencere genişliği × // oran → pencere daraldıkça gösterici de küçülür, en-boy oranı korunur (sabit 340 // dar pencerede yatık, geniş pencerede pul gibi kalıyordu). Kontrollere ayrılan // pay kadar tavan uygulanır ki alt blok kırpılmasın. constexpr f32 kKontrolPay = 220.0f; // sekmeler + slider/alan bloğu için ayrılan const f32 kGostericiYuk = aracKipi ? std::clamp(avail.x * aracGostericiOran(), 200.0f, std::max(avail.y - kKontrolPay, 200.0f)) : 340.0f; const ImVec2 imgBoyut(std::max(avail.x, 16.0f), kGostericiYuk); durum.istG = (u32)imgBoyut.x; durum.istY = (u32)imgBoyut.y; const ImVec2 p = ImGui::GetCursorScreenPos(); durum.rectMinX = p.x; durum.rectMinY = p.y; if (durum.doku) ImGui::Image((ImTextureID)durum.doku, imgBoyut); durum.hover = ImGui::IsItemHovered(); { // sim penceresi kimliği: cyan çerçeve + sci-fi HUD köşeleri (saf görsel) ImDrawList* dl = ImGui::GetWindowDrawList(); const ImVec2 mn = ImGui::GetItemRectMin(), mx = ImGui::GetItemRectMax(); dl->AddRect(mn, mx, IM_COL32(46, 220, 255, 90), 4.0f, 0, 1.0f); ui::hudKoseleri(dl, mn, mx); } // Pick imleci (Image-yerel). durum.imlecX = io.MousePos.x - durum.rectMinX; durum.imlecY = io.MousePos.y - durum.rectMinY; // CATIA kamera jestleri (belgePencereleriCiz birebir eşi). OrbitKamera& kamera = durum.belge->kamera; const int btn = (ImGui::IsMouseDown(ImGuiMouseButton_Left) ? 1 : 0) | (ImGui::IsMouseDown(ImGuiMouseButton_Right) ? 2 : 0) | (ImGui::IsMouseDown(ImGuiMouseButton_Middle) ? 4 : 0); const auto kipEsle = [](int b2) { return b2 == 6 ? 0 : b2 == 3 ? 1 : b2 == 4 ? 2 : -1; }; const bool basildi = ImGui::IsMouseClicked(ImGuiMouseButton_Left) || ImGui::IsMouseClicked(ImGuiMouseButton_Right) || ImGui::IsMouseClicked(ImGuiMouseButton_Middle); if (durum.hover) { if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { durum.solBasX = durum.imlecX; durum.solBasY = durum.imlecY; } if (basildi) { durum.kameraKip = kipEsle(btn); durum.kameraKullandi = false; } if (io.MouseWheel != 0.0f) kamera.yakinlastir(io.MouseWheel * 1.7f); if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) belgeSigdir(*durum.belge); } if (btn == 0 && !basildi) durum.kameraKip = -1; const f32 mdx = io.MouseDelta.x, mdy = io.MouseDelta.y; const bool hareket = (mdx != 0.0f || mdy != 0.0f); if (durum.kameraKip == 0) { kamera.dondur(mdx, mdy); if (hareket) durum.kameraKullandi = true; } else if (durum.kameraKip == 1) { kamera.yakinlastir(-mdy * 0.03f); if (hareket) durum.kameraKullandi = true; } else if (durum.kameraKip == 2) { kamera.kaydir(mdx, mdy, io.KeyShift); if (hareket) durum.kameraKullandi = true; } // On-demand render: hover / kamera jesti / tekerlek varsa kare iste (aksi halde uyu). if (durum.hover || durum.kameraKip >= 0 || io.MouseWheel != 0.0f) kareIste(2); // ---- Alt kontroller (ikon sekmelere bölündü → pencere yüksekliği oturur) ---- ImGui::Spacing(); // Başlık yanına yardım "(?)" (web Açıklama/Kalibre/Yön metinleri → tooltip). const auto yardim = [](const char* metin) { ImGui::SameLine(); ImGui::TextDisabled("(?)"); if (ImGui::IsItemHovered()) { ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(320, FLT_MAX)); ImGui::SetTooltip("%s", metin); } }; // Bölüm başlığı: sim penceresi kimliği (vurgu ikon + neon ayraç). SeparatorText // yerine — pencere görünümü CAM Simülasyon ile aynı kalsın. const auto bolum = [&](const char* ikon, const char* metin, const char* ipucu = nullptr) { ImGui::Spacing(); ImGui::TextColored(ui::tema().vurgu, "%s", ikon); ImGui::SameLine(0.0f, 6.0f); ImGui::TextUnformatted(metin); if (ipucu) yardim(ipucu); ui::neonAyrac(); }; // Yardım: Diagonal kafa HTML5 dokümanını aç (çevrimdışı; ileride gömülü CEF). if (ImGui::Button("📖 ?")) yardimAc(durum.tanim->yardimDosya); ImGui::SameLine(); // ImGui::TextDisabled("Bu pencerenin kullanımı için tıkla"); // Kaçma Değerleri artık AYRI bir pencerede (bkz. kacmaPencereCizTek) — // burada yalnız açan buton var, pencere kafaPencereCizTek sonunda çizilir. // ImGui::Spacing(); if (ImGui::Button("↔ Kaçma D.")) durum.kacmaAcik = !durum.kacmaAcik; if (ImGui::IsItemHovered()) ImGui::SetTooltip("Takımın mevcut B/C duruşunda X/Y/Z eksenlerindeki " "kaçma (runout) değerlerini ayrı bir pencerede gösterir."); ImGui::SameLine(); // Doğrusal Nokta Ofseti artık AYRI bir pencerede (bkz. noktaOfsetiPencereCizTek) — // burada yalnız açan buton var, pencere kafaPencereCizTek sonunda çizilir. // ImGui::Spacing(); if (ImGui::Button("📐 Fikosal Ofset")) durum.noktaOfsetiAcik = !durum.noktaOfsetiAcik; if (ImGui::IsItemHovered()) ImGui::SetTooltip("Takım ekseni (I/J/K) boyunca girilen mesafedeki noktanın " "X/Y/Z koordinatlarını ayrı bir pencerede gösterir."); ImGui::SameLine(); // Takım Göstergesi artık AYRI bir pencerede (bkz. takimGostergePencereCizTek) — // burada yalnız açan buton var, pencere kafaPencereCiz sonunda çizilir. if (ImGui::Button("🔩 Takım")) durum.takimGostergeAcik = !durum.takimGostergeAcik; if (ImGui::IsItemHovered()) ImGui::SetTooltip("Kafa ucundaki takım göstergesinin görünürlüğünü/ölçülerini " "(çap, boy, kaçma, boncuk radüsü) ayrı bir pencerede düzenler."); // Diğer kafayı (Diagonal ↔ Ozmo) YENİ SÜREÇTE aç — süreç izolasyonu "Yeni Belge" // ile aynı desen; iki kafa birbirinin kamerasını/pencere durumunu bozmaz. { const bool buOzmo = durum.tanim->tip == KafaTip::Ozmo; const char* digerAd = buOzmo ? "Diagonal" : "Ozmo"; char etiket[48]; std::snprintf(etiket, sizeof(etiket), "🚀 %s Açısını Aç", digerAd); if (ImGui::Button(etiket)) digerKafayiYeniSurecteAc(durum); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s açısını ayrı bir pencerede (yeni fikocad.exe süreci) açar.", digerAd); } } ImGui::Spacing(); if (ui::sekmeBaslat("##wtecSek")) { // ---- SEKME 1: Açı → IJK (B/C ayarla, sonuç takım yönünü OKU) ---- if (ui::sekme("Açı → IJK", "🔄")) { bool degistiAci = false; // Tam genişlik, tek kolon (kolon YOK). Sonuç değerleri (I/J/K, X/Y/Z) her biri // kendi satırında (alt alta) — yan yana sıkıştırılmış tek satır DEĞİL. bolum("🔄", "B / C Açısı", "B ve C açısını ayarlayın; takımın dünya yön vektörü (I/J/K) aşağıda " "hesaplanır. Kalibrasyon ve yön \"Ayarlar\" sekmesinde.\n" "Çubuğa ÇİFT TIKLA → açıyı klavyeden yaz (Ctrl+Click de çalışır)."); ui::alan("B° (Üst)"); degistiAci |= ui::sliderYazilabilir("##bAci", &durum.bAci, 0.0f, 360.0f, "%.3f"); ui::alan("C° (Alt)"); degistiAci |= ui::sliderYazilabilir("##cAci", &durum.cAci, durum.tanim->cMin, durum.tanim->cMax, "%.3f"); // İleri kinematik: mevcut B/C → takım yönü (I/J/K). const Vec3d ijk = takimYonHesapla(durum, durum.bAci, durum.cAci); const f32 sonucBasligY = ImGui::GetCursorPosY(); bolum("➜", "Sonuç Takım Yönü (I/J/K)"); { // Kopyala butonu başlık satırının SAĞINA bindirilir (bkz. IJK→Açı // sekmesindeki "3d Seç" butonuyla AYNI desen): "bolum" zaten tam // genişlik kapladığından SameLine() çalışmaz, MUTLAK konum kullanılır. // basligY, bolum() ÇAĞRILMADAN ÖNCE (satır çizilmeden önce) kaydedildi. const ImVec2 akisKonumSonuc = ImGui::GetCursorPos(); // bolum() sonrası normal akış const char* kopyalaEtiket = "📋"; const ImVec2 kopyalaMetin = ImGui::CalcTextSize(kopyalaEtiket); const ImVec2 dolgu = ImGui::GetStyle().FramePadding; const f32 kopyalaGenislik = kopyalaMetin.x + dolgu.x * 2.0f; const f32 kopyalaYukseklik = kopyalaMetin.y + dolgu.y * 2.0f; const f32 dikeyOrta = sonucBasligY + (ImGui::GetTextLineHeight() - kopyalaYukseklik) * 0.5f; const f32 sagX = ImGui::GetWindowContentRegionMax().x - kopyalaGenislik; ImGui::SetCursorPos(ImVec2(sagX, dikeyOrta)); if (ImGui::Button(kopyalaEtiket)) { durum.iDeger = (f32)ijk.x; durum.jDeger = (f32)ijk.y; durum.kDeger = (f32)ijk.z; } if (ImGui::IsItemHovered()) ImGui::SetTooltip("I/J/K'yı hedefe kopyala"); ImGui::SetCursorPos(akisKonumSonuc); // normal akışa dön — I/J/K satırları bozulmasın } ImGui::TextColored(ImVec4(0.35f, 0.95f, 0.55f, 1.0f), "I = %.4f", ijk.x); ImGui::TextColored(ImVec4(0.35f, 0.95f, 0.55f, 1.0f), "J = %.4f", ijk.y); ImGui::TextColored(ImVec4(0.35f, 0.95f, 0.55f, 1.0f), "K = %.4f", ijk.z); // // Kaçma Değerleri artık AYRI bir pencerede (bkz. kacmaPencereCizTek) — // // burada yalnız açan buton var, pencere kafaPencereCizTek sonunda çizilir. // ImGui::Spacing(); // if (ImGui::Button("↔ Kaçma Değerleri")) durum.kacmaAcik = true; // if (ImGui::IsItemHovered()) // ImGui::SetTooltip("Takımın mevcut B/C duruşunda X/Y/Z eksenlerindeki " // "kaçma (runout) değerlerini ayrı bir pencerede gösterir."); // // Doğrusal Nokta Ofseti artık AYRI bir pencerede (bkz. noktaOfsetiPencereCizTek) — // // burada yalnız açan buton var, pencere kafaPencereCizTek sonunda çizilir. // ImGui::Spacing(); // if (ImGui::Button("📐 Doğrusal Nokta Ofseti")) durum.noktaOfsetiAcik = true; // if (ImGui::IsItemHovered()) // ImGui::SetTooltip("Takım ekseni (I/J/K) boyunca girilen mesafedeki noktanın " // "X/Y/Z koordinatlarını ayrı bir pencerede gösterir."); // Slider elle değişimi → anında uygula (animasyon YOK). if (degistiAci) { durum.animVar = false; kafaAciUygula(durum); kareIste(2); } ui::sekmeBitir(); } // ---- SEKME 2: IJK → Açı (I/J/K hedefi ver, B/C açısını BUL) ---- if (ui::sekme("IJK → Açı", "🎯")) { bool degistiIJK = false; const bool buKafaBekliyor = bglm.ijkSecimBekliyor && bglm.ijkSecimKafaIndex == (int)durum.tanim->tip; // NOT: buradaki "bolum" ui::bolum (CollapsingHeader) DEĞİL — bu fonksiyonun // İÇİNDE tanımlı yerel bir lambda (yk. bkz. "auto bolum = [&](...)"): ikon + // başlık + "(?)" yazar, ALTINA ui::neonAyrac() ile bir ayraç çizgisi çeker ve // cursoru yeni satıra bırakır. SameLine() bu yüzden butonu başlık satırına // değil, ayraç çizgisinin SONUNA yapıştırıyordu → pencere dışına taşma (önceki // hata). Doğru çözüm: başlık satırının Y'sini ÖNCEDEN kaydet, bolum() bittikten // sonra MUTLAK konumla (satır Y'si + sağ kenar) butonu bindir, ardından normal // akış konumuna GERİ DÖN (I/J/K alanları bolum()'un normal akışından devam etsin). const f32 basligSatirY = ImGui::GetCursorPosY(); // Tam genişlik, tek kolon (kolon YOK). Sonuç değerleri (X/Y/Z) alt alta. bolum("🎯", "Takım Yönü (I/J/K)", "Takımın olması istenen dünya yön vektörü.\n\"Açıyı Bul\" bu yöne en yakın " "B/C açılarını arar (sapma ~0'a iner) ve animasyonla oraya gider."); const ImVec2 akisKonum = ImGui::GetCursorPos(); // bolum() sonrası normal akış { // "3d Seç": ana viewport'ta bir sonraki yüz/kenar tıklaması bu kafanın // I/J/K'sını doldurur (bkz. belge_penceresi.cpp tık-seçim zinciri). const char* uc3dEtiket = buKafaBekliyor ? "✋ İptal" : "🖱 3d Seç"; const ImVec2 uc3dMetin = ImGui::CalcTextSize(uc3dEtiket); const ImVec2 dolgu = ImGui::GetStyle().FramePadding; const f32 uc3dGenislik = uc3dMetin.x + dolgu.x * 2.0f; const f32 uc3dYukseklik = uc3dMetin.y + dolgu.y * 2.0f; const f32 dikeyOrta = basligSatirY + (ImGui::GetTextLineHeight() - uc3dYukseklik) * 0.5f; const f32 sagX = ImGui::GetWindowContentRegionMax().x - uc3dGenislik; ImGui::SetCursorPos(ImVec2(sagX, dikeyOrta)); if (buKafaBekliyor) { if (ImGui::Button(uc3dEtiket)) { bglm.ijkSecimBekliyor = false; bglm.ijkSecimKafaIndex = -1; g_surecKopru.hepsineGonder("ijk.mod_kapat"); // diğer süreçleri de silahsızlandır } } else if (ImGui::Button(uc3dEtiket)) { bglm.ijkSecimBekliyor = true; bglm.ijkSecimKafaIndex = (int)durum.tanim->tip; // Süreçler-arası: diğer açık fikocad.exe pencerelerine de "bu PID bekliyor" // yayınla (bkz. uygulama_baglam.hpp ijkUzakKaynakPid + belge_penceresi.cpp // IPC drain). g_surecKopru.hepsineGonder("ijk.mod_ac"); } ImGui::SetCursorPos(akisKonum); // normal akışa dön — I/J/K alanları bozulmasın } if (buKafaBekliyor) { ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.25f, 1.0f), "Ana görünümde bir yüz veya düz kenara tıklayın"); ImGui::TextDisabled("(Diğer açık fikocad pencerelerinde de tıklanabilir)"); } ui::alan("I"); degistiIJK |= ImGui::InputFloat("##ijkI", &durum.iDeger, 0.1f, 1.0f, "%.3f"); ui::alan("J"); degistiIJK |= ImGui::InputFloat("##ijkJ", &durum.jDeger, 0.1f, 1.0f, "%.3f"); ui::alan("K"); degistiIJK |= ImGui::InputFloat("##ijkK", &durum.kDeger, 0.1f, 1.0f, "%.3f"); // "Sapma" da AYNI şekilde sağa dayalı — metin değere göre genişlik değiştirdiği // için önce biçimlenir, genişliği ölçülür, sonra cursor sağa kaydırılır. { char sapmaMetin[48]; std::snprintf(sapmaMetin, sizeof(sapmaMetin), "Sapma: %.4f°", durum.sapma); const f32 sapmaGenislik = ImGui::CalcTextSize(sapmaMetin).x; const f32 kalan = ImGui::GetContentRegionAvail().x; if (kalan > sapmaGenislik) // ImGui::SetCursorPosX(ImGui::GetCursorPosX() + kalan - sapmaGenislik); ImGui::TextColored(durum.sapma < 0.01f ? ImVec4(0.35f, 0.95f, 0.55f, 1.0f) : ImVec4(1.0f, 0.85f, 0.25f, 1.0f), "%s", sapmaMetin); ImGui::SameLine(); // "Açıyı Bul" pencerenin sağına dayalı — buton genişliği (etiket + iç dolgu) // kadar geriden başlatılır ki sağ kenara hizalansın (etiket "Hesaplanıyor..." // iken de "🎯 Açıyı Bul" iken de doğru hizalanır, ikisi FARKLI genişlikte). const char* aciBulEtiket = durum.araniyor ? "Hesaplanıyor..." : "🎯 Açıyı Bul"; const f32 aciBulGenislik = ImGui::CalcTextSize(aciBulEtiket).x + ImGui::GetStyle().FramePadding.x * 2.0f; const f32 kalanGenislik = ImGui::GetContentRegionAvail().x; if (kalanGenislik > aciBulGenislik) ImGui::SetCursorPosX(ImGui::GetCursorPosX() + kalanGenislik - aciBulGenislik); ImGui::BeginDisabled(durum.araniyor || durum.animVar); if (ImGui::Button(aciBulEtiket)) { tumCozumleriBul(durum); // TÜM duruşlar (genelde 2) if (!durum.cozumler.empty()) { durum.seciliCozum = 0; // en iyi/en yakın duruşa git cozumeGit(durum, durum.cozumler[0].b, durum.cozumler[0].c); } kareIste(4); } ImGui::EndDisabled(); } ImGui::Spacing(); ImGui::TextColored(ImVec4(0.55f, 0.85f, 1.0f, 1.0f), "Bulunan: B = %.2f° C = %.2f°", durum.bAci, durum.cAci); // // Kaçma Değerleri artık AYRI bir pencerede (bkz. kacmaPencereCizTek) — // // burada yalnız açan buton var, pencere kafaPencereCizTek sonunda çizilir. // ImGui::Spacing(); // if (ImGui::Button("↔ Kaçma Değerleri##ijk")) durum.kacmaAcik = true; // if (ImGui::IsItemHovered()) // ImGui::SetTooltip("Takımın mevcut B/C duruşunda X/Y/Z eksenlerindeki " // "kaçma (runout) değerlerini ayrı bir pencerede gösterir."); // // Doğrusal Nokta Ofseti artık AYRI bir pencerede (bkz. noktaOfsetiPencereCizTek) — // // burada yalnız açan buton var, pencere kafaPencereCizTek sonunda çizilir. // ImGui::Spacing(); // if (ImGui::Button("📐 Doğrusal Nokta Ofseti##ijk")) durum.noktaOfsetiAcik = true; // if (ImGui::IsItemHovered()) // ImGui::SetTooltip("Takım ekseni (I/J/K) boyunca girilen mesafedeki noktanın " // "X/Y/Z koordinatlarını ayrı bir pencerede gösterir."); // ---- Aynı yönü sağlayan TÜM duruşlar ---- if (!durum.cozumler.empty()) { bolum("⚖", "Duruşlar (aynı takım yönü)", "B ekseni dünya-Z'de döndüğü için aynı I/J/K yönü genelde İKİ farklı " "B/C duruşuyla elde edilir (takım aynı yöne bakar, kafa farklı katlanır).\n" "Çarpışma, eksen limiti veya en az hareket için birini seç."); for (int i = 0; i < (int)durum.cozumler.size(); ++i) { const KafaCozum& cz = durum.cozumler[i]; char et[128]; std::snprintf(et, sizeof(et), "%d) B = %8.3f° C = %8.3f° (sapma %.4f°)##cz%d", i + 1, cz.b, cz.c, cz.sapma, i); if (ImGui::Selectable(et, durum.seciliCozum == i)) { durum.seciliCozum = i; cozumeGit(durum, cz.b, cz.c); kareIste(4); } } } else if (durum.seciliCozum == -1 && durum.sapma > 0.5f) { ImGui::TextDisabled("(Açıyı Bul'a bas — bu yönü sağlayan tüm duruşlar listelenir)"); } // Değer değişiminde kare iste (on-demand → değişmeden uyanmaz). if (degistiIJK) { durum.cozumler.clear(); durum.seciliCozum = -1; kareIste(2); } ui::sekmeBitir(); } // ---- SEKME 3: Ayarlar (kalibrasyon + yön + görünüm) ---- if (ui::sekme("Ayarlar", "⚙")) { bool degistiAyar = false; bolum("⚙", "B Kalibrasyon / Yön", "Eğer B ve C açısı sıfırdayken 3D görüntüdeki gibi görünmüyorsa Kalibrasyon " "ile ayarlayın.\nKalibrasyon: sıfır konum ofseti.\nYön: açının saat yönünde " "mi tersinde mi arttığını belirler."); ui::alan("B Kalibrasyon"); degistiAyar |= ImGui::InputFloat("##bKalib", &durum.bKalib, 1.0f, 10.0f, "%.1f"); { const char* yonlar[] = {"Saat Tersi", "Saat Yönü"}; int yi = durum.bYon - 1; ui::alan("B Yön"); if (ImGui::Combo("##bYon", &yi, yonlar, 2)) { durum.bYon = yi + 1; degistiAyar = true; } } bolum("⚙", "C Kalibrasyon / Yön"); ui::alan("C Kalibrasyon"); degistiAyar |= ImGui::InputFloat("##cKalib", &durum.cKalib, 1.0f, 10.0f, "%.1f"); { const char* yonlar[] = {"Saat Tersi", "Saat Yönü"}; int yi = durum.cYon - 1; ui::alan("C Yön"); if (ImGui::Combo("##cYon", &yi, yonlar, 2)) { durum.cYon = yi + 1; degistiAyar = true; } } bolum("🎨", "Görünüm"); ImGui::Checkbox("Işık", &durum.isikAcik); ImGui::SameLine(); if (ImGui::Button("Ekrana Sığdır")) belgeSigdir(*durum.belge); ImGui::TextDisabled("Izgara boyu: Ayarlar → Pencere"); // bolum("🔩", "Takım Göstergesi", // "Kafa ucuna takılı basit bir takım (freze) göstergesi.\n" // "Görünürlük/çap/boy/kaçma/boncuk radüsü artık AYRI bir pencerede " // "(üstteki \"🔩 Takım\" butonu)."); // if (ImGui::Button("🔩 Takım Göstergesini Aç")) // durum.takimGostergeAcik = !durum.takimGostergeAcik; // Kalibrasyon/yön değişimi kinematiği değiştirir → anında uygula. if (degistiAyar) { durum.animVar = false; kafaAciUygula(durum); kareIste(2); } ui::sekmeBitir(); } ui::sekmeKapat(); } } } // ui::Pencere pen yıkıcısı (End) burada → sonra stil pop güvenli ImGui::PopStyleVar(kVarSay); } void kafaKaydetTek(KafaDurum& durum, VulkanBaglam& vulkan, BoruHatlari& hatlar, const IsikDurum& isikDurum, VkCommandBuffer kb, f32 zaman, int kare) { if (!durum.acik || !durum.belge || !durum.hedef) return; BelgeCizimGirdi g; g.zaman = zaman; g.kare = kare; g.imlecX = durum.imlecX; g.imlecY = durum.imlecY; g.vpHover = durum.hover; g.navigasyonAktif = durum.kameraKip >= 0; // nav sürerken pick duraklat g.kameraHareketli = durum.belge->kamera.hareketliMi(); // B#2: yumuşatma oturana kadar da belgeCiz(*durum.belge, *durum.hedef, vulkan, hatlar, durum.cizim, isikDurum, kb, g, durum.sonEnBoy, durum.sonG, durum.sonY); } } // namespace // ---- Public 3-fazlı akış: HER kafa için tekil işi çağırır (kapalı olan ucuz çıkar) ---- void kafaGuncelle(ImGuiKatman& arayuz, VulkanBaglam& vulkan, f32 dt, const std::string& shaderDizin, UygulamaBaglam& bglm) { for (int i = 0; i < kKafaSayi; ++i) kafaGuncelleTek(kafaDurum((KafaTip)i), arayuz, vulkan, dt, shaderDizin); // Bekleyen IJK hedefi (DM bağlam menüsü → "Bu Yönü IJK Hedefine Gönder"): AÇIK ilk // kafanın I/J/K alanlarına yaz + TÜKET (tek-seferlik "yapıştır" — pano gibi; bkz. // uygulama_baglam.hpp). Hiçbir kafa açık değilse bekler (bir sonraki açılışta doldurulur). if (bglm.bekleyenIjkHedef) { // İstek belirli bir kafadan geldiyse ("🖱 3d Seç") ONA git; belirsizse // (bağlam menüsü yolu, kafa belirtmez) AÇIK ilk kafaya düş. const int hedefKafa = bglm.ijkSecimKafaIndex; bglm.ijkSecimKafaIndex = -1; for (int i = 0; i < kKafaSayi; ++i) { KafaDurum& d = kafaDurum((KafaTip)i); if (!d.acik) continue; if (hedefKafa >= 0 && hedefKafa != i) continue; const Vec3d& v = *bglm.bekleyenIjkHedef; d.iDeger = (f32)v.x; d.jDeger = (f32)v.y; d.kDeger = (f32)v.z; d.cozumler.clear(); d.seciliCozum = -1; bglm.bekleyenIjkHedef.reset(); kareIste(2); break; // yalnız hedef (ya da ilk açık) kafa alır } bglm.bekleyenIjkHedef.reset(); // hedef kafa artık kapalıysa da bekleyen isteği düşür } } void kafaPencereCiz(VulkanBaglam& vulkan, UygulamaBaglam& bglm) { for (int i = 0; i < kKafaSayi; ++i) kafaPencereCizTek(kafaDurum((KafaTip)i), vulkan, bglm); // Doğrusal Nokta Ofseti penceresi ana kafa penceresinden BAĞIMSIZ — kafa penceresi // kapansa bile (durum.acik=false) açık kalabilir, kullanıcı ayrıca kapatana dek durur. for (int i = 0; i < kKafaSayi; ++i) noktaOfsetiPencereCizTek(kafaDurum((KafaTip)i)); // Kaçma Değerleri penceresi de AYNI şekilde ana kafa penceresinden BAĞIMSIZ. for (int i = 0; i < kKafaSayi; ++i) kacmaPencereCizTek(kafaDurum((KafaTip)i)); // Takım Göstergesi penceresi de AYNI şekilde ana kafa penceresinden BAĞIMSIZ. for (int i = 0; i < kKafaSayi; ++i) takimGostergePencereCizTek(kafaDurum((KafaTip)i)); } void kafaKaydet(VulkanBaglam& vulkan, BoruHatlari& hatlar, const IsikDurum& isikDurum, VkCommandBuffer kb, f32 zaman, int kare) { for (int i = 0; i < kKafaSayi; ++i) kafaKaydetTek(kafaDurum((KafaTip)i), vulkan, hatlar, isikDurum, kb, zaman, kare); } } // namespace fiko