#pragma once // CATIA/Fusion Push/Pull — doğrudan yüz düzenleme. Mod açıkken DÜZ bir yüzün // üstüne gelince normal oku belirir; tıkla-sürükle → yüz normali boyunca canlı // wireframe önizleme (ucuz, OCCT yok). Bırakınca parametrik "yuz_itcek" feature'ı // uygulanır (yerel BRepFeat_MakePrism, global boolean YOK) + undo kaydı // (dm_tutamac_olay.hpp). mesafe>0 dışa çek, <0 içe it. #include #include #include #include #include #include #include #include #include "fiko/cekirdek/cekirdek.hpp" #include "dm_tutamac_olay.hpp" #include "fiko/parca/part_design_sistemi.hpp" // parametrik Yüz İt/Çek feature #include "fiko/render/orbit_kamera.hpp" #include "fiko/sahne/sahne.hpp" #include "fiko/sahne/sahne_islem.hpp" #include "fiko/ui/bilesen_ek.hpp" #include "dogrudan_ayar.hpp" #include "gizmo_gorsel.hpp" #include "imgui.h" namespace fiko { // Çok-seçim hedefi: seçili her uygun yüz kendi normali/radyal yönü boyunca AYNI mesafe ile // itilir/çekilir ([0] = ok/anchor yüzü). Kontur seçim değiştiğinde bir kez örneklenir // (yuzKonturlari her kare çağrılmaz — sıcak yol). struct PushPullHedef { u32 govde = 0, yuz = 0; bool silindirMi = false, kureMi = false, koniMi = false, delik = false; bool teget = false; // seçimle DEĞİL, teğet yayılımıyla eklendi (önizlemede ayrı renk) Vec3f normalD{}, merkezD{}, eksenD{}, eksenNoktaD{}; f32 yaricapBaz = 0; std::vector> kontur; // dünya (boş olabilir — kontur tavanı aşıldıysa) }; // Async bekleyen iş — İŞLEM-ID'Lİ: her bırakış (gövde-grubu başına) kendi hayalet kaydını // açar; bitti callback'i YALNIZ kendi kaydını siler → biri inince diğerlerinin animasyonu // sürer, üst üste jestler birbirinin hayaletini ezmez. struct PushPullBekleyen { u32 id = 0; u32 fk = 0; // feature kimliği (× iptal) bool silinebilir = false; // true=YENİ feature (× → featureIptal güvenli); false=mevcut param düzenleme // MEVCUT feature düzenlemesinde × ile geri alınacak eski parametre(ler) ({key, eskiDeger}). // Boş → geri alınamaz (× yok). Dolu → featureAsyncGeriAl (async iş iptal + param eski değere). std::vector> geriParams; f32 mesafe = 0; Vec3f merkez{}, normal{}; // etiket konumu/yönü (grubun ilk yüzü) std::vector hedefler; // bu işin yüzleri (kontur kopyası bırakış anından) }; struct PushPullDurum { bool aktif = false; // mod açık mı bool surukluyor = false; // sürükleme sürüyor bool tutamacYakin = false; // imleç okta — TUŞTAN BAĞIMSIZ (dokunmatik; bkz. kenar_fillet.hpp) u32 govde = 0, yuz = 0; // hedef gövde + yüz ordinali std::vector hedefler; // seçimden türetilen uygun yüzler ([0]=anchor) u64 hedefImza = 0; // seçim imzası (değişince hedefler yeniden kurulur) Vec3f normalD{}, merkezD{}; // dünya normal + yüz merkezi (anchor, sürükleme başında) f32 mesafe = 0; // canlı sürükleme mesafesi (mm) // Silindirik yüz → radyal offset önizlemesi (düz ötelemenin aksine her nokta kendi // radyal yönünde yeni yarıçapa ölçeklenir; sıfıra inince sivrilir). bool silindirMi = false; bool kureMi = false, koniMi = false; // küre → merkezden radyal; koni → eksenden radyal (offset) Vec3f eksenD{}, eksenNoktaD{}; // dünya eksen yönü + eksen noktası (küre: eksenNoktaD=merkez) f32 yaricapBaz = 0; // sürükleme başı yarıçap (küre: küre R; koni: kullanılmaz) bool delik = false; // iç (delik) → R azalır; dış (mil) → R artar char tipBuf[24] = ""; // sürüklerken yazılan sayı → hassas mesafe (drag override, CATIA tarzı) DugumPusula eski; // undo snapshot // Seçili yüz bir fillet/chamfer/yuz_itcek FEATURE'ına aitse → it/çek YENİ feature açmaz, // o feature'ın parametresini büyütüp küçültür (kullanıcı: "radiüs yüzeyinde it/çek R'yi değiştirsin"). u32 featureFk = 0; bool featureFillet = false; // fillet(yaricap) vs chamfer/yuz_itcek(mesafe) bool featureItcek = false; // yuz_itcek (işaretli mesafe) f32 featureBasDeger = 0; // sürükleme başı feature değeri (delta bunun üstüne biner) // Async apply hayaletleri: iş başına bir kayıt (bırakıştan sonucun inişine dek soluk // kontur + "işleniyor" etiketi). Bırakışta KOPYALANIR (yeni seçim/imza tazelemesi bozamaz). std::vector bekleyenler; u32 bekleyenSayac = 0; // iş kimliği üreteci // ---- Hizalama (referans) snap adayları ---- // Sürükleme BAŞINDA bir kez kurulur (paralelYuzler = tek O(N) tarama); her kare taramak // sıcak yol ihlali olurdu. mesafe = anchor yüzü o paralel yüzle ÇAKIŞTIRAN öteleme (mm). struct SnapAday { f32 mesafe = 0; u32 yuz = 0; // referans yüz ordinali (0 = "sıfır/başlangıç" adayı, görsel yok) }; std::vector snapAdaylar; bool snapKilitli = false; // bu kare bir adaya kilitlendi mi (etiket/renk geri bildirimi) // Kilitlenen referans yüzün konturu: yalnız KİLİT DEĞİŞİNCE örneklenir (yuzKonturlari her // kare koşmasın — sıcak yol). 0 = vurgu yok. u32 snapVurguYuz = 0; std::vector> snapVurguKontur; }; namespace pushpull_ic { inline Vec3f dunyaNok(const Dugum* d, const Vec3d& p) { const Vec3d w = d->donusum.noktaUygula(p); return {(f32)w.x, (f32)w.y, (f32)w.z}; } inline Vec3f dunyaYon(const Dugum* d, const Vec3d& v) { const Vec3d w = d->donusum.yonelim.dondur(v); return Vec3f{(f32)w.x, (f32)w.y, (f32)w.z}.birim(); } // Bir yüzey noktasını hedefin canlı it/çek sonucuna dönüştür: düz→normal ötele, // silindir/koni→eksenden radyal, küre→merkezden radyal (önizleme = commit semantiği). inline Vec3f hedefDonustur(const PushPullHedef& h, const Vec3f& p, f32 mesafe, f32 minR) { if (h.kureMi) { const Vec3f rel = p - h.eksenNoktaD; const f32 rl = rel.uzunluk(); const Vec3f dir = rl > 1e-5f ? rel * (1.0f / rl) : Vec3f{}; return h.eksenNoktaD + dir * std::max(h.yaricapBaz + mesafe, 0.05f); } if (h.koniMi) { // eksenden sabit radyal offset (frustum kayar) const Vec3f rel = p - h.eksenNoktaD; const f32 ax = rel.nokta(h.eksenD); const Vec3f rad = rel - h.eksenD * ax; const f32 rl = rad.uzunluk(); const Vec3f rb = rl > 1e-5f ? rad * (1.0f / rl) : Vec3f{}; const f32 dR = h.delik ? -mesafe : mesafe; return h.eksenNoktaD + h.eksenD * ax + rb * std::max(rl + dR, 0.02f); } if (h.silindirMi) { // yeni yarıçapa ölçekle (delik→R azalır, mil→R artar; commit min çap) const f32 rYeni = h.delik ? (h.yaricapBaz - mesafe) : (h.yaricapBaz + mesafe); const f32 rG = rYeni < minR ? minR : rYeni; const Vec3f rel = p - h.eksenNoktaD; const f32 ax = rel.nokta(h.eksenD); const Vec3f rad = rel - h.eksenD * ax; const f32 rl = rad.uzunluk(); const Vec3f rb = rl > 1e-5f ? rad * (1.0f / rl) : Vec3f{}; return h.eksenNoktaD + h.eksenD * ax + rb * rG; } return p + h.normalD * mesafe; } } // namespace pushpull_ic // ustunde: yüz üstünde/sürüklemede → main pusulaUstunde'ye OR'lar (kamera/seçim bastır). inline void pushPullCizVeIsle(PushPullDurum& pp, Sahne& sahne, Cekirdek& cekirdek, parca::PartDesignSistemi& partD, OrbitKamera& kamera, ImDrawList* dl, ImGuiIO& io, bool vpHover, f32 mx, f32 my, f32 vw, f32 vh, f32 enBoy, bool& ustunde) { using namespace pushpull_ic; (void)cekirdek; // artık feature yolu (partD) kullanılıyor; imza kenar_fillet ile paralel kalsın ustunde = false; if (!pp.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; // ok başlığı da tutamaç boyut ayarıyla ölçeklenir 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); }; // ANİMASYONLU bekleme hayaleti: kontur tabandan bırakılan mesafeye doğru AKAR ve yanıp // söner — işlem arka planda sürüyor hissi (sonuç inince bitti callback'i temizler). if (!pp.bekleyenler.empty() && !pp.surukluyor) { const f32 t = (f32)ImGui::GetTime(); const f32 f01 = std::fmod(t, 1.2f) / 1.2f; // 0→1 süpürme (tekrarlı) const f32 faz = f01 * f01 * (3.0f - 2.0f * f01); // smoothstep (yumuşak akış) const f32 nabiz = 0.55f + 0.35f * std::sin(t * 4.0f); // yanıp sönme const int aTepe = (int)(170 * nabiz), aTaban = (int)(80 * nabiz); const f32 minR = dogrudanAyar().silindirMinR; u32 iptalId = 0; // × iptal edilen işin id'si (döngü sonrası çözülür — yeni feature vs mevcut düzenleme) for (const auto& bek : pp.bekleyenler) { for (const auto& h : bek.hedefler) { for (const auto& lp : h.kontur) { ImVec2 oncekiB{}, oncekiT{}; bool var = false; for (const auto& p : lp) { ImVec2 sb, st; if (proj(p, sb) && proj(hedefDonustur(h, p, bek.mesafe * faz, minR), st)) { if (var) { dl->AddLine(oncekiB, sb, IM_COL32(150, 200, 230, aTaban), 1.2f); // taban (sabit) dl->AddLine(oncekiT, st, IM_COL32(150, 225, 255, aTepe), 2.2f); // akan tepe } dl->AddLine(sb, st, IM_COL32(150, 200, 230, aTaban / 2), 1.0f); // köşe bağları oncekiB = sb; oncekiT = st; var = true; } } } } ImVec2 me; if (proj(bek.merkez + bek.normal * (bek.mesafe * 0.5f), me)) { char b[56]; std::snprintf(b, sizeof(b), "↕ %.2f mm işleniyor…", bek.mesafe); gizmo::degerEtiketi(dl, me, b, IM_COL32(200, 215, 235, (int)(140 + 90 * nabiz))); // ✕ iptal — YENİ feature'da her zaman (featureIptal); MEVCUT feature düzenlemesinde // ayar açıksa (featureAsyncGeriAl: async iş iptal + param eski değere, geometri zaten eski). const bool xVar = bek.silinebilir || (dogrudanAyar().mevcutFeatureIptal && !bek.geriParams.empty()); if (xVar) { 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)(2600u + bek.id)); const bool tik = ImGui::InvisibleButton("##ppGhostIptal", {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; } } } if (iptalId) { auto it = std::find_if(pp.bekleyenler.begin(), pp.bekleyenler.end(), [iptalId](const PushPullBekleyen& b) { return b.id == iptalId; }); bool iptalOldu = true; if (it != pp.bekleyenler.end()) { if (it->silinebilir) partD.featureIptal(it->fk); // YENİ feature → sil (async sonuç güvenle atılır) else // MEVCUT feature → async iş iptal + param eski değere iptalOldu = partD.featureAsyncGeriAl(it->fk, it->geriParams); pp.bekleyenler.erase(it); } ui::toastGoster(iptalOldu ? "İşlem iptal edildi" : "İptal edilemedi (işlem tamamlandı)", iptalOldu ? ui::ToastTip::Bilgi : ui::ToastTip::Uyari); } } if (!pp.surukluyor) { // ---- SEÇİLİ yüz(ler)de normal oku (hover DEĞİL) + ok yakınında sürükleme ---- // ÇOK SEÇİM: seçili TÜM uygun yüzler hedeftir; ok ilk (anchor) yüzde, sürükleme // mesafesi HEPSİNE kendi normali/radyal yönünde uygulanır (Plasticity çoklu it/çek). // Ölçüt SEÇİMİN KENDİSİ (kip DEĞİL): seçili listede bir YÜZ (alt≠0) var mı? // Eskiden `sahne.kip() != SecimKip::Yuz` ile erken çıkılıyordu; ama kip'i güncellemeden // `sahne.sec()` çağıran yollar var (DM sağ-tık, Alt+genişletme, seçimi-koru, ürün ağacı) // → çap/silindir yüzüne İLK tıklayışta ok ÇIKMIYOR, kullanıcı DM kipini kapatıp açınca // (orada kip zorla Yuz'a çekiliyor) çıkıyordu. Düz yüz gizmosu (yuz_tasi_gizmo) kip'e // zaten hiç bakmıyor — bu yüzden yalnız eğri yüzlerde görülüyordu. Artık iki gizmo // AYNI ölçütü kullanır. Yüz seçimi yoksa hedefler + imza TEMİZLENİR (bayat ok kalmasın). bool yuzSecimVar = false; for (const SecimOge& o : sahne.secililer()) if (o.alt) { yuzSecimVar = true; break; } if (!yuzSecimVar) { pp.hedefler.clear(); pp.hedefImza = 0; return; } // Seçim imzası: değişmedikçe hedef listesi + konturlar YENİDEN kurulmaz (yuzKonturlari // her kare koşmasın — sıcak yol). brep pointer'ı da imzada: apply inince tazelenir. 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()); // Dönüşüm de imzada: gövde taşınır/döndürülürse dünya konturları bayatlamasın. 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 != pp.hedefImza) { pp.hedefImza = imza; pp.hedefler.clear(); geometri::YuzBilgi yb; // alt ordinali YALNIZ Yüz kipinde yüz demektir — kenar/nokta kipinde aynı numaralı // alakasız yüzde it/çek oku beliriyordu (yuz_tasi_gizmo ile aynı kök). if (sahne.kip() == SecimKip::Yuz) for (const SecimOge& o : sahne.secililer()) { if (!o.alt) continue; Dugum* dd = sahne.bul(o.govde); // Düz yüz → normal boyunca prism; silindir/küre/koni → radyal offset if (!dd || !dd->brep || !dd->brep->yuzBilgi(o.alt, yb)) continue; if (!(yb.duzlemMi || yb.silindirMi || yb.kureMi || yb.koniMi)) continue; PushPullHedef h; h.govde = o.govde; h.yuz = o.alt; h.silindirMi = yb.silindirMi; h.kureMi = yb.kureMi; h.koniMi = yb.koniMi; if (yb.kureMi) { h.eksenNoktaD = dunyaNok(dd, yb.eksenNokta); // küre merkezi h.yaricapBaz = (f32)yb.yaricap; h.merkezD = h.eksenNoktaD; // ok, anchor kürede kameraya göre hesaplanır } else { h.merkezD = dunyaNok(dd, yb.merkez); h.normalD = dunyaYon(dd, yb.normal); if (yb.silindirMi || yb.koniMi) { // eksenden radyal (silindir + koni) h.eksenD = dunyaYon(dd, yb.eksen); h.eksenNoktaD = dunyaNok(dd, yb.eksenNokta); h.yaricapBaz = (f32)yb.yaricap; // Dış radyal-dışa yön ile dış normal ters ise DELİK (normal içe bakıyor). const Vec3f rel = h.merkezD - h.eksenNoktaD; const Vec3f rad = rel - h.eksenD * rel.nokta(h.eksenD); h.delik = rad.nokta(h.normalD) < 0.0f; } } pp.hedefler.push_back(std::move(h)); } // ---- TEĞET YAYILIMI ("Grow: Moving" — CATIA/Creo tangency propagation) ---- // tanjantYuzler G1-teğet komşu grubunu verir (fillet dahil), ama yuzItCek her yüzü KENDİ // normali boyunca öteler: bu yalnız normali tohumla AYNI YÖNE bakan DÜZLEM parçalarında // rijit ötelemeye denktir. Fillet/eğri komşu aynı mesafeyle "ötelenmez", yarıçapı değişir // → blend bozulur. Bu yüzden yayılım YALNIZ "aynı mesafe aynı sonucu verir" eşdeğerlik // sınıflarına açıktır (bilinçli kapsam): // DÜZLEM tohum → aynı yöne bakan düzlem komşuları (rijit öteleme eşdeğeri) // SİLİNDİR tohum → EŞ EKSENLİ + EŞ YARIÇAP + aynı delik/mil yönlü silindir komşuları // (silindirRadyalOfset ikisine de aynı yarıçap değişimini uygular; // tipik vaka: seam/kenar ile parçalanmış TEK silindirik yüzey). // Küre/koni tohumda yayılım yok (koni yarıçapı boyunca değişir → eşdeğerlik tanımsız). const size_t tohumSayi = pp.hedefler.size(); if (dogrudanAyar().tegetYayilim && tohumSayi && tohumSayi <= (size_t)dogrudanAyar().tegetTohumMax) { const f32 cosTol = std::cos(dogrudanAyar().tegetAciTol * 3.14159265358979323846f / 180.0f); const f32 rTol = dogrudanAyar().tegetYaricapTol; for (size_t ti = 0; ti < tohumSayi; ++ti) { const PushPullHedef tohum = pp.hedefler[ti]; // kopya: vektör büyürken referans bayatlar if (tohum.kureMi || tohum.koniMi) continue; Dugum* dd = sahne.bul(tohum.govde); if (!dd || !dd->brep) continue; for (u32 ord : dd->brep->tanjantYuzler(tohum.yuz, dogrudanAyar().tegetAciTol)) { if (ord == tohum.yuz) continue; const bool zatenVar = std::any_of(pp.hedefler.begin(), pp.hedefler.end(), [&](const PushPullHedef& e) { return e.govde == tohum.govde && e.yuz == ord; }); if (zatenVar) continue; geometri::YuzBilgi yk; if (!dd->brep->yuzBilgi(ord, yk)) continue; PushPullHedef h; h.govde = tohum.govde; h.yuz = ord; h.teget = true; if (tohum.silindirMi) { if (!yk.silindirMi) continue; if (std::abs((f32)yk.yaricap - tohum.yaricapBaz) > rTol) continue; const Vec3f eksK = dunyaYon(dd, yk.eksen); if (std::abs(eksK.nokta(tohum.eksenD)) < cosTol) continue; // eş eksen (yön serbest) const Vec3f nokK = dunyaNok(dd, yk.eksenNokta); // Eksenler PARALEL yetmez, ÇAKIŞIK olmalı: eksen noktaları arası dik uzaklık ~0. const Vec3f fark = nokK - tohum.eksenNoktaD; if ((fark - tohum.eksenD * fark.nokta(tohum.eksenD)).uzunluk() > rTol) continue; h.silindirMi = true; h.merkezD = dunyaNok(dd, yk.merkez); h.normalD = dunyaYon(dd, yk.normal); h.eksenD = tohum.eksenD; // tohumun ekseni: radyal önizleme tek çerçevede kalsın h.eksenNoktaD = tohum.eksenNoktaD; h.yaricapBaz = (f32)yk.yaricap; const Vec3f rel = h.merkezD - h.eksenNoktaD; const Vec3f rad = rel - h.eksenD * rel.nokta(h.eksenD); h.delik = rad.nokta(h.normalD) < 0.0f; if (h.delik != tohum.delik) continue; // delik ↔ mil karışırsa biri şişer biri daralır } else { if (!yk.duzlemMi) continue; const Vec3f nK = dunyaYon(dd, yk.normal); // İŞARETLİ hizalama (abs DEĞİL): ters bakan yüz aynı mesafeyle GERİ giderdi. if (nK.nokta(tohum.normalD) < cosTol) continue; h.merkezD = dunyaNok(dd, yk.merkez); h.normalD = nK; } pp.hedefler.push_back(std::move(h)); } } } // Kontur örnekleme: Sadece imza değiştiğinde (her yüz bir kez frame başı örneklenir). // Satır 273'de hedefler temizlendi, burada konturlar ekleniyor → cache tamam. if (imza != pp.hedefImza) { if (pp.hedefler.size() <= (size_t)dogrudanAyar().cokluKonturMax) { for (auto& h : pp.hedefler) { Dugum* dd = sahne.bul(h.govde); if (!dd || !dd->brep) continue; for (const auto& lp : dd->brep->yuzKonturlari(h.yuz)) { std::vector w; w.reserve(lp.size()); for (const auto& p : lp) w.push_back(dunyaNok(dd, p)); h.kontur.push_back(std::move(w)); } } } } } if (pp.hedefler.empty()) return; // Oto-ok kapalıysa: seçili yüzde ok ZIPLAMAZ; kullanıcı Ctrl basınca deliberatif belirir // (yüz seçimi/ölçüm serbest kalır, kazara it/çek olmaz). Açıkken eski davranış (hep göster). if (!dogrudanAyar().pushPullOtoOk && !ImGui::IsKeyDown(ImGuiMod_Ctrl)) return; // Anchor = SON SEÇİLEN uygun yüz: ok, imlecin en son dokunduğu yüzde belirir (jest akışı). // Teğet yayılımıyla eklenenler listenin SONUNA biner → back() anchor'ı kaydırırdı; okun // seçilmeyen bir yüze zıplamaması için son SEÇİM kaynaklı hedef aranır. const PushPullHedef* ancP = &pp.hedefler.back(); for (auto it = pp.hedefler.rbegin(); it != pp.hedefler.rend(); ++it) if (!it->teget) { ancP = &*it; break; } const PushPullHedef& anc = *ancP; Vec3f merkez = anc.merkezD, normal = anc.normalD; if (anc.kureMi) { // Küre: yüzey noktası yok. Ok = kameraya bakan yüzey noktasında, dışa radyal. Commit // yuzItCek küresel radyal offset yapar (önizleme yaklaşık). MVP: R=yaricap. const Vec3f toCam = kamera.konum() - anc.eksenNoktaD; normal = toCam.uzunlukKare() > 1e-9f ? toCam.birim() : Vec3f{0, 0, 1}; merkez = anc.eksenNoktaD + normal * anc.yaricapBaz; // kameraya bakan yüzey noktası } const f32 L = std::max(kamera.mesafe() * dogrudanAyar().okBoyOran, 0.03f); ImVec2 s0, s1; const bool okVar = proj(merkez, s0) && proj(merkez + normal * L, s1); // Fare ok çizgisine yakın mı? (nokta-segment mesafesi ≤ 14px → gizmo tutamacı) bool okYakin = false; if (okVar) { const f32 dx = s1.x - s0.x, dy = s1.y - s0.y; const f32 L2 = dx * dx + dy * dy; f32 t = L2 > 1e-6f ? ((io.MousePos.x - s0.x) * dx + (io.MousePos.y - s0.y) * dy) / L2 : 0.0f; t = t < 0.0f ? 0.0f : (t > 1.0f ? 1.0f : t); const f32 qx = s0.x + t * dx - io.MousePos.x, qy = s0.y + t * dy - io.MousePos.y; const f32 hr = 14.0f * dokunmaHedefCarpani(); // parmakta büyür, farede 14px okYakin = vpHover && (qx * qx + qy * qy) <= hr * hr; } // Ok yakınındayken sol jestleri bastır (orbit/pan serbest kalsın aksi halde). ustunde = okYakin && ImGui::IsMouseDown(ImGuiMouseButton_Left); pp.tutamacYakin = okYakin; // dokunmatik: tuştan bağımsız yakınlık if (okVar) { const ImU32 renk = okYakin ? IM_COL32(150, 235, 255, 255) : IM_COL32(120, 220, 255, 200); const bool glow = okYakin && dogrudanAyar().glowAcik; // ---- Toggle: Gizmo çevresi önizleme çemberleri ---- if (dogrudanAyar().gizmoCevreOnizleme) { const f32 cevreY = std::max(kamera.mesafe() * dogrudanAyar().okBoyOran, 0.03f); dl->AddCircle(s0, cevreY * 0.15f, IM_COL32(80, 140, 255, 60), 0, 1.0f); // Soft background circle } if (glow) gizmo::glowCizgi(dl, s0, s1, renk, 3.2f); // hover → neon glow (futuristik) else dl->AddLine(s0, s1, renk, okYakin ? 3.2f : 2.6f); // ---- Toggle: Yüz Taşı çember önizlemesi ---- const f32 dr = (okYakin ? 4.0f : 3.0f) * dogrudanAyar().tutamacBoy; if (dogrudanAyar().yuzTasiCevreGoster) { dl->AddCircleFilled(s0, dr, renk); // Ok başı dolgu çember } else { okBasi(s0, s1, renk); // Kapalıysa sadece üçgen göster } if (glow) gizmo::glowDaire(dl, s0, dr + 1.0f, renk); // Neon efekt } // ---- Aç/kapa çipleri (KLAVYESİZ — DM tarzı: kısayol değil, dokunulabilir çip) ---- // Teğet yayılımı + hizalama snap'i ayarlar penceresine gitmeden buradan açılır/kapanır. // dogrudanAyar() alanı doğrudan değişir; AyarSistemi kaydının .al lambdası AYNI alanı // okuduğu için ayarlar penceresi ve kalıcı kayıt bunu olduğu gibi görür. bool cipUstunde = false; if (okVar && !pp.hedefler.empty()) { ImFont* fnt = ImGui::GetFont(); const float fs = 12.5f, py = 3.0f; float cx = s0.x - 2.0f; const float cy = s0.y + 16.0f; const auto cipCiz = [&](const char* lbl, bool aktif, ImU32 renk, const char* ipucu) { const ImVec2 ts = fnt->CalcTextSizeA(fs, FLT_MAX, 0.0f, lbl); const ImVec2 mn{cx, cy}, mx{cx + ts.x + 12.0f, cy + ts.y + py * 2.0f}; const bool hov = vpHover && io.MousePos.x >= mn.x && io.MousePos.x <= mx.x && io.MousePos.y >= mn.y && io.MousePos.y <= mx.y; dl->AddRectFilled(mn, mx, IM_COL32(24, 26, 30, aktif ? 240 : 190), 5.0f); dl->AddRect(mn, mx, aktif ? renk : ((renk & 0x00FFFFFFu) | (hov ? 0xA0000000u : 0x55000000u)), 5.0f, 0, aktif ? 1.8f : 1.0f); dl->AddText(fnt, fs, {mn.x + 6.0f, mn.y + py}, aktif ? renk : IM_COL32(200, 205, 210, hov ? 255 : 180), lbl); cx = mx.x + 4.0f; if (!hov) return false; cipUstunde = true; if (ipucu) ImGui::SetTooltip("%s", ipucu); return ImGui::IsMouseClicked(ImGuiMouseButton_Left); }; // ÜÇ KONUM tek çiple (klavyesiz): kapalı → teğet → teğet+blend → kapalı. const bool tY = dogrudanAyar().tegetYayilim, tB = dogrudanAyar().tegetBlendKorur; if (cipCiz(tY && tB ? "⌥ teğet+blend" : "⌥ teğet", tY, tY && tB ? IM_COL32(255, 150, 210, 255) : IM_COL32(255, 190, 90, 255), "Teğet yayılımı (tıkla → sırayla: kapalı · teğet · teğet+blend)\n" "teğet: birlikte hareket edecek EŞDEĞER komşuları kat (amber konturlar).\n" "teğet+blend: fillet'i sök → ötele → aynı yarıçapla yeniden kur (fillet korunur).")) { if (!tY) { dogrudanAyar().tegetYayilim = true; dogrudanAyar().tegetBlendKorur = false; } else if (!tB) dogrudanAyar().tegetBlendKorur = true; else { dogrudanAyar().tegetYayilim = false; dogrudanAyar().tegetBlendKorur = false; } } if (cipCiz("⇥ hiza", dogrudanAyar().pushPullHizaSnap, IM_COL32(120, 240, 170, 255), "Hizalama snap'i: mesafe komşu paralel yüz düzlemlerine ve sıfıra oturur.\n" "Shift = kademe snap'i · klavyeden yazılan değer ikisini de ezer.")) dogrudanAyar().pushPullHizaSnap = !dogrudanAyar().pushPullHizaSnap; if (cipUstunde) { ustunde = true; // çipin üstünde orbit/pan başlamasın okYakin = false; // ve çipe tıklamak sürüklemeyi tetiklemesin pp.tutamacYakin = false; } } // Kontur vurgusu (soluk) — TÜM hedef yüzler (kullanıcı neyin hareket edeceğini görür). // Teğet yayılımıyla gelenler AMBER: "bunu ben seçmedim, birlikte gelecek" ayrımı görünür. for (const auto& h : pp.hedefler) { const ImU32 kRenk = h.teget ? IM_COL32(255, 190, 90, 140) : IM_COL32(120, 220, 255, 130); for (const auto& lp : h.kontur) { // Kesit aktifken kırpılan yarıdaki kontur parçası çizilmez (2B çizgi shader // clip'i görmez → segment dünya-uzayında kırpılır). 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, kRenk, 1.4f); } } } if (okYakin && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { pp.surukluyor = true; pp.govde = anc.govde; pp.yuz = anc.yuz; pp.mesafe = 0; pp.tipBuf[0] = 0; // yeni sürükleme → yazılan sayı sıfırla pp.normalD = normal; pp.merkezD = merkez; pp.silindirMi = anc.silindirMi; pp.kureMi = anc.kureMi; pp.koniMi = anc.koniMi; pp.eksenD = anc.eksenD; pp.eksenNoktaD = anc.eksenNoktaD; pp.yaricapBaz = anc.yaricapBaz; pp.delik = anc.delik; pp.eski = sahne.anlik(anc.govde); // ---- Hizalama snap adayları (SÜRÜKLEME BAŞINDA BİR KEZ — her kare taramak yasak) ---- // paralelYuzler: gövdedeki anchor'a paralel TÜM düz yüzler (bağsız dahil). Her birinin // düzlemine anchor'ı taşıyan işaretli mesafe bir adaydır → "komşu yüz hizasına otur". // 0 da adaydır (başlangıca dön). Yalnız DÜZ anchor'da anlamlı (silindirde çap-offset). pp.snapAdaylar.clear(); pp.snapKilitli = false; pp.snapVurguYuz = 0; pp.snapVurguKontur.clear(); if (dogrudanAyar().pushPullHizaSnap && !anc.silindirMi && !anc.kureMi && !anc.koniMi) { if (Dugum* dsn = sahne.bul(anc.govde)) { if (dsn->brep) { pp.snapAdaylar.push_back({0.0f, 0}); // başlangıca dön geometri::YuzBilgi yp; for (u32 po : dsn->brep->paralelYuzler(anc.yuz, dogrudanAyar().tegetAciTol)) { if (po == anc.yuz || !dsn->brep->yuzBilgi(po, yp)) continue; const f32 d = (dunyaNok(dsn, yp.merkez) - anc.merkezD).nokta(normal); if (std::abs(d) < 1e-4f) continue; const bool ayni = std::any_of(pp.snapAdaylar.begin(), pp.snapAdaylar.end(), [d](const PushPullDurum::SnapAday& e) { return std::abs(e.mesafe - d) < 1e-4f; }); if (!ayni) pp.snapAdaylar.push_back({d, po}); } } } } // TEK yüz seçiliyken: yüz bir fillet/chamfer/yuz_itcek feature'ına aitse → it/çek O // FEATURE'ı düzenler. Çok seçimde daima YENİ çoklu it/çek (düzenleme belirsiz olurdu). // Sayım SEÇİM kaynaklı hedefler üzerinden: teğet yayılımı açıkken tek yüz seçimi de // hedefler'i büyütür, ham size() düzenleme modunu sessizce düşürürdü. pp.featureFk = 0; const size_t secimHedefSayi = (size_t)std::count_if( pp.hedefler.begin(), pp.hedefler.end(), [](const PushPullHedef& e) { return !e.teget; }); if (secimHedefSayi == 1) { if (const u32 ffk = partD.yuzFeatureBul(anc.govde, anc.yuz)) { const std::string ft = partD.featureTip(ffk); if (ft == "fillet" || ft == "chamfer" || ft == "yuz_itcek") { pp.featureFk = ffk; pp.featureFillet = (ft == "fillet"); pp.featureItcek = (ft == "yuz_itcek"); pp.featureBasDeger = (f32)partD.featureDeger(ffk, pp.featureFillet ? "yaricap" : "mesafe"); gunluk::bilgi("[dm] yüz {} → feature {} ({}) DÜZENLEME modu (yeni feature değil)", anc.yuz, ffk, ft); } } } } return; } // ---- Sürükleme: canlı wireframe önizleme ---- ustunde = true; f32 pxBirim = 0; // 1 mm'nin ekrandaki piksel karşılığı (snap toleransı bununla ölçeklenir) ImVec2 c0, c1; if (proj(pp.merkezD, c0) && proj(pp.merkezD + pp.normalD, c1)) { const f32 vx = c1.x - c0.x, vy = c1.y - c0.y, L2 = vx * vx + vy * vy; pxBirim = std::sqrt(L2); if (L2 > 1e-6f) { const f32 dm = (io.MouseDelta.x * vx + io.MouseDelta.y * vy) / L2; // Silindirde -dm terslemesi ESKİ ham-orientation delik hatasını telafi ediyordu; commit // gerçek yüzey normaline geçince çifte-tersleme oldu (kullanıcı: R büyüt/küçült ters). // Ok yönünde sürükleme = mesafe artar (mil R↑, delik R↓) — tüm yüzey tiplerinde aynı. pp.mesafe += dm; } } // Sürüklerken SAYI yaz → hassas mesafe (drag'i override; CATIA/Fusion tarzı). Backspace sil. for (int i = 0; i < io.InputQueueCharacters.Size; ++i) { const ImWchar c = io.InputQueueCharacters[i]; const size_t l = std::strlen(pp.tipBuf); if (((c >= '0' && c <= '9') || c == '.' || (c == '-' && l == 0)) && l < sizeof(pp.tipBuf) - 1) { pp.tipBuf[l] = (char)c; pp.tipBuf[l + 1] = 0; } } if (ImGui::IsKeyPressed(ImGuiKey_Backspace)) { const size_t l = std::strlen(pp.tipBuf); if (l) pp.tipBuf[l - 1] = 0; } const bool tipVar = pp.tipBuf[0] && !(pp.tipBuf[0] == '-' && !pp.tipBuf[1]); if (tipVar) pp.mesafe = (f32)std::atof(pp.tipBuf); // yazılan değer drag'i geçersiz kılar // ---- SNAP ---- Yazılan sayı KUTSAL: elle girilen değer asla yuvarlanmaz (yuz_tasi kalıbı). // Shift → kademe (grid) snap; Shift YOKKEN → hizalama snap'i (komşu paralel yüz düzlemine // veya sıfıra mıknatıslanma). Tolerans PİKSEL cinsinden → yakınlaşınca hassaslaşır, uzakta // kolay yakalanır (sabit mm tolerans zoom'da yanlış hissettiriyordu). // TÜMÜ tek bayrağın arkasında: kapalıyken (varsayılan) mesafe eskisi gibi tamamen serbest — // Shift kademe snap'i de dahil, yoksa bayrak kapalıyken davranış sessizce değişirdi. pp.snapKilitli = false; if (!tipVar && dogrudanAyar().pushPullHizaSnap) { if (io.KeyShift && dogrudanAyar().snapMesafe > 1e-4f) { const f32 sn = dogrudanAyar().snapMesafe; pp.mesafe = std::round(pp.mesafe / sn) * sn; pp.snapKilitli = true; } else if (!pp.snapAdaylar.empty() && pxBirim > 1e-4f) { const f32 tolMm = dogrudanAyar().pushPullSnapPx / pxBirim; f32 enIyi = 0, enIyiFark = tolMm; u32 enIyiYuz = 0; bool bulundu = false; for (const PushPullDurum::SnapAday& aday : pp.snapAdaylar) { const f32 fark = std::abs(pp.mesafe - aday.mesafe); if (fark <= enIyiFark) { enIyiFark = fark; enIyi = aday.mesafe; enIyiYuz = aday.yuz; bulundu = true; } } if (bulundu) { pp.mesafe = enIyi; pp.snapKilitli = true; // Referans yüz konturu: YALNIZ kilit değişince örneklenir (yuzKonturlari pahalı). if (enIyiYuz != pp.snapVurguYuz) { pp.snapVurguYuz = enIyiYuz; pp.snapVurguKontur.clear(); if (enIyiYuz) { if (Dugum* dv = sahne.bul(pp.govde)) { if (dv->brep) { for (const auto& lp : dv->brep->yuzKonturlari(enIyiYuz)) { std::vector w; w.reserve(lp.size()); for (const auto& p : lp) w.push_back(dunyaNok(dv, p)); pp.snapVurguKontur.push_back(std::move(w)); } } } } } } } } if (!pp.snapKilitli) { pp.snapVurguYuz = 0; pp.snapVurguKontur.clear(); } const bool ekle = pp.mesafe >= 0; const ImU32 col = ekle ? IM_COL32(120, 220, 255, 240) : IM_COL32(255, 150, 90, 240); const ImU32 baglanti = ekle ? IM_COL32(120, 220, 255, 110) : IM_COL32(255, 150, 90, 110); // Yeni yarıçap (silindir): delik→R azalır, mil→R artar. Commit silindir ÇAP-offset yapar // (koni değil) ve R<~0.01'de başarısız → önizleme de min yarıçaplı silindir gösterir // (eksene toplayıp SİVRİLTMEZ), böylece önizleme=commit. Gerçek koni/sivrilme ayrı feature. const f32 rYeni = pp.delik ? (pp.yaricapBaz - pp.mesafe) : (pp.yaricapBaz + pp.mesafe); const f32 minR = dogrudanAyar().silindirMinR; const f32 rGecerli = rYeni < minR ? minR : rYeni; // commit min çap ile hizalı (ayarlanabilir) // Her hedef yüzün konturu KENDİ dönüşümüyle canlı önizlenir (çok seçim: hepsi birlikte akar). for (const auto& h : pp.hedefler) { for (const auto& lp : h.kontur) { std::vector tepe, taban; tepe.reserve(lp.size()); taban.reserve(lp.size()); for (const auto& p : lp) { ImVec2 sb, st; const bool okb = proj(p, sb); const bool okt = proj(hedefDonustur(h, p, pp.mesafe, minR), st); if (okb && okt) { 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); } } } // Kilitlenen hizalama referansı: "neye oturdum" görünür olsun (yeşil, kesikli hissi için ince). if (pp.snapKilitli && !pp.snapVurguKontur.empty()) { for (const auto& lp : pp.snapVurguKontur) { 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 q0, q1; if (proj(a, q0) && proj(b, q1)) dl->AddLine(q0, q1, IM_COL32(120, 240, 170, 200), 2.0f); } } } // Mesafe/yarıçap etiketi + ok (anchor yüz üzerinden) ImVec2 ml; Vec3f mid = pp.merkezD + pp.normalD * pp.mesafe; for (const auto& h : pp.hedefler) if (h.govde == pp.govde && h.yuz == pp.yuz) { // anchor hedefi (sürükleme başındaki) mid = hedefDonustur(h, pp.merkezD, pp.mesafe, minR); break; } if (proj(mid, ml)) { char b[48]; if (pp.featureFk) { // feature düzenleme: mevcut R/pah/↕ + delta const f32 yeni = pp.featureItcek ? (pp.featureBasDeger + pp.mesafe) : std::max(pp.featureBasDeger + pp.mesafe, 0.01f); std::snprintf(b, sizeof(b), "%s %.2f mm ✎", pp.featureItcek ? "↕" : (pp.featureFillet ? "◜ R" : "◺ pah"), yeni); } else if (tipVar) std::snprintf(b, sizeof(b), "%s mm ▏", pp.tipBuf); // canlı yazılan hassas değer else if (pp.silindirMi) std::snprintf(b, sizeof(b), "R %.2f mm", rGecerli); else if (pp.kureMi) std::snprintf(b, sizeof(b), "R %.2f mm", std::max(pp.yaricapBaz + pp.mesafe, 0.05f)); else if (pp.hedefler.size() > 1) std::snprintf(b, sizeof(b), "%s %.2f mm ×%d%s", ekle ? "↑ çek" : "↓ it", std::abs(pp.mesafe), (int)pp.hedefler.size(), pp.snapKilitli ? " ⇥" : ""); else std::snprintf(b, sizeof(b), "%s %.2f mm%s", ekle ? "↑ çek" : "↓ it", std::abs(pp.mesafe), pp.snapKilitli ? " ⇥" : ""); // Snap kilitliyken etiket YEŞİL: "serbest değil, bir referansa oturdu" anlık geri bildirim. const ImU32 eRenk = pp.snapKilitli ? IM_COL32(120, 240, 170, 255) : (ekle ? IM_COL32(120, 220, 255, 255) : IM_COL32(255, 150, 90, 255)); gizmo::degerEtiketi(dl, ml, b, eRenk); } // ESC = İPTAL (işlem yapmadan çık) · Enter = ONAYLA (yazılan hassas değerle commit — // fareyi bırakmadan). CATIA/Plasticity jest standardı. if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { pp.surukluyor = false; pp.tipBuf[0] = 0; pp.featureFk = 0; return; } if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) || ImGui::IsKeyPressed(ImGuiKey_Enter)) { const f32 mes = pp.mesafe; // yazılan sayı varsa pp.mesafe onu yansıtır (drag override) pp.surukluyor = false; pp.tipBuf[0] = 0; const u32 fFk = pp.featureFk; const bool fFil = pp.featureFillet, fItc = pp.featureItcek; const f32 fBas = pp.featureBasDeger; pp.featureFk = 0; // İşlem sonrası SEÇİMİ KORU (Plasticity akışı): uygulanan it/çek'in ötelediği yüz(ler) // yeni sonuç düğümünde yeniden seçilir → ok hemen belirir, kullanıcı aynı yüzü art arda // itip çekebilir (eskiden her jest için yeniden seçim gerekiyordu). Ayarla kapatılabilir. const auto seciminiKoru = [&partD, &sahne](u32 fk) { if (!dogrudanAyar().secimiKoru || !fk) return; const u32 sd = partD.featureSonucDugum(fk); if (!sd) return; for (u32 yy : partD.featureUretilenYuzler(fk)) if (yy) sahne.sec({sd, yy}, /*coklu*/ true); }; // İş-ID'li hayalet: bitti callback'i YALNIZ kendi kaydını siler (diğer bekleyen işlerin // animasyonu sürer). Toast async sonuç indiğinde main-thread'de koşar. const auto bittiKur = [&pp, seciminiKoru](u32 isId, u32 fk, const char* okMsg, const char* hataMsg) { return [&pp, seciminiKoru, isId, fk, okMsg, hataMsg](bool ok) { pp.bekleyenler.erase( std::remove_if(pp.bekleyenler.begin(), pp.bekleyenler.end(), [isId](const PushPullBekleyen& b) { return b.id == isId; }), pp.bekleyenler.end()); if (ok) seciminiKoru(fk); ui::toastGoster(ok ? okMsg : hataMsg, ok ? ui::ToastTip::Basari : ui::ToastTip::Hata); }; }; // Hayalet kaydı: işin KENDİ hedef kopyasıyla (kontur bırakış anından; yeni seçim bozamaz). const auto hayaletEkle = [&pp, mes](u32 isId, u32 fk, bool silinebilir, std::vector hedefler, std::vector> geriParams = {}) { PushPullBekleyen b; b.id = isId; b.fk = fk; b.silinebilir = silinebilir; b.geriParams = std::move(geriParams); b.mesafe = mes; if (!hedefler.empty()) { b.merkez = hedefler.front().merkezD; b.normal = hedefler.front().normalD; } b.hedefler = std::move(hedefler); pp.bekleyenler.push_back(std::move(b)); }; if (std::abs(mes) > 1e-3f) { // Bayat seçim temizliği: ordinaller değişecek — sonuç indiğinde sahipli yüzler // yeniden seçilir (seciminiKoru). Ayar kapalıysa eski davranış (seçime dokunma). if (dogrudanAyar().secimiKoru) sahne.secimTemizle(); if (fFk) { // Feature YÜZÜ → mevcut fillet/chamfer/yuz_itcek'i büyüt/küçült (YENİ feature YOK). // Delta mevcut değerin üstüne biner; fillet/pah negatife inmez, it/çek işaretli kalır. f32 yeni = fBas + mes; if (!fItc) yeni = std::max(yeni, 0.01f); const char* key = fFil ? "yaricap" : "mesafe"; const char* okMsg = fFil ? "Fillet güncellendi" : (fItc ? "Yüz İt/Çek güncellendi" : "Chamfer güncellendi"); const u32 isId = ++pp.bekleyenSayac; // Undo anlığı param YAZILMADAN önce alınır (async yol da pAyarla'yı hemen yapar, // yalnız OCCT hesabı worker'a gider) — TAM harita, kısmi snapshot yuzSayi'yı silerdi. const auto ppOnce = dmFeatureParamAnlik(partD, fFk); const auto temelCb = bittiKur(isId, fFk, okMsg, "Güncelleme başarısız"); const bool as = dogrudanAyar().asyncApply && partD.featureAsyncUygula(fFk, {{key, (f64)yeni}}, [&cekirdek, &partD, fFk, ppOnce, temelCb](bool ok) { temelCb(ok); if (ok) dmFeatureDuzenleUndo(cekirdek, partD, fFk, ppOnce, dmFeatureParamAnlik(partD, fFk)); }); // mevcut feature → yeni-feature değil (× featureAsyncGeriAl ile: param eski değere, geometri zaten eski) if (as) hayaletEkle(isId, fFk, /*silinebilir*/ false, pp.hedefler, {{key, (f64)fBas}}); else { partD.featureGuncelle(fFk, key, (f64)yeni); dmFeatureDuzenleUndo(cekirdek, partD, fFk, ppOnce, dmFeatureParamAnlik(partD, fFk)); seciminiKoru(fFk); ui::toastGoster(okMsg, ui::ToastTip::Basari); } } else { // Feature-DIŞI yüz(ler) → GÖVDE BAŞINA tek parametrik "Yüz İt/Çek" feature (yuzSayi + // yuz0..N; fillet'in kenarSayi kalıbı). mesafe İŞARETLİ, her yüz kendi normali/radyali // boyunca (silindir/küre/koni radyal offset dahil). Async kuyruk gövdeleri sırayla işler; // her gövde grubunun KENDİ hayalet kaydı vardır (biri inince diğeri kalır). std::vector>> gruplar; for (const auto& h : pp.hedefler) { auto it = std::find_if(gruplar.begin(), gruplar.end(), [&](const auto& gr) { return gr.first == h.govde; }); if (it == gruplar.end()) gruplar.push_back({h.govde, {h}}); else it->second.push_back(h); } for (auto& [gg, grupHedefler] : gruplar) { const u32 fk = partD.featureBaslat("yuz_itcek", VARLIK_YOK, gg); if (!fk) { ui::toastGoster("Yüz İt/Çek başarısız (katı gövde gerek)", ui::ToastTip::Hata); continue; } std::vector> params; for (std::size_t i = 0; i < grupHedefler.size(); ++i) params.emplace_back("yuz" + std::to_string(i), (f64)grupHedefler[i].yuz); params.emplace_back("yuzSayi", (f64)grupHedefler.size()); params.emplace_back("mesafe", (f64)mes); // Blend koruma: teğet yayılımı AÇIKKEN anlamlı (motor teğet blend'i söküp yeniden kurar). // Parametre olarak yazılır → ürün ağacından da düzenlenebilir, dosyaya da gider. if (dogrudanAyar().tegetYayilim && dogrudanAyar().tegetBlendKorur) { params.emplace_back("blendKorur", 1.0); params.emplace_back("tegetTol", (f64)dogrudanAyar().tegetAciTol); } const u32 isId = ++pp.bekleyenSayac; const auto temelCb = bittiKur(isId, fk, "Yüz İt/Çek feature eklendi", "Yüz it/çek başarısız (mesafe geçersiz olabilir)"); const bool as = dogrudanAyar().asyncApply && partD.featureAsyncUygula(fk, params, [&cekirdek, &partD, fk, temelCb](bool ok) { temelCb(ok); if (ok) dmFeatureEkleUndo(cekirdek, partD, fk); }); if (as) hayaletEkle(isId, fk, /*silinebilir*/ true, std::move(grupHedefler)); // yeni feature → × else { // TOPLU + tek recompute (ayrı featureGuncelle'ler ara recompute israfıydı). partD.featureParamToplu(fk, params); if (partD.featureOnayla(fk)) dmFeatureEkleUndo(cekirdek, partD, fk); seciminiKoru(fk); ui::toastGoster("Yüz İt/Çek feature eklendi", ui::ToastTip::Basari); } } } // Tutamaç it/çek de KomutCalisti yayar (makro + öğrenme) — mes>eşik = iş yapıldı. dmTutamacOlayiYay(cekirdek, "Değiştir", "Yüz İt/Çek"); } } } } // namespace fiko