#pragma once // Kenar gizmosu (Fikri 07-22 "kenardan tutup gizmo ile"): TEK kenar seçiliyken kenar // ortasında ⟀ DÖNDÜRME YAYI (eksen = kenar yönü → Kenardan Eğ) + ⇱ OK (komşu düz yüz // normali → Kenardan Uzat). KG1: hedef çözümü + çizim + hover (ustunde ile kamera/seçim // bastırılır); sürükleme işlevi KG2'de bağlanır (yay → kenar_eg, ok → yuz_itcek). // Ayar: dogrudan.kenar_gizmo (kapalı = hiç çizilmez, eski davranış). #include #include #include #include #include #include #include #include #include "dm_tutamac_olay.hpp" // dmFeatureEkleUndo + dmTutamacOlayiYay #include "dogrudan_ayar.hpp" #include "fiko/parca/part_design_sistemi.hpp" #include "fiko/render/orbit_kamera.hpp" #include "fiko/sahne/sahne.hpp" #include "fiko/ui/bilesen_ek.hpp" // ui::toastGoster #include "imgui.h" namespace fiko { struct KenarGizmoDurum { bool aktif = false; // DM workbench'inde açık (giriş/çıkış kancaları) u32 govde = 0; // çözülmüş hedef (0 = uygun kenar seçimi yok) u32 kenar = 0, yuz = 0; // menteşe kenarı + komşu düz yüz (eğilecek/itilecek) Vec3f merkezD{}; // dünya: kenar ortası (gizmo ankrajı) Vec3f basD{}, sonD{}; // dünya: kenar uçları (KG5 hayalet önizleme şeridi) Vec3f eksenD{}; // dünya: kenar yönü (yay ekseni) Vec3f okYonD{}; // dünya: komşu yüz normali (uzatma oku) // KG3 (Fikri: "diğer yüze açı veremiyoruz"): kenara komşu HER İKİ düz yüz aday tutulur; // aktif olmayanın normali yönünde soluk hayalet ok çizilir, tıklayınca hedef takas. u32 yuzAday[2] = {0, 0}; // düz komşu yüz ordinalleri (adaySay kadarı dolu) Vec3f okAday[2]{}; // adayların dünya normalleri // KG4 (Fikri: "sadece o kenar uzasın, karşı kenar sabit"): yüz DÜZLEM-İÇİ, kenara dik, // dışarı yön — ok bu yöne bakar, bırakışta kenar_tasi feature (ayar: kenar_uzat_duzlemde). Vec3f uzatAdayD[2]{}; // düzlem-içi dışarı yönün dünyası (ok çizimi + mm eşlemi) // KG6 (Fikri: "önizleme TAM olsun"): iki komşu yüzün dünya konturları — sürüklerken // kenara değen noktalar ötelenir → işlem SONRASI görünümün hayaleti (geometri hesabı yok). std::vector> adayKontur[2]; int adaySay = 0, adaySec = 0; int hover = -1; // -1 yok · 0 yay · 1 ok · 2 hayalet ok (yüz takası) u64 hedefImza = 0; // (govde<<32)|kenar — seçim değişince yeniden çözülür // KG2: sürükleme durumu — yay: canlı ° · ok: canlı mm; BIRAKIŞTA feature uygulanır // (her karede DraftAngle/BRepFeat pahalı — canlı geometri önizleme bilinçli YOK). bool surukluyor = false; int tutamac = -1; // sürüklenen: 0 yay · 1 ok f32 deger = 0; // yay: derece · ok: mm (işaretli) char tipBuf[24] = ""; // sürüklerken yazılan sayı → hassas değer (drag override) }; // ustunde: tutamaç üstünde/sürüklemede → çağıran pusulaUstunde'ye OR'lar (jest bastırma). inline void kenarGizmoCizVeIsle(KenarGizmoDurum& kg, 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) { ustunde = false; if (!kg.aktif || !dogrudanAyar().kenarGizmo) { kg.surukluyor = false; return; } if (!kg.surukluyor) kg.hover = -1; // Hedef: kenar kipinde TEK öğe seçili + düz kenar + komşu DÜZ yüz (Kenardan Eğ önkoşulu). u32 g = 0, ke = 0; int say = 0; for (const SecimOge& o : sahne.secililer()) { if (!o.alt) { say = 99; break; } ++say; g = o.govde; ke = o.alt; } if (say != 1 || sahne.kip() != SecimKip::Kenar) { kg.govde = 0; kg.hedefImza = 0; return; } const u64 imza = ((u64)g << 32) | ke; if (imza != kg.hedefImza) { kg.hedefImza = imza; kg.govde = 0; // 2026-08-09 teşhis (Fikri: "kenardan tutup sürüklemeler çalışmıyor"): gizmo şartları // sağlanmazsa aşağıda SESSİZCE return ediyordu — kullanıcı neden belirmediğini göremiyordu. // Artık her ret noktası toast ile açıklanıyor (yalnız yeni seçimde bir kez, spam yok). Dugum* d = sahne.bul(g); if (!d || !d->brep) { ui::toastGoster("Kenar gizmosu: gövde/brep bulunamadı", ui::ToastTip::Uyari); return; } geometri::KenarBilgi kb; if (!d->brep->kenarBilgi(ke, kb) || !kb.dogruMu) { ui::toastGoster("Kenar gizmosu: kenar bilgisi çözülemedi (geçersiz topoloji)", ui::ToastTip::Uyari); return; } kg.adaySay = 0; kg.adaySec = 0; for (u32 ky : d->brep->kenarKomsuYuzler(ke)) { geometri::YuzBilgi yb; if (kg.adaySay < 2 && d->brep->yuzBilgi(ky, yb, /*alanHesapla=*/false) && yb.duzlemMi) { const Vec3d nd = d->donusum.yonelim.dondur(yb.normal); kg.yuzAday[kg.adaySay] = ky; kg.okAday[kg.adaySay] = Vec3f{(f32)nd.x, (f32)nd.y, (f32)nd.z}.birim(); // KG4: düzlem-içi dışarı yön = normal × kenar, yüz merkezinden UZAĞA yönlendirilir. Vec3d t = yb.normal.capraz(kb.yon); if (t.uzunlukKare() > 1e-12) { t = t.birim(); const Vec3d ic{yb.merkez.x - kb.orta.x, yb.merkez.y - kb.orta.y, yb.merkez.z - kb.orta.z}; if (t.nokta(ic) > 0.0) t = {-t.x, -t.y, -t.z}; } const Vec3d td = d->donusum.yonelim.dondur(t); kg.uzatAdayD[kg.adaySay] = Vec3f{(f32)td.x, (f32)td.y, (f32)td.z}; // KG6: yüz konturu (dünya) — sürükleme önizlemesi için bir kez örneklenir. kg.adayKontur[kg.adaySay].clear(); for (const auto& lp : d->brep->yuzKonturlari(ky)) { std::vector wl; wl.reserve(lp.size()); for (const auto& p : lp) { const Vec3d wp = d->donusum.noktaUygula(p); wl.push_back({(f32)wp.x, (f32)wp.y, (f32)wp.z}); } kg.adayKontur[kg.adaySay].push_back(std::move(wl)); } ++kg.adaySay; } } if (!kg.adaySay) { // En sık görülen "sessiz" durum: kenarın hiç DÜZ komşu yüzü yok (iki eğri/silindirik // yüz arası kenar, fillet-fillet kenarı, vb.) — Kenardan Eğ/Uzat önkoşulu sağlanmıyor. ui::toastGoster("Bu kenarda düz komşu yüz yok — Kenardan Eğ/Uzat çalışmaz", ui::ToastTip::Uyari); return; } kg.govde = g; kg.kenar = ke; kg.yuz = kg.yuzAday[0]; kg.okYonD = kg.okAday[0]; // varsayılan = eski davranış (ilk düz) const Vec3d om = d->donusum.noktaUygula(kb.orta); kg.merkezD = {(f32)om.x, (f32)om.y, (f32)om.z}; const Vec3d ob = d->donusum.noktaUygula(kb.bas), os = d->donusum.noktaUygula(kb.son); kg.basD = {(f32)ob.x, (f32)ob.y, (f32)ob.z}; kg.sonD = {(f32)os.x, (f32)os.y, (f32)os.z}; const Vec3d yd = d->donusum.yonelim.dondur(kb.yon); kg.eksenD = Vec3f{(f32)yd.x, (f32)yd.y, (f32)yd.z}.birim(); } if (!kg.govde) 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; }; // Ekran-yaklaşık sabit boy: kamera uzaklığına oranlı dünya yarıçapı (yuz_tasi hissi). const f32 R = std::max(0.02f, kamera.mesafe() * 0.055f) * dogrudanAyar().tutamacBoy; // Yay taban çerçevesi: eksene dik iki yön (biri ok yönü — yay eğme düzleminde durur). const Vec3f B0 = kg.okYonD; const Vec3f B1 = kg.eksenD.capraz(B0).birim(); const ImU32 yayRenk = kg.hover == 0 ? IM_COL32(255, 210, 90, 255) : IM_COL32(235, 180, 60, 205); const ImU32 okRenk = kg.hover == 1 ? IM_COL32(150, 235, 255, 255) : IM_COL32(120, 220, 255, 205); const f32 fx = io.MousePos.x, fy = io.MousePos.y; f32 yayD2 = 1e18f, okD2 = 1e18f; const auto segMesafe2 = [&](ImVec2 a, ImVec2 b) -> f32 { const f32 abx = b.x - a.x, aby = b.y - a.y; const f32 L2 = abx * abx + aby * aby; f32 t = L2 > 1e-6f ? ((fx - a.x) * abx + (fy - a.y) * aby) / L2 : 0.0f; t = t < 0 ? 0 : (t > 1 ? 1 : t); const f32 dx = fx - (a.x + abx * t), dy = fy - (a.y + aby * t); return dx * dx + dy * dy; }; // ---- YAY (⟀ Kenardan Eğ): ±105° kenar ekseni etrafında, 24 segment ---- { ImVec2 onceki{}; bool oncekiVar = false; for (int i = 0; i <= 24; ++i) { const f32 a = (-105.0f + 210.0f * (f32)i / 24.0f) * 0.01745329f; const Vec3f p = kg.merkezD + (B0 * std::cos(a) + B1 * std::sin(a)) * R; ImVec2 s; if (!proj(p, s)) { oncekiVar = false; continue; } if (oncekiVar) { dl->AddLine(onceki, s, yayRenk, kg.hover == 0 ? 3.2f : 2.2f); if (vpHover) yayD2 = std::min(yayD2, segMesafe2(onceki, s)); } onceki = s; oncekiVar = true; } } // ---- OK (⇱ Kenardan Uzat): düzlemde kipte yüz DÜZLEM-İÇİ dışarı yön (kenar uzar, // karşı kenar sabit) — eski kipte yüz normali (komşu yüz komple itilir) ---- const bool uzatDz = dogrudanAyar().kenarUzatDuzlemde; const Vec3f okCiz = uzatDz && kg.uzatAdayD[kg.adaySec].uzunlukKare() > 1e-8f ? kg.uzatAdayD[kg.adaySec] : kg.okYonD; { ImVec2 s0, s1; if (proj(kg.merkezD, s0) && proj(kg.merkezD + okCiz * (R * 1.35f), s1)) { dl->AddLine(s0, s1, okRenk, kg.hover == 1 ? 3.4f : 2.4f); f32 dx = s1.x - s0.x, dy = s1.y - s0.y; const f32 L = std::sqrt(dx * dx + dy * dy); if (L > 1.0f) { dx /= L; dy /= L; const f32 u = 9.0f, w2 = 3.5f; dl->AddTriangleFilled(s1, ImVec2(s1.x - dx * u - dy * w2, s1.y - dy * u + dx * w2), ImVec2(s1.x - dx * u + dy * w2, s1.y - dy * u - dx * w2), okRenk); } if (vpHover) okD2 = segMesafe2(s0, s1); } } // ---- KG3: HAYALET OK (⇄ diğer komşu yüz) — tıklayınca eğilecek/itilecek yüz takas ---- f32 hayaletD2 = 1e18f; if (kg.adaySay == 2 && !kg.surukluyor) { const Vec3f hy = uzatDz && kg.uzatAdayD[1 - kg.adaySec].uzunlukKare() > 1e-8f ? kg.uzatAdayD[1 - kg.adaySec] : kg.okAday[1 - kg.adaySec]; const ImU32 hRenk = kg.hover == 2 ? IM_COL32(200, 255, 170, 235) : IM_COL32(170, 200, 170, 95); ImVec2 s0, s1; if (proj(kg.merkezD, s0) && proj(kg.merkezD + hy * (R * 0.95f), s1)) { dl->AddLine(s0, s1, hRenk, kg.hover == 2 ? 3.0f : 1.8f); f32 dx = s1.x - s0.x, dy = s1.y - s0.y; const f32 L = std::sqrt(dx * dx + dy * dy); if (L > 1.0f) { dx /= L; dy /= L; const f32 u = 7.0f, w2 = 2.8f; dl->AddTriangleFilled(s1, ImVec2(s1.x - dx * u - dy * w2, s1.y - dy * u + dx * w2), ImVec2(s1.x - dx * u + dy * w2, s1.y - dy * u - dx * w2), hRenk); } if (vpHover) hayaletD2 = segMesafe2(s0, s1); if (kg.hover == 2) dl->AddText({s1.x + 8, s1.y - 8}, IM_COL32(210, 255, 190, 255), "⇄ diğer yüz"); } } // ---- Hover kararı (dokunma payı 10px) ---- const f32 tol2 = 100.0f; if (!kg.surukluyor && vpHover && (yayD2 < tol2 || okD2 < tol2 || hayaletD2 < tol2)) { kg.hover = (hayaletD2 < yayD2 && hayaletD2 < okD2) ? 2 : (okD2 < yayD2 ? 1 : 0); ustunde = true; } // ---- KG3: hayalet oka tık → hedef yüz takası (sürükleme başlatmaz) ---- if (kg.hover == 2 && !kg.surukluyor && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { kg.adaySec = 1 - kg.adaySec; kg.yuz = kg.yuzAday[kg.adaySec]; kg.okYonD = kg.okAday[kg.adaySec]; return; // yay/ok yeni yüzle SONRAKİ kare çizilir (tek kare gecikme kabul) } // ---- KG2: sürükleme — yay: yatay delta → ° (Shift 5° snap) · ok: ekran-izdüşüm → mm ---- if (kg.hover >= 0 && kg.hover != 2 && !kg.surukluyor && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { kg.surukluyor = true; kg.tutamac = kg.hover; kg.deger = 0; kg.tipBuf[0] = 0; } if (kg.surukluyor) { ustunde = true; if (kg.tutamac == 0) { kg.deger += io.MouseDelta.x * 0.4f; // yuz_tasi yay kalıbı if (io.KeyShift) kg.deger = std::round(kg.deger / 5.0f) * 5.0f; kg.deger = std::clamp(kg.deger, -80.0f, 80.0f); } else { // mm ölçeği: 1mm'lik ok yönünün ekran izdüşüm uzunluğu (kamera uzaklığına uyumlu). ImVec2 s0, s1; if (proj(kg.merkezD, s0) && proj(kg.merkezD + okCiz, s1)) { const f32 dx = s1.x - s0.x, dy = s1.y - s0.y; const f32 pxMm = std::sqrt(dx * dx + dy * dy); if (pxMm > 1e-3f) kg.deger += (io.MouseDelta.x * dx + io.MouseDelta.y * dy) / (pxMm * pxMm); if (io.KeyShift) kg.deger = std::round(kg.deger); } } // Sürüklerken sayı yaz → hassas değer (yuz_tasi sayiGiris kalıbı; Backspace siler). for (int i = 0; i < io.InputQueueCharacters.Size; ++i) { const ImWchar c = io.InputQueueCharacters[i]; const size_t l = std::strlen(kg.tipBuf); if (((c >= '0' && c <= '9') || c == '.' || (c == '-' && l == 0)) && l < sizeof(kg.tipBuf) - 1) { kg.tipBuf[l] = (char)c; kg.tipBuf[l + 1] = 0; } } if (ImGui::IsKeyPressed(ImGuiKey_Backspace)) { const size_t l = std::strlen(kg.tipBuf); if (l) kg.tipBuf[l - 1] = 0; } const bool tipVar = kg.tipBuf[0] && !(kg.tipBuf[0] == '-' && !kg.tipBuf[1]); if (tipVar) kg.deger = (f32)std::atof(kg.tipBuf); // KG5/KG6: HAYALET ÖNİZLEME (ok sürüklerken) — işlem SONRASI görünüm: iki komşu yüzün // konturu, kenara değen noktaları ötelenerek çizilir (uzayan yüz + eğilen duvar birlikte). // Geometri hesabı YOK (her karede DraftAngle pahalı — bilinçli); saf çizim. if (kg.tutamac == 1 && std::abs(kg.deger) > 0.01f) { const Vec3f dv = okCiz * kg.deger; // Nokta çekilen kenarın üstünde mi? (segment mesafesi, uzunluğa oranlı tolerans) const Vec3f ab = kg.sonD - kg.basD; const f32 abL2 = ab.uzunlukKare(); const f32 eps = std::max(1e-3f, std::sqrt(abL2) * 1e-3f); const auto kenardaMi = [&](const Vec3f& p) -> bool { const Vec3f ap = p - kg.basD; f32 u = abL2 > 1e-9f ? ap.nokta(ab) / abL2 : 0.0f; u = u < 0 ? 0 : (u > 1 ? 1 : u); return (ap - ab * u).uzunlukKare() < eps * eps; }; const ImU32 hatRenk = IM_COL32(150, 235, 255, 150); for (int a = 0; a < kg.adaySay; ++a) for (const auto& lp : kg.adayKontur[a]) { ImVec2 once{}; bool onceVar = false, ilkVar = false; ImVec2 ilk{}; for (const Vec3f& p : lp) { ImVec2 s; if (!proj(kenardaMi(p) ? p + dv : p, s)) { onceVar = false; continue; } if (onceVar) dl->AddLine(once, s, hatRenk, 1.6f); if (!ilkVar) { ilk = s; ilkVar = true; } once = s; onceVar = true; } if (ilkVar && onceVar) dl->AddLine(once, ilk, hatRenk, 1.6f); // konturu kapat } // Uzayan şerit + kenarın yeni yeri (vurgu). ImVec2 a0, b0, a1, b1; if (proj(kg.basD, a0) && proj(kg.sonD, b0) && proj(kg.basD + dv, a1) && proj(kg.sonD + dv, b1)) { dl->AddQuadFilled(a0, b0, b1, a1, IM_COL32(120, 220, 255, 34)); dl->AddLine(a1, b1, IM_COL32(150, 235, 255, 230), 2.6f); // kenarın yeni yeri } } char et[48]; if (kg.tutamac == 0) std::snprintf(et, sizeof et, "∠ %.1f°%s%s", kg.deger, tipVar ? " ⌨" : "", io.KeyShift ? " ⇥" : ""); else std::snprintf(et, sizeof et, "⇱ %.2f mm%s%s", kg.deger, tipVar ? " ⌨" : "", io.KeyShift ? " ⇥" : ""); dl->AddText({io.MousePos.x + 14, io.MousePos.y - 18}, IM_COL32(255, 235, 170, 255), et); // ---- BIRAKIŞ: feature uygula (canlı geometri önizleme bilinçli yok — pahalı) ---- if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { kg.surukluyor = false; const bool yay = kg.tutamac == 0; const f32 v = kg.deger; kg.tutamac = -1; if ((yay && std::abs(v) >= 0.5f) || (!yay && std::abs(v) >= 0.05f)) { // KG4: düzlemde kip — kenar_tasi feature: SADECE çekilen kenar hareket eder (seçili // yüz düzleminde uzar, öteki komşu uzak menteşesinden eğilir). Eski kip: yuz_itcek. const bool duzlemde = !yay && uzatDz; const u32 fk = partD.featureBaslat( yay ? "kenar_eg" : (duzlemde ? "kenar_tasi" : "yuz_itcek"), VARLIK_YOK, kg.govde); if (!fk) { ui::toastGoster("İşlem başlatılamadı (katı gövde gerek)", ui::ToastTip::Hata); return; } std::vector> params; if (yay) params = {{"kenar0", (f64)kg.kenar}, {"yuz0", (f64)kg.yuz}, {"aci", (f64)v}}; else if (duzlemde) params = {{"kenar0", (f64)kg.kenar}, {"yuz0", (f64)kg.yuz}, {"mesafe", (f64)v}}; else params = {{"yuzSayi", 1.0}, {"yuz0", (f64)kg.yuz}, {"mesafe", (f64)v}}; // KG5: işlem bitince AYNI kenarı yeni gövdede KONUMDAN bulup yeniden seç (art arda // çekme akışı — ordinal usd sonrası kayabilir, IsSame değil konum eşlenir) + gizmo // hedef önbelleği tazelenir (bayat ankraj kullanılmasın). Eğ'de kenar yerinde kalır, // uzatta beklenen yeni yeri = eski + ok yönü × mesafe. const Vec3f hedefD = yay ? kg.merkezD : kg.merkezD + okCiz * v; const Vec3f eksenW = kg.eksenD; KenarGizmoDurum& kgRef = kg; Sahne& sahneRef = sahne; const auto kenarSec = [&sahneRef, &partD, &kgRef, hedefD, eksenW, v](u32 f) { kgRef.hedefImza = 0; // başarı/başarısızlıkta yeniden çözülsün if (!dogrudanAyar().secimiKoru || !f) return; const u32 sd = partD.featureSonucDugum(f); Dugum* d2 = sd ? sahneRef.bul(sd) : nullptr; if (!d2 || !d2->brep) return; const auto oz = geometri::topolojiOzeti(*d2->brep); u32 enIyi = 0; f32 enD2 = 1e18f; for (u32 e2 = 1; e2 <= (u32)oz.kenarSayi; ++e2) { geometri::KenarBilgi kb2; if (!d2->brep->kenarBilgi(e2, kb2) || !kb2.dogruMu) continue; const Vec3d ow = d2->donusum.noktaUygula(kb2.orta); Vec3f fark{(f32)ow.x - hedefD.x, (f32)ow.y - hedefD.y, (f32)ow.z - hedefD.z}; fark = fark - eksenW * fark.nokta(eksenW); // eksen bileşeni (orta kayması) hariç if (fark.uzunlukKare() < enD2) { enD2 = fark.uzunlukKare(); enIyi = e2; } } const f32 tol = std::max(0.5f, std::abs(v) * 0.02f); if (enIyi && enD2 < tol * tol) { sahneRef.secimTemizle(); sahneRef.sec({sd, enIyi}, /*coklu*/ false); } }; if (dogrudanAyar().asyncApply && partD.featureAsyncUygula(fk, params, [&cekirdek, &partD, fk, yay, kenarSec](bool ok) { if (ok) { dmFeatureEkleUndo(cekirdek, partD, fk); kenarSec(fk); } else kenarSec(0); ui::toastGoster(ok ? (yay ? "Kenardan eğildi" : "Kenardan uzatıldı") : "İşlem başarısız (nedeni ürün ağacında)", ok ? ui::ToastTip::Basari : ui::ToastTip::Hata); })) { ui::toastGoster(yay ? "Eğiliyor…" : "Uzatılıyor…", ui::ToastTip::Bilgi); } else { partD.featureParamToplu(fk, params); const bool ok = partD.featureOnayla(fk); if (ok) dmFeatureEkleUndo(cekirdek, partD, fk); kenarSec(ok ? fk : 0); } dmTutamacOlayiYay(cekirdek, "Değiştir", yay ? "Kenardan Eğ" : "Kenardan Uzat"); } } } } } // namespace fiko