#pragma once // Move Face gizmosu (Doğrudan Modelleme) — seçili DÜZ yüzde 3-eksen (X/Y/Z) tutamaç triadı. // Bir eksen oku sürüklenince yüz o DÜNYA-ekseni boyunca canlı önizlenir (ucuz wireframe, // OCCT yok); bırakışta parametrik "yuz_tasi" feature'ı (BRepFeat_MakePrism, async worker) // uygulanır → ürün ağacında düzenlenebilir (tasiX/Y/Z), provenance korunur. // Push/Pull normal-ok'undan farkı: KEYFİ yön (teğet kayma / eğik hareket → komşu duvarlar esner). // Rotate ekseni Faz 2 (yeni geometri::yuzDondur gerekir). Yalnız TEK düz yüz + tek gövde (MVP). #include #include #include #include #include #include #include #include "fiko/cekirdek/cekirdek.hpp" #include "fiko/kinematik/mekanizma.hpp" // Montaj Kısıtları canlı takip (baglıGovdeleriBul) #include "dm_tutamac_olay.hpp" #include "fiko/parca/part_design_sistemi.hpp" #include "fiko/render/orbit_kamera.hpp" #include "fiko/sahne/sahne.hpp" #include "fiko/sahne/sahne_islem.hpp" // DuzenleIslem (döndürme=draft undo) #include "fiko/ui/bilesen_ek.hpp" #include "fiko/ayar/ayar_sistemi.hpp" #include "dogrudan_ayar.hpp" #include "gizmo_gorsel.hpp" #include "imgui.h" namespace fiko { // Async bekleyen taşıma işi — İŞLEM-ID'li (push_pull kalıbı): bitti callback'i yalnız kendi // kaydını siler, üst üste jestler birbirinin hayaletini ezmez. struct YuzTasiBekleyen { u32 id = 0; u32 fk = 0; // feature kimliği (× iptal → featureIptal; async sonuç güvenle atılır) Vec3f vec{}; // dünya öteleme vektörü (normal-ok VEYA serbest-taşı; eğik olabilir) f32 aci = 0; // döndürme işi (yuz_dondur): 0 = öteleme işi → etiket mm yerine ° gösterir Vec3f merkez{}; std::vector> kontur; // bırakış anındaki dünya konturu }; struct YuzTasiDurum { bool aktif = false; // gizmo modu açık mı (bağlam menüsü/komutla açılan geçici araç) bool surukluyor = false; bool tutamacYakin = false; // imleç tutamaçta — TUŞTAN BAĞIMSIZ (dokunmatik; bkz. kenar_fillet.hpp) u32 govde = 0, yuz = 0; // hedef gövde + düz yüz ordinali Vec3f merkezD{}; // dünya yüz merkezi (triad ankrajı) Vec3f eksenlerD[3]{}; // YÜZ çerçevesi (dünya): [0]=U teğet, [1]=V teğet, [2]=N normal // Tutamaç kodları: 0/1/2 = U/V/N translate oku · 3 = merkez serbest-taşı · 10/11/12 = U/V/N döndürme yayı int eksen = -1; // sürüklenen tutamaç int hoverEksen = -1; // fare altındaki tutamaç f32 mesafe = 0; // translate modunda canlı işaretli mesafe (mm) f32 aci = 0; // döndürme (draft) modunda canlı açı (derece) f32 olcekBaz = 0, olcekFak = 1; // gövde uniform-scale: başlangıç ekran-uzaklığı + canlı çarpan Vec3f serbestVec{}; // serbest-taşı modunda birikmiş DÜNYA öteleme vektörü (eğik hareket) Vec3f serbestN{}, serbestKok{}; // serbest sürükleme düzlemi normali + başlangıç vuruş noktası bool blokKaydir = false; // Ctrl+merkez sürükle → çıkıntının TAMAMINI taban boyunca rijit kaydır // (cikinti_kaydir feature; yuz_tasi tek yüz taşır, bu tüm bloğu) Vec3f tabanNoktaD{}, tabanNormalD{}; // taban düzlemi DÜNYA (döndürme önizleme pivotu) Vec3f tabanYerelP{}, tabanYerelN{}; // taban düzlemi YEREL (draft nötr + çekme; yuzDraft yerel brep'te çalışır) bool tabanVar = false; // taban düzlemi hesaplandı mı (rotate-drag başında LAZY — seçimde DEĞİL, donma önler) int yonMod = 0; // translate/planar yönelimi: 0=Yüz çerçevesi (U/V/N) · 1=Dünya (X/Y/Z). W ile döngü bool govdeMod = false; // GÖVDE modu (alt=0 tüm gövde seçili) → translate node dönüşümü (yüz feature değil) std::vector govdeler; // ÇOKLU gövde seçimi: hepsi ortak pivot (merkezD) etrafında transform, tek GrupIslem undo char tipBuf[24] = ""; // translate modunda yazılan sayı → hassas mesafe (drag override) u64 hedefImza = 0; // seçim imzası (değişince yüz/kontur yeniden örneklenir) std::vector> kontur; // seçili yüzün dünya konturu (önizleme) std::vector bekleyenler; u32 bekleyenSayac = 0; }; namespace yuztasi_ic { // [2]=Normal en parlak/ayırt edici (birincil eksen; push/pull normal-oku ile aynı his). inline ImU32 eksenRenk(int e, bool parlak) { const int a = parlak ? 255 : 205; return e == 0 ? IM_COL32(238, 96, 96, a) : e == 1 ? IM_COL32(120, 226, 120, a) : IM_COL32(120, 220, 255, a); } // Yüz normalinden (dünya) sağ-elli çerçeve: U ⟂ N, V = N×U. ref, N'e en dik dünya ekseni. inline void cerceveKur(const Vec3f& N, Vec3f& U, Vec3f& V) { const Vec3f ref = std::abs(N.x) < 0.9f ? Vec3f{1, 0, 0} : Vec3f{0, 1, 0}; U = N.capraz(ref).birim(); V = N.capraz(U).birim(); } // Montaj Kısıtları DOF'a saygılı canlı takip ──────────────────────────────────────────── // belge_penceresi.cpp'deki montajKisitUygula yalnız Sabit/Doner/Duzlemsel mafsal üretir // (kisit.hpp eşlemesi), ama Lua/MCP başka tipler de kurabilir → tam switch yazıldı. // Bir mafsalın hareketini HEDEF göreli dönüşüme PROJE eder (izin verilmeyen bileşenler // atılır), m.q/q2/q3'e YAZAR. hedefHareket, mafsalDonusumu'nun TERSİ mantığıyla elde // edilir: capa^-1 * (ana^-1 * cocukHam) * capaCocuk (bkz. mekanizma.cpp mafsalDonusumu). inline void mafsalHareketProjeEt(kinematik::Mafsal& m, const Donusumd& hedefHareket) { using kinematik::MafsalTip; const Quatd& q = hedefHareket.yonelim; switch (m.tip) { case MafsalTip::Sabit: break; // 0 DOF — hiçbir bileşen yazılmaz case MafsalTip::Doner: m.q = 2.0 * std::atan2(q.z, q.w); break; case MafsalTip::Kayar: m.q = hedefHareket.konum.z; break; case MafsalTip::Silindirik: m.q = 2.0 * std::atan2(q.z, q.w); m.q2 = hedefHareket.konum.z; break; case MafsalTip::Vida: m.q = 2.0 * std::atan2(q.z, q.w); // öteleme adımdan TÜRER (mafsalDonusumu) — yazma break; case MafsalTip::Kuresel: { // Standart ZYX Euler çıkarımı (mafsalDonusumu: Rz(q3)*Ry(q2)*Rx(q) sırasıyla kurar). const f64 sinp = std::clamp(2.0 * (q.w * q.y - q.z * q.x), -1.0, 1.0); m.q2 = std::asin(sinp); m.q = std::atan2(2.0 * (q.w * q.x + q.y * q.z), 1.0 - 2.0 * (q.x * q.x + q.y * q.y)); m.q3 = std::atan2(2.0 * (q.w * q.z + q.x * q.y), 1.0 - 2.0 * (q.y * q.y + q.z * q.z)); break; } case MafsalTip::Duzlemsel: m.q = hedefHareket.konum.x; m.q2 = hedefHareket.konum.y; m.q3 = 2.0 * std::atan2(q.z, q.w); break; case MafsalTip::Serbest: m.q = hedefHareket.konum.x; m.q2 = hedefHareket.konum.y; m.q3 = hedefHareket.konum.z; break; } } // Sürüklenen gövdenin (driverSahne) HAM hedef dünya dönüşümü (driverHam, henüz mekanizma // kısıtı uygulanmamış — gizmo matematiğinin çıktısı) verilir. Driver'ın mekanizmadaki TEK // YUKARI (ana) bağlantısı varsa VE Sabit DEĞİLSE, o mafsalın izin verdiği eksende PROJE // edilip driver'ın GERÇEK konumu bulunur (joint'in izin vermediği yöne çekilirse gövde en // yakın uygun konuma "yapışır" — CATIA/SolidWorks mate davranışı). Sabit ise üst uzuv da // rijit birlikte taşınır (kaynak). Oradan AŞAĞI doğru: Sabit=rijit, diğerleri=MEVCUT q ile // rijit taşıma (bir ucu tutup mafsalı taşımak q'yu DEĞİŞTİRMEZ) BFS ile tüm ağaç gezilir. // Döndürür: {sahne kimlik -> yeni dünya dönüşümü} (driver DAHİL — üst bağlantı projekte // edildiyse driver'ın kendi girdisi de HAM değerden FARKLI olabilir). inline std::unordered_map montajTakipHesapla(kinematik::Mekanizma& mek, u32 driverSahne, const Donusumd& driverHam) { using kinematik::MafsalTip; std::unordered_map sonuc; const auto& uz = mek.uzuvlar(); const auto& mf = mek.mafsallar(); std::size_t driverU = uz.size(); for (std::size_t i = 0; i < uz.size(); ++i) if (uz[i].sahne == driverSahne) { driverU = i; break; } if (driverU == uz.size()) { sonuc[driverSahne] = driverHam; return sonuc; } // mekanizmada yok // 1) Driver'ın TEK üst (ana) mafsalını bul (driver == cocuk olduğu mafsal — ağaçta ≤1). std::size_t ustMafsal = mf.size(); for (std::size_t i = 0; i < mf.size(); ++i) if (mf[i].cocuk == driverU) { ustMafsal = i; break; } Donusumd driverGercek = driverHam; if (ustMafsal < mf.size() && mf[ustMafsal].tip != MafsalTip::Sabit && mf[ustMafsal].ana < uz.size()) { const std::size_t parentU = mf[ustMafsal].ana; const Donusumd worldParent = uz[parentU].taban; // üst uzuv bu sürüklemede SABİT kabul edilir const Donusumd hedefHareket = mf[ustMafsal].capa.ters() * (worldParent.ters() * driverHam) * mf[ustMafsal].capaCocuk; kinematik::Mafsal* mYaz = mek.mafsal(ustMafsal); mafsalHareketProjeEt(*mYaz, hedefHareket); driverGercek = worldParent * kinematik::mafsalDonusumu(*mYaz); } sonuc[driverSahne] = driverGercek; std::vector ziyaret(uz.size(), false); ziyaret[driverU] = true; std::vector> kuyruk{{driverU, driverGercek}}; // Sabit üst-mafsal ise üst uzuv da rijit gruba katılır (kaynak — birlikte taşınır). if (ustMafsal < mf.size() && mf[ustMafsal].tip == MafsalTip::Sabit && mf[ustMafsal].ana < uz.size()) { const std::size_t parentU = mf[ustMafsal].ana; if (!ziyaret[parentU]) { const Donusumd delta = driverGercek * uz[driverU].taban.ters(); const Donusumd parentYeni = delta * uz[parentU].taban; ziyaret[parentU] = true; if (uz[parentU].sahne != 0) sonuc[uz[parentU].sahne] = parentYeni; kuyruk.push_back({parentU, parentYeni}); } } std::size_t bas = 0; while (bas < kuyruk.size()) { const std::size_t u = kuyruk[bas].first; const Donusumd worldU = kuyruk[bas].second; ++bas; for (const auto& m : mf) { std::size_t komsu; bool uAna; // u bu mafsalda ANA mı? (komsu == cocuk ise true) if (m.ana == u) { komsu = m.cocuk; uAna = true; } else if (m.cocuk == u) { komsu = m.ana; uAna = false; } else continue; if (komsu >= uz.size() || ziyaret[komsu]) continue; Donusumd worldKomsu; if (m.tip == MafsalTip::Sabit) { const Donusumd delta = worldU * uz[u].taban.ters(); worldKomsu = delta * uz[komsu].taban; } else if (uAna) { // u=ana, komsu=cocuk → MEVCUT q ile rijit taşı (aşağı yönde DOF'a dokunulmaz). worldKomsu = worldU * kinematik::mafsalDonusumu(m); } else { // u=cocuk, komsu=ana: ağaçta driver'ın TEK üstü zaten yukarıda işlendi; buraya // düşülüyorsa kapalı zincir/çoklu-üst demektir — güvenlik: komşuya DOKUNMA. continue; } ziyaret[komsu] = true; if (uz[komsu].sahne != 0) sonuc[uz[komsu].sahne] = worldKomsu; kuyruk.push_back({komsu, worldKomsu}); } } return sonuc; } } // namespace yuztasi_ic // ustunde: eksen oku üstünde/sürüklemede → main pusulaUstunde'ye OR'lanır (kamera/seçim bastır). inline void yuzTasiCizVeIsle(YuzTasiDurum& yt, Sahne& sahne, Cekirdek& cekirdek, parca::PartDesignSistemi& partD, kinematik::Mekanizma& mek, OrbitKamera& kamera, ImDrawList* dl, ImGuiIO& io, bool vpHover, f32 mx, f32 my, f32 vw, f32 vh, f32 enBoy, bool& ustunde) { using namespace yuztasi_ic; ustunde = false; if (!yt.aktif) return; const Mat4f vp = kamera.izdusum(enBoy) * kamera.gorunum(); const auto proj = [&](const Vec3f& p, ImVec2& s) -> bool { const Vec4f h = vp * Vec4f{p, 1.0f}; if (h.w <= 1e-4f) return false; s = ImVec2(mx + (h.x / h.w * 0.5f + 0.5f) * vw, my + (h.y / h.w * 0.5f + 0.5f) * vh); return true; }; const auto okBasi = [&](ImVec2 taban, ImVec2 uc, ImU32 renk) { f32 dx = uc.x - taban.x, dy = uc.y - taban.y; const f32 L = std::sqrt(dx * dx + dy * dy); if (L < 1.0f) return; dx /= L; dy /= L; const f32 tb = dogrudanAyar().tutamacBoy; const f32 u = 9.0f * tb, w2 = 3.5f * tb; dl->AddTriangleFilled(uc, ImVec2(uc.x - dx * u - dy * w2, uc.y - dy * u + dx * w2), ImVec2(uc.x - dx * u + dy * w2, uc.y - dy * u - dx * w2), renk); }; const auto dunyaNok = [](const Dugum* d, const Vec3d& p) -> Vec3f { const Vec3d w = d->donusum.noktaUygula(p); return {(f32)w.x, (f32)w.y, (f32)w.z}; }; // Ekran (px,py) → dünya ışını (reverse-Z: near=1, far=0; orto+persp doğru). NDC bu viewport'a göre. const auto fareIsin = [&](f32 px, f32 py, Vec3f& o, Vec3f& d) { const f32 ndcX = 2.0f * (px - mx) / vw - 1.0f; const f32 ndcY = 2.0f * (py - my) / vh - 1.0f; const Mat4f invVP = vp.ters(); const Vec4f yH = invVP * Vec4f{ndcX, ndcY, 1.0f, 1.0f}; const Vec4f uH = invVP * Vec4f{ndcX, ndcY, 0.0f, 1.0f}; const Vec3f yakin = yH.xyz() * (1.0f / yH.w); const Vec3f uzak = uH.xyz() * (1.0f / uH.w); o = yakin; d = (uzak - yakin).birim(); }; const auto duzlemVur = [](const Vec3f& o, const Vec3f& d, const Vec3f& pN, const Vec3f& pP, Vec3f& hit) -> bool { const f32 dn = d.nokta(pN); if (std::abs(dn) < 1e-6f) return false; hit = o + d * ((pP - o).nokta(pN) / dn); return true; }; // Aktif translate/planar çerçevesi: Yüz (U/V/N) VEYA Dünya (X/Y/Z). W ile döngü. Döndürme // yayları/draft DAİMA yüz çerçevesi kullanır (tabana göre açılandırma) — yönMod'dan bağımsız. const auto tEks = [&](int i) -> Vec3f { if (yt.yonMod == 1) return i == 0 ? Vec3f{1, 0, 0} : i == 1 ? Vec3f{0, 1, 0} : Vec3f{0, 0, 1}; return yt.eksenlerD[i]; }; // Sürüklerken sayı yaz → hassas değer (CATIA/Fusion/Plasticity). Rakam/./− kabul, Backspace sil. // tipBuf'a yazar; "değer yazıldı mı?" döner (translate mesafe · rotate açı · scale çarpan ortak). const auto sayiGiris = [&](char* buf, size_t cap) -> bool { for (int i = 0; i < io.InputQueueCharacters.Size; ++i) { const ImWchar c = io.InputQueueCharacters[i]; const size_t l = std::strlen(buf); if (((c >= '0' && c <= '9') || c == '.' || (c == '-' && l == 0)) && l < cap - 1) { buf[l] = (char)c; buf[l + 1] = 0; } } if (ImGui::IsKeyPressed(ImGuiKey_Backspace)) { const size_t l = std::strlen(buf); if (l) buf[l - 1] = 0; } return buf[0] && !(buf[0] == '-' && !buf[1]); }; // Tüm seçili gövdelere per-node dönüşüm uygula (ORTAK pivot) → tek GrupIslem (çoklu-seçim // tek Ctrl+Z). donusumHesapla(eski)→yeni. Tek gövde → düz DonusumIslem (grup gereksiz). // Montaj Kısıtları DOF'a saygılı canlı takip: her seçili gövde için HAM hedefi mekanizma // grafına göre düzeltir (üst bağlantı varsa proje eder) ve BAĞLI diğer gövdeleri de (Sabit= // rijit, diğerleri=mevcut q ile rijit taşıma) AYNI GrupIslem'e katar (bkz. montajTakipHesapla). const auto govdeleriUygula = [&](auto&& donusumHesapla, const char* etiket) { std::vector> alt; std::unordered_map islenmis; // aynı gövdeye iki kez Islem eklenmesin for (u32 g : yt.govdeler) { Dugum* dd = sahne.bul(g); if (!dd) continue; const Donusumd eski = dd->donusum; Donusumd yeni = donusumHesapla(eski); const auto takip = montajTakipHesapla(mek, g, yeni); const auto itSelf = takip.find(g); if (itSelf != takip.end()) yeni = itSelf->second; // üst bağlantı projekte edilmiş olabilir dd->donusum = yeni; if (!islenmis[g]) { alt.push_back(std::make_unique(sahne, g, eski, yeni)); islenmis[g] = true; } for (const auto& kv : takip) { if (kv.first == g || islenmis[kv.first]) continue; Dugum* dd2 = sahne.bul(kv.first); if (!dd2) continue; const Donusumd eski2 = dd2->donusum; dd2->donusum = kv.second; alt.push_back(std::make_unique(sahne, kv.first, eski2, kv.second)); islenmis[kv.first] = true; } } if (alt.empty()) return; if (alt.size() == 1) cekirdek.uygula(std::move(alt.front())); else cekirdek.uygula(std::make_unique(std::move(alt), etiket)); }; // ---- ANİMASYONLU bekleme hayaleti (async sonuç inene dek konturu akıtır + yanıp söner) ---- if (!yt.bekleyenler.empty() && !yt.surukluyor) { const f32 t = (f32)ImGui::GetTime(); const f32 f01 = std::fmod(t, 1.2f) / 1.2f; const f32 faz = f01 * f01 * (3.0f - 2.0f * f01); const f32 nabiz = 0.55f + 0.35f * std::sin(t * 4.0f); const int aTepe = (int)(170 * nabiz), aTaban = (int)(80 * nabiz); u32 iptalId = 0, iptalFk = 0; // × ile iptal (döngü sonrası uygulanır) for (const auto& bek : yt.bekleyenler) { for (const auto& lp : bek.kontur) { ImVec2 oB{}, oT{}; bool var = false; for (const auto& p : lp) { ImVec2 sb, st; if (proj(p, sb) && proj(p + bek.vec * faz, st)) { if (var) { dl->AddLine(oB, sb, IM_COL32(150, 200, 230, aTaban), 1.2f); dl->AddLine(oT, st, IM_COL32(150, 225, 255, aTepe), 2.2f); } dl->AddLine(sb, st, IM_COL32(150, 200, 230, aTaban / 2), 1.0f); oB = sb; oT = st; var = true; } } } ImVec2 me; if (proj(bek.merkez + bek.vec * 0.5f, me)) { char b[48]; if (bek.aci != 0) std::snprintf(b, sizeof(b), "↻ %.1f° işleniyor…", bek.aci); else std::snprintf(b, sizeof(b), "⇲ %.2f mm işleniyor…", bek.vec.uzunluk()); gizmo::degerEtiketi(dl, me, b, IM_COL32(200, 215, 235, (int)(140 + 90 * nabiz))); // ✕ iptal (etiket sağında) — featureIptal → async sonuç güvenle atılır + ghost kalkar. const ImVec2 ts = ImGui::CalcTextSize(b); const ImVec2 xm{me.x + 20.0f + ts.x + 13.0f, me.y - ts.y * 0.5f}; ImGui::SetCursorScreenPos({xm.x - 9.0f, xm.y - 9.0f}); ImGui::PushID((int)(2400u + bek.id)); const bool tik = ImGui::InvisibleButton("##ytGhostIptal", {18, 18}); const bool hov = ImGui::IsItemHovered(); ImGui::PopID(); const ImU32 xr = hov ? IM_COL32(255, 95, 82, 255) : IM_COL32(232, 120, 108, 225); dl->AddCircleFilled(xm, 9.0f, IM_COL32(30, 20, 22, 235)); dl->AddCircle(xm, 9.0f, xr, 0, 1.6f); dl->AddLine({xm.x - 3.4f, xm.y - 3.4f}, {xm.x + 3.4f, xm.y + 3.4f}, xr, 1.8f); dl->AddLine({xm.x - 3.4f, xm.y + 3.4f}, {xm.x + 3.4f, xm.y - 3.4f}, xr, 1.8f); if (hov) ImGui::SetTooltip("İşlemi iptal et"); if (tik) { iptalId = bek.id; iptalFk = bek.fk; } } } if (iptalId) { if (iptalFk) partD.featureIptal(iptalFk); yt.bekleyenler.erase( std::remove_if(yt.bekleyenler.begin(), yt.bekleyenler.end(), [iptalId](const YuzTasiBekleyen& b) { return b.id == iptalId; }), yt.bekleyenler.end()); ui::toastGoster("İşlem iptal edildi", ui::ToastTip::Bilgi); } } // Seçim imzası: değişmedikçe yüz/kontur YENİDEN örneklenmez (yuzKonturlari sıcak yolda koşmasın). if (!yt.surukluyor) { u64 imza = 1469598103934665603ull; const auto kar = [&imza](u64 v) { imza = (imza ^ v) * 1099511628211ull; }; const auto karF = [&kar](f64 v) { u64 b; std::memcpy(&b, &v, 8); kar(b); }; for (const SecimOge& o : sahne.secililer()) { kar(((u64)o.govde << 32) | o.alt); if (Dugum* dd = sahne.bul(o.govde)) { kar((u64)(uintptr_t)dd->brep.get()); karF(dd->donusum.konum.x); karF(dd->donusum.konum.y); karF(dd->donusum.konum.z); karF(dd->donusum.yonelim.x); karF(dd->donusum.yonelim.y); karF(dd->donusum.yonelim.z); karF(dd->donusum.yonelim.w); } } if (imza != yt.hedefImza) { yt.hedefImza = imza; yt.govde = 0; yt.yuz = 0; yt.kontur.clear(); yt.tabanVar = false; yt.govdeMod = false; yt.govdeler.clear(); // YALNIZ TEK düz yüz seçiliyken yüz-gizmosu (çok yüz/silindir/küre → push/pull devrede). int altSayi = 0, govdeSayi = 0; for (const SecimOge& o : sahne.secililer()) { if (o.alt) altSayi++; else govdeSayi++; } geometri::YuzBilgi yb; // GÖVDE modu: alt seçim yok + ≥1 gövde tümü seçili → gizmo ORTAK CoG'da, transform tüm // seçili gövdelere (ortak pivot etrafında), tek GrupIslem undo (çoklu-seçim). // ÜRÜN AĞACINDAN seçimde gizmo BELİRMEZ/taşınmaz (kullanıcı isteği) — yalnız 3D-sahne seçimi // gövde gizmosunu üretir. sahne.secimAgactan() ağaç panelinden set edilir. if (altSayi == 0 && govdeSayi >= 1 && !sahne.secimAgactan()) { Vec3d cogTop{0, 0, 0}; int n = 0; for (const SecimOge& o : sahne.secililer()) { Dugum* dd = sahne.bul(o.govde); if (!dd || !dd->brep || !dd->brep->gecerliMi()) continue; // PERF: pivot = world-bbox merkezi (O(1), önbellekli yerelSinir). Eskiden her gövde için // geometri::kutleOzellikleri (OCCT BREP mass-props yürüyüşü, ~35ms/gövde) çağrılıyordu → // ağaçtan GRUP seçince (ör. 800 gövde) ~30 SANİYE DONMA. Mass-CoG pivot için gereksiz; // bbox merkezi CAD gizmosunda standart (Plasticity/CATIA). Yalnız seçimde asla ağır hesap. const Vec3f w = dd->dunyaMerkez(); cogTop = cogTop + Vec3d{w.x, w.y, w.z}; yt.govdeler.push_back(o.govde); ++n; } if (n > 0) { yt.govde = yt.govdeler.front(); yt.yuz = 0; yt.govdeMod = true; const Vec3d m = cogTop * (1.0 / n); yt.merkezD = {(f32)m.x, (f32)m.y, (f32)m.z}; yt.eksenlerD[0] = {1, 0, 0}; yt.eksenlerD[1] = {0, 1, 0}; yt.eksenlerD[2] = {0, 0, 1}; // Montaj Kısıtları canlı takip: DOF'a saygılı (bkz. montajTakipHesapla) — artık // burada KÖR rijit ekleme YOK, govdeleriUygula içinde HER seçili gövde için ayrı // hesaplanıp uygulanıyor (pivot da yalnız GERÇEKTEN seçilenlerden kuruluyor). } } // alt ordinali YALNIZ Yüz kipinde yüz demektir — kenar/nokta kipinde aynı numaralı // alakasız yüzde gizmo beliriyordu (Fikri 07-22: "bambaşka yüzeyde gizmo çıkıyor"). if (altSayi == 1 && sahne.kip() == SecimKip::Yuz) for (const SecimOge& o : sahne.secililer()) { if (!o.alt) continue; Dugum* dd = sahne.bul(o.govde); if (!dd || !dd->brep || !dd->brep->yuzBilgi(o.alt, yb) || !yb.duzlemMi) continue; yt.govde = o.govde; yt.yuz = o.alt; yt.merkezD = dunyaNok(dd, yb.merkez); const Vec3d nd = dd->donusum.yonelim.dondur(yb.normal); const Vec3f N = Vec3f{(f32)nd.x, (f32)nd.y, (f32)nd.z}.birim(); cerceveKur(N, yt.eksenlerD[0], yt.eksenlerD[1]); // U, V teğetler yt.eksenlerD[2] = N; // normal for (const auto& lp : dd->brep->yuzKonturlari(o.alt)) { std::vector w; w.reserve(lp.size()); for (const auto& p : lp) w.push_back(dunyaNok(dd, p)); yt.kontur.push_back(std::move(w)); } // Taban düzlemi (döndürme/draft nötr) SEÇİMDE HESAPLANMAZ — devasa modelde donma yapar // (cikintiTabanBul: bölge büyütme + yüz sayımı). Rotate-drag başında LAZY hesaplanır. yt.tabanVar = false; break; } } if (!yt.govde) return; // seçili düz yüz yok → gizmo çizme if (!yt.surukluyor) { yt.yonMod = dogrudanAyar().gizmoYonMod; } // Oto-gizmo kapalıysa: seçilen yüze/gövdeye gizmo ZIPLAMAZ; yalnız Ctrl basılıyken // deliberatif belirir (seçim/ölçüm serbest kalır, kazara transform olmaz). if (!dogrudanAyar().yuzTasiOto && !ImGui::IsKeyDown(ImGuiMod_Ctrl)) return; // W = translate/planar yönelim döngüsü (Yüz U/V/N ↔ Dünya X/Y/Z) — Plasticity orientation. if (vpHover && ImGui::IsKeyPressed(ImGuiKey_W)) { const int yeniMod = (yt.yonMod + 1) % 2; yt.yonMod = yeniMod; dogrudanAyar().gizmoYonMod = yeniMod; ayar::sistem().degerAta("dogrudan.gizmo_yon_mod", ayar::dMetin(yeniMod == 1 ? "Dünya (X/Y/Z)" : "Yüz (U/V/N)")); ui::toastGoster(yeniMod == 0 ? "Yönelim: Yüz (U/V/N)" : "Yönelim: Dünya (X/Y/Z)", ui::ToastTip::Bilgi); } // ---- TAM TRANSFORM GİZMOSU: 3 translate oku (U/V/N) + merkez serbest-taşı + 3 döndürme yayı ---- // Döndürme yayı = draft (yan duvar açılandırma; nötr=otomatik taban düzlemi). Pusula hissi. const f32 L = std::max(kamera.mesafe() * dogrudanAyar().okBoyOran, 0.03f); const f32 arcR = L * 0.92f; const f32 mr = 7.0f * dogrudanAyar().tutamacBoy; ImVec2 sc; const bool merkezVar = proj(yt.merkezD, sc); ImVec2 sAx[3]; bool axVar[3]; for (int e = 0; e < 3; ++e) axVar[e] = merkezVar && proj(yt.merkezD + tEks(e) * L, sAx[e]); // PLANAR kareler (2-eksen düzlem kaydırma — Plasticity move). Eksen çifti köşesinde; // düzlem[p] = pa[p] eksen ikilisinin gerdiği düzlem, kaydırma o düzleme kilitli. constexpr int pa[3][2] = {{0, 1}, {1, 2}, {2, 0}}; const f32 plO = L * 0.34f; ImVec2 sPl[3]; bool plVar[3]; for (int p = 0; p < 3; ++p) plVar[p] = merkezVar && proj(yt.merkezD + (tEks(pa[p][0]) + tEks(pa[p][1])) * plO, sPl[p]); // GÖVDE uniform-scale tutamacı (köşegen küp) — sadece gövde modunda (CoG etrafında ölçek). ImVec2 sScale; bool scaleVar = yt.govdeMod && merkezVar && proj(yt.merkezD + (tEks(0) + tEks(1) + tEks(2)) * (L * 0.5f), sScale); // Döndürme yayı örnekleri (A ekseni etrafı → ⟂ düzlem, diğer iki frame ekseniyle spanla). // Sıcak yol: yaylar SABİT diziye örneklenir (eskiden her kare 3 heap tahsisi vardı — face/ // body seçiliyken sürekli çizim). Stack: 3×45×8B ≈ 1KB. constexpr int SEG = 44; ImVec2 arc[3][SEG + 1]; int arcN[3] = {0, 0, 0}; for (int a = 0; a < 3; ++a) { const Vec3f u = yt.eksenlerD[(a + 1) % 3], w = yt.eksenlerD[(a + 2) % 3]; for (int i = 0; i <= SEG; ++i) { const f32 th = (f32)i / SEG * 6.2831853f; ImVec2 s; if (proj(yt.merkezD + (u * std::cos(th) + w * std::sin(th)) * arcR, s)) arc[a][arcN[a]++] = s; } } // ---- Hover önceliği: merkez > yaylar > translate okları ---- yt.hoverEksen = -1; if (vpHover && merkezVar) { const f32 dxm = io.MousePos.x - sc.x, dym = io.MousePos.y - sc.y; const f32 psr = 6.0f * dogrudanAyar().tutamacBoy; // planar kare yarı-boyut (px) int hp = -1; f32 hpd = (psr + 3.0f) * (psr + 3.0f); for (int p = 0; p < 3; ++p) { if (!plVar[p]) continue; const f32 dx = io.MousePos.x - sPl[p].x, dy = io.MousePos.y - sPl[p].y, d2 = dx * dx + dy * dy; if (d2 < hpd) { hpd = d2; hp = p; } } bool scaleYakin = false; if (scaleVar) { const f32 dxs = io.MousePos.x - sScale.x, dys = io.MousePos.y - sScale.y; scaleYakin = dxs * dxs + dys * dys <= (psr + 3.0f) * (psr + 3.0f); } if (dxm * dxm + dym * dym <= (mr + 4.0f) * (mr + 4.0f)) { yt.hoverEksen = 3; // merkez öncelikli (serbest taşı) } else if (scaleYakin) { yt.hoverEksen = 30; // gövde uniform-scale küpü } else if (hp >= 0) { yt.hoverEksen = 20 + hp; // planar kare } else { const auto segMes = [&](ImVec2 p0, ImVec2 p1) { const f32 dx = p1.x - p0.x, dy = p1.y - p0.y, l2 = dx * dx + dy * dy; f32 tp = l2 > 1e-6f ? ((io.MousePos.x - p0.x) * dx + (io.MousePos.y - p0.y) * dy) / l2 : 0; tp = tp < 0 ? 0 : (tp > 1 ? 1 : tp); const f32 qx = p0.x + tp * dx - io.MousePos.x, qy = p0.y + tp * dy - io.MousePos.y; return qx * qx + qy * qy; }; f32 best = 11.0f * 11.0f; int bestArc = -1; // Yaylar hep etkin: yüz → draft (taban LAZY drag başında), gövde → node döndürme. for (int a = 0; a < 3; ++a) for (int i = 1; i < arcN[a]; ++i) { const f32 d2 = segMes(arc[a][i - 1], arc[a][i]); if (d2 < best) { best = d2; bestArc = a; } } if (bestArc >= 0) { yt.hoverEksen = 10 + bestArc; } else { f32 bestx = 13.0f * 13.0f; int bx = -1; for (int e = 0; e < 3; ++e) if (axVar[e]) { const f32 d2 = segMes(sc, sAx[e]); if (d2 < bestx) { bestx = d2; bx = e; } } if (bx >= 0) yt.hoverEksen = bx; } } } // ---- ÇİZ: döndürme yayları (bold, pusula) — yüz(draft)+gövde(node) her ikisinde ---- // Toggle: dogrudanAyar().yuzTasiDondurmeYay ile kontrol edilir if (dogrudanAyar().yuzTasiDondurmeYay) { for (int a = 0; a < 3; ++a) { const bool hv = yt.hoverEksen == 10 + a; const ImU32 renk = eksenRenk(a, hv); for (int i = 1; i < arcN[a]; ++i) dl->AddLine(arc[a][i - 1], arc[a][i], (renk & 0x00FFFFFFu) | ((hv ? 255u : 150u) << 24), hv ? 3.6f : 1.8f); } } // ---- ÇİZ: translate okları (bold) ---- for (int e = 0; e < 3; ++e) { if (!axVar[e]) continue; const bool hv = yt.hoverEksen == e; const ImU32 renk = eksenRenk(e, hv); if (hv && dogrudanAyar().glowAcik) gizmo::glowCizgi(dl, sc, sAx[e], renk, 3.6f); else dl->AddLine(sc, sAx[e], renk, hv ? 3.6f : 2.8f); okBasi(sc, sAx[e], renk); } // ---- ÇİZ: planar kareler (2-eksen düzlem) ---- for (int p = 0; p < 3; ++p) { if (!plVar[p]) continue; const bool hv = yt.hoverEksen == 20 + p; const ImU32 renk = eksenRenk(pa[p][0], hv); // çiftin ilk ekseninin rengi const f32 r = 6.0f * dogrudanAyar().tutamacBoy; dl->AddRectFilled({sPl[p].x - r, sPl[p].y - r}, {sPl[p].x + r, sPl[p].y + r}, (renk & 0x00FFFFFFu) | ((hv ? 150u : 70u) << 24), 1.5f); dl->AddRect({sPl[p].x - r, sPl[p].y - r}, {sPl[p].x + r, sPl[p].y + r}, renk, 1.5f, 0, hv ? 2.2f : 1.4f); } // ---- ÇİZ: gövde uniform-scale küpü ---- if (scaleVar) { const bool hv = yt.hoverEksen == 30; const ImU32 renk = hv ? IM_COL32(255, 235, 140, 255) : IM_COL32(230, 215, 120, 220); const f32 r = 6.0f * dogrudanAyar().tutamacBoy; dl->AddRectFilled({sScale.x - r, sScale.y - r}, {sScale.x + r, sScale.y + r}, (renk & 0x00FFFFFFu) | ((hv ? 170u : 90u) << 24), 2.0f); dl->AddRect({sScale.x - r, sScale.y - r}, {sScale.x + r, sScale.y + r}, renk, 2.0f, 0, hv ? 2.4f : 1.6f); } // ---- ÇİZ: merkez serbest-taşı ---- if (merkezVar) { const bool hv = yt.hoverEksen == 3; const ImU32 renk = hv ? IM_COL32(170, 240, 255, 255) : IM_COL32(160, 220, 250, 235); // Yüz Taşı çember önizlemesi (toggle: dogrudanAyar().yuzTasiCevreGoster) if (dogrudanAyar().yuzTasiCevreGoster) { dl->AddCircleFilled(sc, mr, (renk & 0x00FFFFFFu) | ((hv ? 110u : 70u) << 24)); dl->AddCircle(sc, mr, renk, 0, hv ? 2.6f : 1.8f); } else { // Kapalıysa sadece çizgiler (ok benzeri) göster const f32 t = mr * 0.62f; dl->AddLine({sc.x - t, sc.y}, {sc.x + t, sc.y}, renk, 1.8f); dl->AddLine({sc.x, sc.y - t}, {sc.x, sc.y + t}, renk, 1.8f); } if (hv && dogrudanAyar().glowAcik) gizmo::glowDaire(dl, sc, mr + 1.0f, renk); // Yönelim rozeti (W ile döngü): Yüz / Dünya. dl->AddText({sc.x + mr + 4.0f, sc.y - mr - 2.0f}, IM_COL32(180, 210, 235, 220), yt.yonMod == 0 ? "Yüz [W]" : "Dünya [W]"); } // Seçili yüz konturu vurgusu (soluk). Kesit aktifken kırpılan yarıdaki parça // çizilmez — 2B çizgi shader clip'i görmez, segment dünya-uzayında kırpılır. for (const auto& lp : yt.kontur) { for (size_t i = 1; i < lp.size(); ++i) { Vec3f a = lp[i - 1], b = lp[i]; if (!gizmo::kesitSegment(sahne, a, b)) continue; ImVec2 s0, s1; if (proj(a, s0) && proj(b, s1)) dl->AddLine(s0, s1, IM_COL32(150, 210, 255, 120), 1.4f); } } ustunde = yt.hoverEksen >= 0 && ImGui::IsMouseDown(ImGuiMouseButton_Left); yt.tutamacYakin = yt.hoverEksen >= 0; // dokunmatik: tuştan bağımsız yakınlık if (yt.hoverEksen >= 0 && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { // ALT+sürükle = KOPYALA-taşı (yalnız GÖVDE modu, SW/Fusion kalıbı): önce klonla // (brep paylaşılır — ucuz instancing), seçim + sürükleme hedefi klon(lar) olur. if (io.KeyAlt && yt.govdeMod && dogrudanAyar().altKopyalaTasi) { std::vector yeniler; for (u32 g : yt.govdeler) if (const u32 y = sahne.govdeKopyalaUygula(g)) yeniler.push_back(y); if (!yeniler.empty()) { sahne.sec({yeniler.front(), 0}, false); for (size_t i = 1; i < yeniler.size(); ++i) sahne.sec({yeniler[i], 0}, true); yt.govdeler = yeniler; yt.govde = yeniler.front(); ui::toastGoster("Kopya oluşturuldu — sürükleyerek taşı", ui::ToastTip::Basari); } } yt.surukluyor = true; yt.eksen = yt.hoverEksen; yt.mesafe = 0; yt.aci = 0; yt.serbestVec = Vec3f{}; yt.tipBuf[0] = 0; if (yt.eksen == 3) { // merkez: serbest eğik taşı VEYA Ctrl → Blok Kaydır (taban boyunca rijit) yt.blokKaydir = false; Vec3f o, d; fareIsin(io.MousePos.x, io.MousePos.y, o, d); yt.serbestN = d; // varsayılan sürükleme düzlemi = kameraya bakan (eğik hareket) if (io.KeyCtrl && !yt.govdeMod) { // Ctrl → çıkıntının TAMAMINI taban düzlemine kilitli kaydır bool tabanOk = false; if (Dugum* dd = sahne.bul(yt.govde)) { // taban LAZY (seçimde değil → devasa modelde donma yok) const geometri::CikintiTabanSonuc ts = geometri::cikintiTabanBul(*dd->brep, yt.yuz, /*tamGovdeHesapla*/ false); if (ts.gecerli) { const Vec3d tn = dd->donusum.yonelim.dondur(ts.normal); yt.serbestN = Vec3f{(f32)tn.x, (f32)tn.y, (f32)tn.z}.birim(); // taban normali → teğet kayma yt.blokKaydir = tabanOk = true; } } if (!tabanOk) { // taban yok → çıkıntı değil; jesti iptal (tek uyarı) yt.surukluyor = false; ui::toastGoster("Blok Kaydır için düz üst yüzlü bir çıkıntı seçin", ui::ToastTip::Uyari); } } if (yt.surukluyor && !duzlemVur(o, d, yt.serbestN, yt.merkezD, yt.serbestKok)) yt.serbestKok = yt.merkezD; } else if (yt.eksen == 30) { // gövde uniform-scale: başlangıç ekran-uzaklığı (merkeze) const f32 dxs = io.MousePos.x - sc.x, dys = io.MousePos.y - sc.y; yt.olcekBaz = std::sqrt(dxs * dxs + dys * dys); yt.olcekFak = 1.0f; } else if (yt.eksen >= 20) { // planar: düzlem normali = çift-DIŞI üçüncü eksen (kilitli) const int p = yt.eksen - 20, ucuncu = 3 - pa[p][0] - pa[p][1]; yt.serbestN = tEks(ucuncu); Vec3f o, d; fareIsin(io.MousePos.x, io.MousePos.y, o, d); if (!duzlemVur(o, d, yt.serbestN, yt.merkezD, yt.serbestKok)) yt.serbestKok = yt.merkezD; } else if (yt.eksen >= 10 && yt.eksen < 20 && !yt.govdeMod) { // YÜZ döndürme = draft → taban düzlemi ŞİMDİ hesaplanır (LAZY; seçimde değil → donma yok). yt.tabanVar = false; if (Dugum* dd = sahne.bul(yt.govde)) { const geometri::CikintiTabanSonuc ts = geometri::cikintiTabanBul(*dd->brep, yt.yuz, /*tamGovdeHesapla*/ false); if (ts.gecerli) { yt.tabanYerelP = {(f32)ts.nokta.x, (f32)ts.nokta.y, (f32)ts.nokta.z}; yt.tabanYerelN = Vec3f{(f32)ts.normal.x, (f32)ts.normal.y, (f32)ts.normal.z}.birim(); yt.tabanNoktaD = dunyaNok(dd, ts.nokta); const Vec3d tn = dd->donusum.yonelim.dondur(ts.normal); yt.tabanNormalD = Vec3f{(f32)tn.x, (f32)tn.y, (f32)tn.z}.birim(); yt.tabanVar = true; } } if (!yt.tabanVar) { // taban yok → açı verilemez, jesti iptal (hata yağmuru yerine tek uyarı) yt.surukluyor = false; ui::toastGoster("Bu yüze açı verilemiyor — düz üst yüzlü bir çıkıntı/duvar seçin", ui::ToastTip::Uyari); } } } return; } // ---- GÖVDE uniform-scale: küpü sürükle → CoG etrafında ölçek (node.olcek) ---- if (yt.eksen == 30) { ustunde = true; ImVec2 scc; proj(yt.merkezD, scc); const f32 dxs = io.MousePos.x - scc.x, dys = io.MousePos.y - scc.y; const f32 cur = std::sqrt(dxs * dxs + dys * dys); yt.olcekFak = yt.olcekBaz > 1.0f ? cur / yt.olcekBaz : 1.0f; if (io.KeyShift) { // Shift → 0.1 kademe snap yt.olcekFak = std::round(yt.olcekFak / 0.1f) * 0.1f; } const bool tipVar = sayiGiris(yt.tipBuf, sizeof(yt.tipBuf)); // sayı yaz → hassas çarpan if (tipVar) { const f32 v = (f32)std::atof(yt.tipBuf); if (v > 0.01f) yt.olcekFak = v; } if (yt.olcekFak < 0.05f) yt.olcekFak = 0.05f; const f32 L2 = std::max(kamera.mesafe() * dogrudanAyar().okBoyOran, 0.03f) * yt.olcekFak; for (int e = 0; e < 3; ++e) { ImVec2 a, b; if (proj(yt.merkezD, a) && proj(yt.merkezD + tEks(e) * L2, b)) dl->AddLine(a, b, eksenRenk(e, true), 2.0f); } ImVec2 ml; if (proj(yt.merkezD, ml)) { char b[44]; if (tipVar) std::snprintf(b, sizeof(b), "⤢ ×%s ▏", yt.tipBuf); else std::snprintf(b, sizeof(b), "⤢ ×%.3f%s", yt.olcekFak, io.KeyShift ? " ⇥" : ""); gizmo::degerEtiketi(dl, ml, b, IM_COL32(255, 225, 120, 255)); } if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { yt.surukluyor = false; return; } if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) || ImGui::IsKeyPressed(ImGuiKey_Enter)) { yt.surukluyor = false; const f32 s = yt.olcekFak; const u32 gg = yt.govde; if (std::abs(s - 1.0f) < 1e-3f) return; // Uniform ölçek → normaller korunur, BVH/pick güvenli. ORTAK pivot P etrafında: // olcek*=s, konum=P+(konum−P)·s (her nokta P etrafında s ile ölçeklenir). const Vec3d piv{yt.merkezD.x, yt.merkezD.y, yt.merkezD.z}; govdeleriUygula( [&](Donusumd e) { e.olcek = e.olcek * (f64)s; e.konum = piv + (e.konum - piv) * (f64)s; return e; }, "Gövde Ölçek"); ui::toastGoster(yt.govdeler.size() > 1 ? "Gövdeler ölçeklendi" : "Gövde ölçeklendi", ui::ToastTip::Basari); } return; } // ---- DÖNDÜRME yayı (draft): yan duvarı/yüzü açılandır (nötr = otomatik taban düzlemi) ---- if (yt.eksen >= 10 && yt.eksen < 20) { ustunde = true; const int a = yt.eksen - 10; yt.aci += io.MouseDelta.x * 0.4f; // yatay sürükleme → açı (derece) if (io.KeyShift && dogrudanAyar().snapAci > 0.01f) { // Shift → açı snap (Plasticity) const f32 sn = dogrudanAyar().snapAci; yt.aci = std::round(yt.aci / sn) * sn; } const bool tipVar = sayiGiris(yt.tipBuf, sizeof(yt.tipBuf)); // sayı yaz → hassas açı if (tipVar) yt.aci = (f32)std::atof(yt.tipBuf); const f32 rad = yt.aci * 0.01745329f; const Vec3f eks = yt.eksenlerD[a]; const Vec3f kok = yt.govdeMod ? yt.merkezD : yt.tabanNoktaD; const ImU32 col = eksenRenk(a, true); if (yt.govdeMod) { // gövde: kontur yok → yay + 0°/açı yarıçapları (canlı açı göstergesi) const f32 arcR = std::max(kamera.mesafe() * dogrudanAyar().okBoyOran, 0.03f) * 0.92f; const Vec3f u = yt.eksenlerD[(a + 1) % 3], w = yt.eksenlerD[(a + 2) % 3]; ImVec2 sc2, r0, r1; const bool okc = proj(yt.merkezD, sc2); if (okc && proj(yt.merkezD + u * arcR, r0)) dl->AddLine(sc2, r0, (col & 0x00FFFFFFu) | (120u << 24), 1.6f); if (okc && proj(yt.merkezD + (u * std::cos(rad) + w * std::sin(rad)) * arcR, r1)) dl->AddLine(sc2, r1, col, 2.4f); } else { const auto dondurNok = [&](const Vec3f& p) -> Vec3f { const Vec3f v = p - kok; const f32 c = std::cos(rad), s = std::sin(rad); return kok + v * c + eks.capraz(v) * s + eks * (eks.nokta(v)) * (1 - c); }; for (const auto& lp : yt.kontur) { ImVec2 onceki{}; bool var = false; for (const auto& p : lp) { ImVec2 s; if (proj(dondurNok(p), s)) { if (var) dl->AddLine(onceki, s, col, 2.0f); onceki = s; var = true; } } } } ImVec2 ml; if (proj(yt.merkezD, ml)) { char b[52]; const char* rm = yt.govdeMod ? "gövde döndür" : "açı/draft"; if (tipVar) std::snprintf(b, sizeof(b), "∠ %s° ▏ (%s)", yt.tipBuf, rm); else std::snprintf(b, sizeof(b), "∠ %.1f°%s (%s)", yt.aci, io.KeyShift ? " ⇥" : "", rm); gizmo::degerEtiketi(dl, ml, b, col); } if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { yt.surukluyor = false; return; } if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) || ImGui::IsKeyPressed(ImGuiKey_Enter)) { yt.surukluyor = false; const f32 aci = yt.aci; const u32 gg = yt.govde, yy = yt.yuz; if (std::abs(aci) < 0.05f) return; if (yt.govdeMod) { // GÖVDE(ler) döndür → ORTAK pivot (merkezD) etrafında + GrupIslem undo const Quatd q = Quatd::eksenAci(Vec3d{eks.x, eks.y, eks.z}, (f64)aci * 0.017453292519943); const Vec3d piv{yt.merkezD.x, yt.merkezD.y, yt.merkezD.z}; govdeleriUygula( [&](Donusumd e) { e.yonelim = (q * e.yonelim).birim(); e.konum = piv + q.dondur(e.konum - piv); return e; }, "Gövde Döndür"); ui::toastGoster(yt.govdeler.size() > 1 ? "Gövdeler döndürüldü" : "Gövde döndürüldü", ui::ToastTip::Basari); return; } // RE-EDITABLE FEATURE (yuz_dondur): ürün ağacından açı düzenlenebilir + undo + async // (Splitter/Fuse worker'da → bırakışta donma yok) + ✕ iptal. Feature DÜNYA brep'inde // çalışır → taban/eksen DÜNYA değerleriyle geçilir (yerel tabanYerelP/N draft'ın eski // senkron yolu içindi; hedefDunyaBrep frame'i bu değil). const u32 dfk = partD.featureBaslat("yuz_dondur", VARLIK_YOK, gg); if (!dfk) { ui::toastGoster("Yüz Döndür başarısız (katı gövde gerek)", ui::ToastTip::Hata); return; } std::vector> dp{ {"yuz0", (f64)yy}, {"aci", (f64)aci}, {"eksNX", (f64)eks.x}, {"eksNY", (f64)eks.y}, {"eksNZ", (f64)eks.z}, {"tabanX", (f64)yt.tabanNoktaD.x}, {"tabanY", (f64)yt.tabanNoktaD.y}, {"tabanZ", (f64)yt.tabanNoktaD.z}, {"tabanNX", (f64)yt.tabanNormalD.x}, {"tabanNY", (f64)yt.tabanNormalD.y}, {"tabanNZ", (f64)yt.tabanNormalD.z}, // Rijit dene → başarısızsa draft'a düş (dikey eksen=spin, yatay=duvar açılandır). {"rijit", dogrudanAyar().dondurRijit ? 1.0 : 0.0}}; const u32 dIsId = ++yt.bekleyenSayac; const auto dBitti = [&yt, &cekirdek, &partD, dIsId, dfk](bool ok) { yt.bekleyenler.erase( std::remove_if(yt.bekleyenler.begin(), yt.bekleyenler.end(), [dIsId](const YuzTasiBekleyen& b) { return b.id == dIsId; }), yt.bekleyenler.end()); if (ok) dmFeatureEkleUndo(cekirdek, partD, dfk); // × ile iptalde callback koşmaz ui::toastGoster(ok ? "Yüz döndürüldü (ağaçtan açı düzenlenebilir)" : "Döndürme başarısız (açı çok büyük veya yüz uygun değil)", ok ? ui::ToastTip::Basari : ui::ToastTip::Hata); }; if (dogrudanAyar().asyncApply && partD.featureAsyncUygula(dfk, dp, dBitti)) { YuzTasiBekleyen b; b.id = dIsId; b.fk = dfk; b.aci = aci; b.merkez = yt.merkezD; b.kontur = yt.kontur; yt.bekleyenler.push_back(std::move(b)); return; } partD.featureParamToplu(dfk, dp); if (partD.featureOnayla(dfk)) dmFeatureEkleUndo(cekirdek, partD, dfk); ui::toastGoster("Yüz döndürüldü (ağaçtan açı düzenlenebilir)", ui::ToastTip::Basari); } return; } // ---- Sürükleme: translate oku (skaler) VEYA serbest/planar (düzlem vektörü) → tek dVec ---- ustunde = true; const bool serbest = yt.eksen == 3 || yt.eksen >= 20; // 3=ekran-serbest · 20-22=eksen-düzlemi bool tipVar = false; if (serbest) { // Kameraya bakan sabit düzlemde imleç dünya noktasını izle → eğik öteleme (walls skew). Vec3f o, d; fareIsin(io.MousePos.x, io.MousePos.y, o, d); Vec3f hit; if (duzlemVur(o, d, yt.serbestN, yt.merkezD, hit)) yt.serbestVec = hit - yt.serbestKok; } else { const Vec3f yon = tEks(yt.eksen); // seçili translate ekseni (Yüz U/V/N veya Dünya X/Y/Z) ImVec2 c0, c1; if (proj(yt.merkezD, c0) && proj(yt.merkezD + yon, c1)) { const f32 vx = c1.x - c0.x, vy = c1.y - c0.y, L2 = vx * vx + vy * vy; if (L2 > 1e-6f) yt.mesafe += (io.MouseDelta.x * vx + io.MouseDelta.y * vy) / L2; } tipVar = sayiGiris(yt.tipBuf, sizeof(yt.tipBuf)); // eksen modunda sayı yaz → hassas mesafe if (tipVar) yt.mesafe = (f32)std::atof(yt.tipBuf); else if (io.KeyShift && dogrudanAyar().snapMesafe > 1e-4f) { // Shift → grid snap (Plasticity) const f32 sn = dogrudanAyar().snapMesafe; yt.mesafe = std::round(yt.mesafe / sn) * sn; } } // Ortak öteleme vektörü (dünya) — her iki mod tek yoldan önizlenir + uygulanır. const Vec3f dVec = serbest ? yt.serbestVec : tEks(yt.eksen) * yt.mesafe; const ImU32 col = serbest ? IM_COL32(150, 232, 255, 240) : eksenRenk(yt.eksen, true); const ImU32 baglanti = (col & 0x00FFFFFFu) | (110u << 24); for (const auto& lp : yt.kontur) { std::vector tepe, taban; tepe.reserve(lp.size()); taban.reserve(lp.size()); for (const auto& p : lp) { ImVec2 sb, st; if (proj(p, sb) && proj(p + dVec, st)) { taban.push_back(sb); tepe.push_back(st); } } const int n = (int)tepe.size(); for (int i = 0; i < n; ++i) { dl->AddLine(tepe[i], tepe[(i + 1) % n], col, 1.8f); dl->AddLine(taban[i], tepe[i], baglanti, 1.0f); } } if (yt.govdeMod) { // gövde: kontur yok → hayalet ok + hedef daire ImVec2 a, bpt; if (proj(yt.merkezD, a) && proj(yt.merkezD + dVec, bpt)) { dl->AddLine(a, bpt, col, 2.0f); dl->AddCircle(bpt, 6.0f, col, 0, 2.0f); } } ImVec2 ml; if (proj(yt.merkezD + dVec, ml)) { char b[56]; // Etiket ekseni: DÜNYA çerçevesinde (gövde modu veya W=Dünya) X/Y/Z, yüz çerçevesinde U/V/N⊥. const bool dunyaEks = yt.govdeMod || yt.yonMod == 1; const char* ea = dunyaEks ? (yt.eksen == 0 ? "X" : yt.eksen == 1 ? "Y" : "Z") : (yt.eksen == 0 ? "U" : yt.eksen == 1 ? "V" : "N⊥"); if (yt.eksen >= 20) { static const char* pn[3] = {"UV", "VN", "NU"}; std::snprintf(b, sizeof(b), "▱ %s %.2f mm", pn[yt.eksen - 20], dVec.uzunluk()); } else if (yt.blokKaydir) std::snprintf(b, sizeof(b), "⇄ %.2f mm (blok kaydır)", dVec.uzunluk()); else if (serbest) std::snprintf(b, sizeof(b), "⇲ %.2f mm (eğik)", dVec.uzunluk()); else if (tipVar) std::snprintf(b, sizeof(b), "%s %s mm ▏", ea, yt.tipBuf); else std::snprintf(b, sizeof(b), "%s ⇲ %.2f mm%s", ea, yt.mesafe, io.KeyShift ? " ⇥" : ""); gizmo::degerEtiketi(dl, ml, b, col); } // ESC = sürüklemeyi iptal · Enter/bırak = uygula. if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { yt.surukluyor = false; yt.tipBuf[0] = 0; yt.blokKaydir = false; return; } if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) || ImGui::IsKeyPressed(ImGuiKey_Enter)) { yt.surukluyor = false; yt.tipBuf[0] = 0; const Vec3f vec = dVec; // DÜNYA vektörü (yuz_tasi hedefi donusumUygula ile dünya-brep) const bool blok = yt.blokKaydir; yt.blokKaydir = false; if (vec.uzunluk() <= 1e-3f) return; const u32 gg = yt.govde, yy = yt.yuz; if (blok) { // Ctrl-merkez → BLOK KAYDIR: çıkıntının TAMAMINI taban boyunca rijit kaydır (parametrik) const u32 bfk = partD.featureBaslat("cikinti_kaydir", VARLIK_YOK, gg); if (!bfk) { ui::toastGoster("Blok Kaydır başarısız (kaynamış katı gövde gerek)", ui::ToastTip::Hata); return; } dmTutamacOlayiYay(cekirdek, "Değiştir", "Blok Kaydır"); // makro/öğrenme görsün std::vector> bp{ {"yuz0", (f64)yy}, {"tasiX", (f64)vec.x}, {"tasiY", (f64)vec.y}, {"tasiZ", (f64)vec.z}}; // Async: taban bulma + Splitter/Fuse worker'da → devasa modelde bırakışta donma yok. // Hayalet (yuz_tasi ile aynı) bekleyenler'de; ✕ ile iptal edilebilir. const u32 bIsId = ++yt.bekleyenSayac; const auto bBitti = [&yt, &cekirdek, &partD, bIsId, bfk](bool ok) { yt.bekleyenler.erase( std::remove_if(yt.bekleyenler.begin(), yt.bekleyenler.end(), [bIsId](const YuzTasiBekleyen& b) { return b.id == bIsId; }), yt.bekleyenler.end()); if (ok) dmFeatureEkleUndo(cekirdek, partD, bfk); // × ile iptalde callback koşmaz ui::toastGoster(ok ? "Blok kaydırıldı (ağaçtan yön/mesafe düzenlenebilir)" : "Blok kaydır başarısız (çıkıntı taban dışına taşmış olabilir)", ok ? ui::ToastTip::Basari : ui::ToastTip::Hata); }; if (dogrudanAyar().asyncApply && partD.featureAsyncUygula(bfk, bp, bBitti)) { YuzTasiBekleyen b; b.id = bIsId; b.fk = bfk; b.vec = vec; b.merkez = yt.merkezD; b.kontur = yt.kontur; yt.bekleyenler.push_back(std::move(b)); return; } partD.featureParamToplu(bfk, bp); if (partD.featureOnayla(bfk)) dmFeatureEkleUndo(cekirdek, partD, bfk); ui::toastGoster("Blok kaydırıldı (ağaçtan yön/mesafe düzenlenebilir)", ui::ToastTip::Basari); return; } if (yt.govdeMod) { // GÖVDE(ler) taşı → node dönüşümü + GrupIslem undo (çoklu-seçim tek Ctrl+Z) const Vec3d dv{vec.x, vec.y, vec.z}; govdeleriUygula([&](Donusumd e) { e.konum = e.konum + dv; return e; }, "Gövde Taşı"); ui::toastGoster(yt.govdeler.size() > 1 ? "Gövdeler taşındı" : "Gövde taşındı", ui::ToastTip::Basari); return; } const u32 fk = partD.featureBaslat("yuz_tasi", VARLIK_YOK, gg); if (!fk) { ui::toastGoster("Yüz Taşı başarısız (katı gövde gerek)", ui::ToastTip::Hata); return; } dmTutamacOlayiYay(cekirdek, "Değiştir", "Yüz Taşı"); // makro/öğrenme görsün std::vector> params{ {"yuz0", (f64)yy}, {"tasiX", (f64)vec.x}, {"tasiY", (f64)vec.y}, {"tasiZ", (f64)vec.z}}; // İşlem sonrası ötelenen yüzü yeni gövdede yeniden seç (secimiKoru — triad hemen belirir). const auto seciminiKoru = [&partD, &sahne](u32 f) { if (!dogrudanAyar().secimiKoru || !f) return; const u32 sd = partD.featureSonucDugum(f); if (!sd) return; for (u32 uy : partD.featureUretilenYuzler(f)) if (uy) sahne.sec({sd, uy}, /*coklu*/ true); }; const u32 isId = ++yt.bekleyenSayac; const auto bitti = [&yt, &sahne, &cekirdek, &partD, seciminiKoru, isId, fk](bool ok) { yt.bekleyenler.erase( std::remove_if(yt.bekleyenler.begin(), yt.bekleyenler.end(), [isId](const YuzTasiBekleyen& b) { return b.id == isId; }), yt.bekleyenler.end()); if (ok) { dmFeatureEkleUndo(cekirdek, partD, fk); sahne.secimTemizle(); seciminiKoru(fk); } ui::toastGoster(ok ? "Yüz taşındı" : "Yüz Taşı başarısız (yön yüze paralel olabilir)", ok ? ui::ToastTip::Basari : ui::ToastTip::Hata); }; if (dogrudanAyar().asyncApply && partD.featureAsyncUygula(fk, params, bitti)) { YuzTasiBekleyen b; b.id = isId; b.fk = fk; b.vec = vec; b.merkez = yt.merkezD; b.kontur = yt.kontur; yt.bekleyenler.push_back(std::move(b)); } else { partD.featureParamToplu(fk, params); if (partD.featureOnayla(fk)) dmFeatureEkleUndo(cekirdek, partD, fk); if (dogrudanAyar().secimiKoru) sahne.secimTemizle(); seciminiKoru(fk); ui::toastGoster("Yüz taşındı", ui::ToastTip::Basari); } } } } // namespace fiko