#include "pmi_ciz.hpp" #include #include "imgui.h" #include "ui_ortak.hpp" #include "uygulama_baglam.hpp" #include #include "fiko/sahne/pmi_nesne.hpp" #include "fiko/sahne/pmi_sistemi.hpp" #include "fiko/sahne/sahne_islem.hpp" #include "fiko/cekirdek/cekirdek.hpp" #include "fiko/render/referans_olcek.hpp" namespace fiko { f32& pmiCerceveDolguPx() { static f32 v = 6.0f; return v; } f32& pmiOkBoyuPx() { static f32 v = 10.0f; return v; } f32& pmiLodEsigiPx() { static f32 v = 14.0f; return v; } bool& pmiOrtmeKontrolu() { static bool v = false; return v; } namespace { ImU32 renk32(const Vec3f& r, u8 alfa) { return IM_COL32((int)(r.x * 255), (int)(r.y * 255), (int)(r.z * 255), alfa); } } // namespace // Öğe sağ-tık menüsü: karakteristik/tolerans/koşul/datum/lider/custom alan/sil. // Her içerik değişikliği ++surum (FCF metin önbelleği tazelenir). static void pmiOgeMenusu(UygulamaBaglam& b, PmiUIDurum& durum) { if (!ImGui::BeginPopup("##pmiMenu")) return; pmi::PmiOge* o = durum.menuPmi ? b.pmiSis.defter().bul(durum.menuPmi) : nullptr; if (!o) { ImGui::EndPopup(); return; } if (o->tip == pmi::PmiTip::Gdt) { ImGui::TextDisabled("GD&T"); ImGui::Separator(); if (ImGui::BeginMenu("Karakteristik")) { for (int i = 0; i < 14; ++i) { const auto k = (pmi::GdtKarakteristik)i; char et[64]; std::snprintf(et, sizeof et, "%s %s", pmi::karakterGlif(k), pmi::karakterAdi(k)); if (ImGui::MenuItem(et, nullptr, o->gdt[0].karakter == k)) { o->gdt[0].karakter = k; ++o->surum; } } ImGui::EndMenu(); } f64 tol = o->gdt[0].tolerans; ImGui::SetNextItemWidth(120); if (ImGui::InputDouble("Tolerans", &tol, 0.0, 0.0, "%.3f")) { o->gdt[0].tolerans = std::max(0.0, tol); ++o->surum; } if (ImGui::MenuItem("Ø bölge", nullptr, o->gdt[0].capsal)) { o->gdt[0].capsal = !o->gdt[0].capsal; ++o->surum; } if (ImGui::BeginMenu("Malzeme koşulu")) { const std::pair ks[] = { {"Yok", pmi::MalzemeKosulu::Yok}, {"Ⓜ MMC", pmi::MalzemeKosulu::MMC}, {"Ⓛ LMC", pmi::MalzemeKosulu::LMC}, {"Ⓢ RFS", pmi::MalzemeKosulu::RFS}}; for (const auto& [ad, m] : ks) if (ImGui::MenuItem(ad, nullptr, o->gdt[0].kosul == m)) { o->gdt[0].kosul = m; ++o->surum; } ImGui::EndMenu(); } if (ImGui::BeginMenu("Datum referansları")) { // Defterdeki datum harfleri aday; A|B|C slotlarına sırayla dolar/boşalır. for (const pmi::PmiOge& da : b.pmiSis.defter().hepsi()) { if (da.tip != pmi::PmiTip::Datum) continue; int slot = -1; for (int i = 0; i < 3; ++i) if (o->gdt[0].datum[i] == da.datumHarf) slot = i; char et[24]; std::snprintf(et, sizeof et, "%c%s", da.datumHarf, slot >= 0 ? pmi::kosulGlif(o->gdt[0].datumKosul[slot]) : ""); if (ImGui::BeginMenu(et)) { // harf alt menüsü: üyelik + datum koşulu (ISO/ASME) if (ImGui::MenuItem("Referans", nullptr, slot >= 0)) { if (slot >= 0) { // çıkar + sağa kaydır for (int i = slot; i < 2; ++i) { o->gdt[0].datum[i] = o->gdt[0].datum[i + 1]; o->gdt[0].datumKosul[i] = o->gdt[0].datumKosul[i + 1]; } o->gdt[0].datum[2] = 0; } else { for (int i = 0; i < 3; ++i) if (!o->gdt[0].datum[i]) { o->gdt[0].datum[i] = da.datumHarf; break; } } ++o->surum; } if (slot >= 0) { // koşul yalnız referans üyesiyken anlamlı ImGui::Separator(); const std::pair dks[] = { {"Koşulsuz", pmi::MalzemeKosulu::Yok}, {"Ⓜ MMB", pmi::MalzemeKosulu::MMC}, {"Ⓛ LMB", pmi::MalzemeKosulu::LMC}, {"Ⓢ RMB", pmi::MalzemeKosulu::RFS}}; for (const auto& [kad, km] : dks) if (ImGui::MenuItem(kad, nullptr, o->gdt[0].datumKosul[slot] == km)) { o->gdt[0].datumKosul[slot] = km; ++o->surum; } } ImGui::EndMenu(); } } ImGui::EndMenu(); } if (ImGui::MenuItem("İkinci satır (kompozit)", nullptr, o->gdtSatirSayi > 1)) { o->gdtSatirSayi = o->gdtSatirSayi > 1 ? 1 : 2; ++o->surum; } } if (o->tip == pmi::PmiTip::Not || o->tip == pmi::PmiTip::Bayrak) { char tampon[256]; std::snprintf(tampon, sizeof tampon, "%s", o->metin.c_str()); ImGui::SetNextItemWidth(220); if (ImGui::InputText("Metin", tampon, sizeof tampon)) { o->metin = tampon; ++o->surum; } } ImGui::Separator(); if (ImGui::BeginMenu("Yerleşim")) { const std::pair ys[] = { {"Ekrana paralel", pmi::Yerlesim::Billboard}, {"Düzlemde", pmi::Yerlesim::Duzlem}, {"Düzlemde (zoomda sabit)", pmi::Yerlesim::DuzlemSabitBoy}}; for (const auto& [ad, y] : ys) if (ImGui::MenuItem(ad, nullptr, o->yerlesim == y)) { o->yerlesim = y; ++o->surum; } ImGui::EndMenu(); } if (ImGui::MenuItem("Lider çizgisi", nullptr, o->liderVar)) { o->liderVar = !o->liderVar; ++o->surum; } if (ImGui::MenuItem("Özel alan ekle")) { o->ozelAlanlar.push_back({"Alan " + std::to_string(o->ozelAlanlar.size() + 1), ""}); ++o->surum; // özellik panelinde düzenlenir } ImGui::Separator(); if (ImGui::MenuItem("Sil")) { u32 dugumKimlik = 0; for (Dugum& dg : b.sahne.dugumler()) if (dg.tip == DugumTip::Pmi) if (const NesneKok* nk = b.sahne.dunya().bul(dg.kimlik)) if (const auto* pn = dynamic_cast(nk->get())) if (pn->pmiKimlik() == durum.menuPmi) { dugumKimlik = dg.kimlik; break; } b.cekirdek.uygula( std::make_unique(b.sahne, &b.pmiSis, durum.menuPmi, dugumKimlik)); durum.metinOnbellek.erase(durum.menuPmi); if (durum.seciliPmi == durum.menuPmi) durum.seciliPmi = 0; durum.menuOncesi.reset(); // silinen öğe için düzen kaydı üretme durum.menuPmi = 0; } ImGui::EndPopup(); } void pmiOverlayCiz(UygulamaBaglam& b, PmiUIDurum& durum, const PmiCizimBaglam& cb) { if (!cb.ciz || !cb.ekrana) return; ImDrawList* ciz = cb.ciz; ImGuiIO& io = *cb.io; const f32 dolgu = pmiCerceveDolguPx(); const ImVec2 fare = io.MousePos; durum.fareUstunde = false; u32 vurulan = 0; // imleç altındaki öğe (son çizilen üstte kazanır) for (pmi::PmiOge& o : b.pmiSis.defter().hepsi()) { if (!o.goster || !o.gecerli) continue; ImVec2 capaPx; if (!cb.ekrana(o.capa, capaPx)) continue; // kamera arkasında → atla // FCF metni: sürüm-anahtarlı önbellek (kare başı ogeMetni/tahsis YOK). auto& kayit = durum.metinOnbellek[o.kimlik]; if (kayit.first != o.surum + 1) { // 0 hiç-üretilmedi işareti için +1 kaydırma kayit.first = o.surum + 1; kayit.second = pmi::ogeMetni(o); } const std::string& metin = kayit.second; const bool secili = durum.seciliPmi == o.kimlik; const ImVec2 mb = ImGui::CalcTextSize(metin.c_str()); // Örtme (opsiyonel, varsayılan KAPALI): çapadan kamera gözüne BVH ışını — // arada gövde varsa çerçeve soluklaşır. Önbellekli: kamera kabaca (0.5 birim // kuantalı) + öğe sürümü imzası değişmedikçe ışın atılmaz (sıcak yol temiz). u8 etkinAlfa = o.stil.alfa; if (pmiOrtmeKontrolu()) { const auto q = [](f32 v) { return (u64)(i64)std::floor(v * 2.0f); }; const u64 kimza = (q(cb.kameraGoz.x) * 73856093u) ^ (q(cb.kameraGoz.y) * 19349663u) ^ (q(cb.kameraGoz.z) * 83492791u) ^ ((u64)o.surum << 32); auto& ok = durum.ortmeOnbellek[o.kimlik]; if (ok.first != kimza) { ok.first = kimza; ok.second = false; const Vec3f fark{cb.kameraGoz.x - o.capa.x, cb.kameraGoz.y - o.capa.y, cb.kameraGoz.z - o.capa.z}; const f32 uz = std::sqrt(fark.x * fark.x + fark.y * fark.y + fark.z * fark.z); if (uz > 1e-4f) { const Vec3f yon{fark.x / uz, fark.y / uz, fark.z / uz}; const Vec3f orij{o.capa.x + yon.x * 0.01f, o.capa.y + yon.y * 0.01f, o.capa.z + yon.z * 0.01f}; Sahne::IsinVurus vur; if (b.sahne.isinVur(orij, yon, vur) && vur.t < uz - 0.02f) ok.second = true; } } if (ok.second) etkinAlfa = (u8)(o.stil.alfa / 3); // örtülü → soluk } const ImU32 ana = renk32(o.stil.renk, etkinAlfa); const ImU32 cizgiRenk = secili ? IM_COL32(70, 210, 235, 255) : ana; if (mb.x < pmiLodEsigiPx() && mb.y < pmiLodEsigiPx()) { // uzak/boş → nokta işareti ciz->AddCircleFilled(capaPx, 2.5f, ana); continue; } ImVec2 merkez; ImVec2 a, c; f32 metinAci = 0.0f; // düzlem kipinde kutunun ekran eğimi (billboard: 0) if (o.yerlesim == pmi::Yerlesim::Billboard) { // Billboard: çerçeve EKRAN uzayında (ofsetYerel piksel; doğal zoom-sabit). merkez = {capaPx.x + o.ofsetYerel.x, capaPx.y - o.ofsetYerel.y}; a = {merkez.x - mb.x * 0.5f - dolgu, merkez.y - mb.y * 0.5f - dolgu}; c = {merkez.x + mb.x * 0.5f + dolgu, merkez.y + mb.y * 0.5f + dolgu}; } else { // Düzlemde: 4 köşe DÜNYADA (perspektif kısaltma doğal), metin billboard okunur // kalır (NX kipi; ImDrawList metin döndüremez). Kutu boyutu mm: yazı boyu // kademesiyle px→mm; DuzlemSabitBoy'da referansGorselOlcek çarpanı (pivot=çapa). const f64 mmOran = o.stil.yaziBoyu / (f64)ImGui::GetFontSize(); const f64 olcek = o.yerlesim == pmi::Yerlesim::DuzlemSabitBoy ? (f64)referansGorselOlcek(cb.kameraMesafe, 1.0f, o.gorselOran) : 1.0; Vec3f kose[4], merkez3; pmi::duzlemKoseleri(o, (mb.x + 2 * dolgu) * mmOran, (mb.y + 2 * dolgu) * mmOran, olcek, kose, merkez3); ImVec2 k2[4]; bool tumGorunur = true; for (int i = 0; i < 4; ++i) if (!cb.ekrana(kose[i], k2[i])) { tumGorunur = false; break; } ImVec2 m2; if (!tumGorunur || !cb.ekrana(merkez3, m2)) { ciz->AddCircleFilled(capaPx, 2.5f, ana); continue; } ciz->AddQuadFilled(k2[0], k2[1], k2[2], k2[3], IM_COL32(22, 24, 28, 215)); ciz->AddQuad(k2[0], k2[1], k2[2], k2[3], cizgiRenk, o.stil.cizgiKalinlik); merkez = m2; // Metin kutunun alt kenarı doğrultusunda (CL-2): baş aşağı kalmasın diye ±90° // aralığına çevrilir (pafta_olcu ISO 129 kalıbı); donukMetinCiz saat-tersi pozitif. metinAci = std::atan2(k2[1].y - k2[0].y, k2[1].x - k2[0].x); if (metinAci > 1.5707963f) metinAci -= 3.1415926f; else if (metinAci < -1.5707963f) metinAci += 3.1415926f; metinAci = -metinAci; // Hit-test/lider için eksen-hizalı kapsayan kutu (yaklaşık — yeterli). a = {std::min(std::min(k2[0].x, k2[1].x), std::min(k2[2].x, k2[3].x)), std::min(std::min(k2[0].y, k2[1].y), std::min(k2[2].y, k2[3].y))}; c = {std::max(std::max(k2[0].x, k2[1].x), std::max(k2[2].x, k2[3].x)), std::max(std::max(k2[0].y, k2[1].y), std::max(k2[2].y, k2[3].y))}; } // Lider: çerçevenin çapaya bakan kenar ortasından çapaya + ok ucu. if (o.liderVar) { const ImVec2 cik{std::clamp(capaPx.x, a.x, c.x) == a.x ? a.x : std::clamp(capaPx.x, a.x, c.x) == c.x ? c.x : merkez.x, std::clamp(capaPx.y, a.y, c.y) == a.y ? a.y : std::clamp(capaPx.y, a.y, c.y) == c.y ? c.y : merkez.y}; ciz->AddLine(cik, capaPx, cizgiRenk, o.stil.cizgiKalinlik * 0.8f); const f32 dx = capaPx.x - cik.x, dy = capaPx.y - cik.y; const f32 uz = std::sqrt(dx * dx + dy * dy); if (uz > 1.0f) { const f32 ux = dx / uz, uy = dy / uz; const f32 ok = pmiOkBoyuPx(); ciz->AddTriangleFilled( capaPx, {capaPx.x - ux * ok + uy * ok * 0.35f, capaPx.y - uy * ok - ux * ok * 0.35f}, {capaPx.x - ux * ok - uy * ok * 0.35f, capaPx.y - uy * ok + ux * ok * 0.35f}, cizgiRenk); } } // Çerçeve: billboard'da eksen-hizalı kutu (düzlem kipi kutusunu yukarıda çizdi); // datum tipinde altta bağ üçgeni (ISO datum sembolü). if (o.yerlesim == pmi::Yerlesim::Billboard) { ciz->AddRectFilled(a, c, IM_COL32(22, 24, 28, 215), 2.0f); ciz->AddRect(a, c, cizgiRenk, 2.0f, 0, o.stil.cizgiKalinlik); } if (o.tip == pmi::PmiTip::Datum || o.tip == pmi::PmiTip::DatumHedef) { const f32 ort = (a.x + c.x) * 0.5f; ciz->AddTriangleFilled({ort - 5, c.y}, {ort + 5, c.y}, {ort, c.y + 8}, cizgiRenk); } // Çok satırlı metin: satırlar merkez etrafında; düzlem kipinde kutu eğimiyle // DÖNDÜRÜLÜR (donukMetinCiz — CL-2). Dikey dizilim döndürülmüş "yukarı" boyunca. int satirSayi = 1; for (char ch : metin) if (ch == '\n') ++satirSayi; const f32 satirYuk = ImGui::GetTextLineHeight(); const f32 dca = std::cos(-metinAci), dsa = std::sin(-metinAci); f32 sIdx = 0; size_t bas = 0; while (bas <= metin.size()) { const size_t son = metin.find('\n', bas); const std::string satir = metin.substr(bas, son == std::string::npos ? std::string::npos : son - bas); const f32 dy = (sIdx - (f32)(satirSayi - 1) * 0.5f) * satirYuk; donukMetinCiz(ciz, {merkez.x - dsa * dy, merkez.y + dca * dy}, metinAci, ana, satir.c_str()); sIdx += 1.0f; if (son == std::string::npos) break; bas = son + 1; } // Hit-test: imleç kutu içinde mi (etkileşim aşağıda — son çizilen üstte kazanır). if (cb.vpHover && fare.x >= a.x && fare.x <= c.x && fare.y >= a.y && fare.y <= c.y) { durum.fareUstunde = true; vurulan = o.kimlik; } } // ---- Etkileşim: sol tık seç+sürükle · sağ tık öğe menüsü (DM, toolbar yok) ---- if (cb.vpHover && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { durum.seciliPmi = vurulan; // boşa tık seçimi bırakır (sahne pick'i ayrıca koşar) durum.surukle = vurulan; if (vurulan) if (const pmi::PmiOge* o = b.pmiSis.defter().bul(vurulan)) durum.surukleOncesi = std::make_shared(*o); // undo: önce durumu } if (durum.surukle) { if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) { if (pmi::PmiOge* o = b.pmiSis.defter().bul(durum.surukle)) { o->ofsetYerel.x += io.MouseDelta.x; o->ofsetYerel.y -= io.MouseDelta.y; // ekran +y aşağı, ofset +y yukarı } } else { if (durum.surukleOncesi) if (const pmi::PmiOge* o = b.pmiSis.defter().bul(durum.surukle)) b.cekirdek.uygula(std::make_unique(&b.pmiSis, durum.surukle, std::move(*durum.surukleOncesi), *o, "PMI Taşı")); durum.surukleOncesi.reset(); durum.surukle = 0; } } if (cb.vpHover && vurulan && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { durum.menuPmi = vurulan; durum.seciliPmi = vurulan; if (const pmi::PmiOge* o = b.pmiSis.defter().bul(vurulan)) durum.menuOncesi = std::make_shared(*o); // undo: menü öncesi ImGui::OpenPopup("##pmiMenu"); } pmiOgeMenusu(b, durum); // Menü kapandı: içerik değiştiyse (surum farkı) TEK undo adımı. Sil zaten kendi işlemi. if (durum.menuOncesi && !ImGui::IsPopupOpen("##pmiMenu")) { if (const pmi::PmiOge* o = b.pmiSis.defter().bul(durum.menuPmi); o && o->surum != durum.menuOncesi->surum) b.cekirdek.uygula(std::make_unique(&b.pmiSis, durum.menuPmi, std::move(*durum.menuOncesi), *o, "PMI Düzenle")); durum.menuOncesi.reset(); } } } // namespace fiko