#include "fiko/parca/part_design_sistemi.hpp" #include "fiko/sketch/eskiz_segment.hpp" // merkezi eskiz→segment (tek doğruluk kaynağı) #include #include #include #include #include #include #include #include "fiko/cekirdek/cekirdek.hpp" #include "fiko/core/gunluk.hpp" #include "fiko/geometri/brep.hpp" #include "fiko/parca/feature_registry.hpp" #include "fiko/parametre/depo.hpp" #include "fiko/sahne/sahne.hpp" #include "fiko/sahne/sketch_nesne.hpp" namespace fiko::parca { namespace { // Bir eskiz geometrisini 2B polyline segmentlerine cevir (yerel uv). // Cizgi: 2 nokta. Daire/Yay: parametrik ornekleme. Nokta: atlanir. using eskiz::Seg2B; // merkezi tanım (fiko/sketch/eskiz_segment.hpp) using eskiz::geoSegmentle; // Segment havuzundan kapali polyline zincirler cikar (uc-uca, tolerans icinde). // Her zincir: kapali dongu nokta listesi (son != ilk). Aciklar atilir. std::vector> zincirle(std::vector seg) { constexpr f64 tol = 1e-4; const auto yakin = [&](const Vec2d& p, const Vec2d& q) { return std::hypot(p.x - q.x, p.y - q.y) < tol; }; std::vector> konturlar; std::vector kullanildi(seg.size(), false); for (size_t i = 0; i < seg.size(); ++i) { if (kullanildi[i]) continue; std::vector zincir = {seg[i].a, seg[i].b}; kullanildi[i] = true; bool buyudu = true; while (buyudu) { buyudu = false; const Vec2d son = zincir.back(); if (yakin(son, zincir.front()) && zincir.size() >= 3) break; // kapandi for (size_t j = 0; j < seg.size(); ++j) { if (kullanildi[j]) continue; if (yakin(seg[j].a, son)) { zincir.push_back(seg[j].b); kullanildi[j] = true; buyudu = true; break; } if (yakin(seg[j].b, son)) { zincir.push_back(seg[j].a); kullanildi[j] = true; buyudu = true; break; } } } // Kapali mi? (bas ~ son) → son tekrar noktayi at, kontur olarak al. if (zincir.size() >= 4 && yakin(zincir.front(), zincir.back())) { zincir.pop_back(); konturlar.push_back(std::move(zincir)); } } return konturlar; } // Segment havuzundan EN UZUN uç-uca zincir (açık yol; Rib/Slot kılavuz eğri spine'ı). Kapalı // da olabilir. zincirle()'den farkı: kapatma zorunlu değil — açık kılavuz yolları korunur. std::vector yolZincir(const std::vector& seg) { constexpr f64 tol = 1e-4; const auto yakin = [&](const Vec2d& p, const Vec2d& q) { return std::hypot(p.x - q.x, p.y - q.y) < tol; }; std::vector enIyi; for (size_t i = 0; i < seg.size(); ++i) { std::vector kull(seg.size(), false); std::vector zincir = {seg[i].a, seg[i].b}; kull[i] = true; bool buyudu = true; // ileriye (son uç) while (buyudu) { buyudu = false; const Vec2d son = zincir.back(); for (size_t j = 0; j < seg.size(); ++j) { if (kull[j]) continue; if (yakin(seg[j].a, son)) { zincir.push_back(seg[j].b); kull[j] = true; buyudu = true; break; } if (yakin(seg[j].b, son)) { zincir.push_back(seg[j].a); kull[j] = true; buyudu = true; break; } } } buyudu = true; // geriye (bas uç) while (buyudu) { buyudu = false; const Vec2d bas = zincir.front(); for (size_t j = 0; j < seg.size(); ++j) { if (kull[j]) continue; if (yakin(seg[j].b, bas)) { zincir.insert(zincir.begin(), seg[j].a); kull[j] = true; buyudu = true; break; } if (yakin(seg[j].a, bas)) { zincir.insert(zincir.begin(), seg[j].b); kull[j] = true; buyudu = true; break; } } } if (zincir.size() > enIyi.size()) enIyi = std::move(zincir); } return enIyi; } // Stiffener (Nervür) kesiti: AÇIK dünya-uzayı polyline yolu, düzlem 'normal'i çevresinde // ±kalinlik/2 düzlem-içi ofsetleyip KAPALI kontur (şerit) yap. Her vertexte in-plane dik = // (normal × segment_yön); komşu iki segmentin dik ortalaması alınır (köşe yumuşatma). Sol // kenar ileri + sağ kenar geri → kapalı döngü (prizma tabanı). < 2 nokta → boş. std::vector acikYolKalinlastir(const std::vector& yol, Vec3d normal, f64 kalinlik) { const size_t n = yol.size(); if (n < 2 || kalinlik <= 1e-9) return {}; const Vec3d nB = normal.birim(); const f64 h = kalinlik * 0.5; const auto segDik = [&](const Vec3d& a, const Vec3d& b) -> Vec3d { const Vec3d d = b - a; if (d.uzunlukKare() < 1e-18) return {}; const Vec3d p = nB.capraz(d); // düzlem-içi dik (normal × yön) return p.uzunlukKare() > 1e-18 ? p.birim() : Vec3d{}; }; std::vector dik(n); for (size_t i = 0; i < n; ++i) { const Vec3d pa = i > 0 ? segDik(yol[i - 1], yol[i]) : Vec3d{}; const Vec3d pb = i + 1 < n ? segDik(yol[i], yol[i + 1]) : Vec3d{}; Vec3d p = pa + pb; if (p.uzunlukKare() < 1e-18) p = pa.uzunlukKare() > 1e-18 ? pa : pb; dik[i] = p.uzunlukKare() > 1e-18 ? p.birim() : Vec3d{}; } std::vector kontur; kontur.reserve(2 * n); for (size_t i = 0; i < n; ++i) kontur.push_back(yol[i] + dik[i] * h); // sol kenar for (size_t i = n; i-- > 0;) kontur.push_back(yol[i] - dik[i] * h); // sağ kenar (geri) return kontur; } // Up To Next/Last YAKLAŞIK limiti: hedef katının 'orijin'den 'yonB' (birim) boyunca // en yakın (yakin) / en uzak (uzak) izdüşüm mesafeleri (üçgenleme düğümleri). // TODO: Kesin CATIA davranışı için OCCT BRepFeat_MakePrism (Perform UpToFace/UpToLast) // — ilk/son gerçek yüze durur; bu yaklaşım yalnız yön-boyu bounding açıklığı verir. bool hedefYonAraligi(const geometri::BrepGovde& g, Vec3d orijin, Vec3d yonB, f64& yakin, f64& uzak) { if (!g.gecerliMi() || yonB.uzunlukKare() < 1e-18) return false; const MeshVeri m = g.ucgenle({0.5f, 0.5f, 0.5f}); if (m.vertexler.empty()) return false; yakin = 1e300; uzak = -1e300; for (const auto& v : m.vertexler) { const f64 t = (Vec3d{v.konum[0], v.konum[1], v.konum[2]} - orijin).nokta(yonB); if (t < yakin) yakin = t; if (t > uzak) uzak = t; } return uzak > yakin; } } // namespace bool PartDesignSistemi::sketchKonturlari(Varlik sketch, std::vector>& konturlar, Vec3d& normal) const { DuzlemBilgi dz; return sketchKonturlari(sketch, konturlar, normal, dz); } bool PartDesignSistemi::sketchKonturlari(Varlik sketch, std::vector>& konturlar, Vec3d& normal, DuzlemBilgi& duzlem) const { konturlar.clear(); auto* sk = dynamic_cast(cekirdek_.nesne(sketch)); if (!sk) return false; const Dugum* dz = sahne_.bul(sk->duzlem()); if (!dz) return false; const eskiz::EskizVeri& ev = sk->eskiz(); // 1) Yapi-olmayan geometrilerden 2B segment havuzu. std::vector seg; for (const eskiz::Geometri& g : ev.geometriler()) { if (g.yapi) continue; geoSegmentle(ev, g, seg); } if (seg.empty()) return false; // 2) Kapali 2B konturlar → 3) duzlem donusumuyle dunyaya tasi (uv,0 → dunya). const std::vector> uv = zincirle(std::move(seg)); if (uv.empty()) return false; for (const std::vector& k : uv) { std::vector dunya; dunya.reserve(k.size()); for (const Vec2d& p : k) dunya.push_back(dz->donusum.noktaUygula({p.x, p.y, 0.0})); konturlar.push_back(std::move(dunya)); } // Profil normali = duzlem yerel +Z dunyaya donmus (cikinti yonu). normal = dz->donusum.yonelim.dondur({0, 0, 1}); // Shaft ekseni icin duzlem cercevesi (dunya-uzayi orijin + U/V eksenleri). duzlem.orijin = dz->donusum.konum; duzlem.u = dz->donusum.yonelim.dondur({1, 0, 0}); duzlem.v = dz->donusum.yonelim.dondur({0, 1, 0}); duzlem.normal = normal; return !konturlar.empty(); } bool PartDesignSistemi::sketchProfilleri(Varlik sketch, std::vector& konturlar, Vec3d& normal, DuzlemBilgi& duzlem) const { using GT = eskiz::GeoTip; using PT = geometri::ProfilEgri::Tip; konturlar.clear(); auto* sk = dynamic_cast(cekirdek_.nesne(sketch)); if (!sk) return false; const Dugum* dz = sahne_.bul(sk->duzlem()); if (!dz) return false; const eskiz::EskizVeri& ev = sk->eskiz(); normal = dz->donusum.yonelim.dondur({0, 0, 1}); duzlem.orijin = dz->donusum.konum; duzlem.u = dz->donusum.yonelim.dondur({1, 0, 0}); duzlem.v = dz->donusum.yonelim.dondur({0, 1, 0}); duzlem.normal = normal; const auto d3 = [&](f64 x, f64 y) { return dz->donusum.noktaUygula({x, y, 0.0}); }; // Kapalı tek-eğriler (Daire/tam Elips) doğrudan; açık eğriler (Cizgi/Yay) havuza. struct AcikEgri { Vec2d bas, son; geometri::ProfilEgri egri; }; std::vector acik; for (const eskiz::Geometri& g : ev.geometriler()) { if (g.yapi || g.tip == GT::Nokta) continue; if (g.tip == GT::Cizgi) { const Vec2d a{ev.noktaX(g.kimlik, 0), ev.noktaY(g.kimlik, 0)}; const Vec2d b{ev.noktaX(g.kimlik, 1), ev.noktaY(g.kimlik, 1)}; geometri::ProfilEgri e; e.tip = PT::Cizgi; e.p0 = d3(a.x, a.y); e.p1 = d3(b.x, b.y); acik.push_back({a, b, e}); continue; } if (g.tip == GT::Spline) { // Spline: uçlar a/b (zincir bu noktalara bağlanır), geçiş noktaları SIRAYLA // (uçlar dahil) dünyaya — egriKonturWire GeomAPI_Interpolate ile TAM geçer. const Vec2d a{ev.noktaX(g.kimlik, 0), ev.noktaY(g.kimlik, 0)}; const Vec2d b{ev.noktaX(g.kimlik, 1), ev.noktaY(g.kimlik, 1)}; geometri::ProfilEgri e; e.tip = PT::Spline; e.p0 = d3(a.x, a.y); e.p1 = d3(b.x, b.y); e.noktalar.reserve(g.ekNoktalar.size() + 2); e.noktalar.push_back(e.p0); for (const eskiz::Nokta& n : g.ekNoktalar) e.noktalar.push_back(d3(ev.px(n.ix), ev.px(n.iy))); e.noktalar.push_back(e.p1); acik.push_back({a, b, e}); continue; } // Daire / Yay / Elips — geoSegmentle ile TUTARLI parametreler. const f64 cx = ev.noktaX(g.kimlik, 2), cy = ev.noktaY(g.kimlik, 2); const bool elips = g.tip == GT::Elips; const f64 ra = ev.px(g.ir); const f64 rb = elips ? ev.px(g.ir2) : ra; if (ra < 1e-9 || rb < 1e-9) continue; const f64 rot = elips ? ev.px(g.iaci) : 0.0; const bool yayVar = (g.ib1 >= 0 && g.ib2 >= 0); const f64 a0 = yayVar ? ev.px(g.ib1) : 0.0; const f64 a1 = yayVar ? ev.px(g.ib2) : 2.0 * std::numbers::pi; geometri::ProfilEgri e; e.merkez = d3(cx, cy); e.eksen = normal; // refX = eskiz X ekseni rot kadar dönmüş → dünya (yay/çember açı-0; elips büyük eksen). e.refX = dz->donusum.yonelim.dondur({std::cos(rot), std::sin(rot), 0.0}); e.yaricap = ra; e.kucukYaricap = rb; e.aci0 = a0; e.aci1 = a1; // Açık yay uçları (eskiz uzayında, rot dönmesi dahil) → dünya. const f64 cs = std::cos(rot), sr = std::sin(rot); const auto ept = [&](f64 t) -> Vec2d { const f64 lx = ra * std::cos(t), ly = rb * std::sin(t); return {cx + lx * cs - ly * sr, cy + lx * sr + ly * cs}; }; if (elips && yayVar) { // gerçek eliptik yay edge (tessellation YOK) e.tip = PT::EliptikYay; const Vec2d q0 = ept(a0), q1 = ept(a1); e.p0 = d3(q0.x, q0.y); e.p1 = d3(q1.x, q1.y); acik.push_back({q0, q1, e}); } else if (elips) { e.tip = PT::Elips; konturlar.push_back({e}); // kapalı tek eğri } else if (yayVar) { e.tip = PT::Yay; // Uçlar GERÇEK nokta (noktaX 0/1) — coincidence kısıtı & zincirleme bu noktalara bağlı; // açı-tabanlı uç (ept) solver sonrası sapıp zinciri "açık" bırakıyordu (kare+yay profili). const Vec2d q0{ev.noktaX(g.kimlik, 0), ev.noktaY(g.kimlik, 0)}; const Vec2d q1{ev.noktaX(g.kimlik, 1), ev.noktaY(g.kimlik, 1)}; e.p0 = d3(q0.x, q0.y); e.p1 = d3(q1.x, q1.y); acik.push_back({q0, q1, e}); } else { e.tip = PT::Cember; konturlar.push_back({e}); // kapalı tek eğri } } // Açık eğrileri uç-uca zincirle → kapalı ProfilKontur (MakeWire yönü kendi bağlar). constexpr f64 tol = 1e-4; const auto yakin = [&](const Vec2d& p, const Vec2d& q) { return std::hypot(p.x - q.x, p.y - q.y) < tol; }; std::vector kull(acik.size(), false); for (size_t i = 0; i < acik.size(); ++i) { if (kull[i]) continue; geometri::ProfilKontur kontur{acik[i].egri}; Vec2d bas = acik[i].bas, son = acik[i].son; kull[i] = true; bool buyudu = true; while (buyudu && !yakin(son, bas)) { buyudu = false; for (size_t j = 0; j < acik.size(); ++j) { if (kull[j]) continue; if (yakin(acik[j].bas, son)) { kontur.push_back(acik[j].egri); son = acik[j].son; kull[j] = true; buyudu = true; break; } if (yakin(acik[j].son, son)) { kontur.push_back(acik[j].egri); son = acik[j].bas; kull[j] = true; buyudu = true; break; } } } if (yakin(son, bas)) konturlar.push_back(std::move(kontur)); // kapalı → profil } return !konturlar.empty(); } bool PartDesignSistemi::sketchYolu(Varlik sketch, std::vector& yol) const { yol.clear(); auto* sk = dynamic_cast(cekirdek_.nesne(sketch)); if (!sk) return false; const Dugum* dz = sahne_.bul(sk->duzlem()); if (!dz) return false; const eskiz::EskizVeri& ev = sk->eskiz(); // Yapı-olmayan geometrilerden 2B segment havuzu (profil ile aynı tesselasyon). std::vector seg; for (const eskiz::Geometri& g : ev.geometriler()) { if (g.yapi) continue; geoSegmentle(ev, g, seg); } if (seg.empty()) return false; // En uzun uç-uca zincir → düzlem dönüşümüyle dünyaya taşı (uv,0 → dünya). const std::vector uv = yolZincir(seg); if (uv.size() < 2) return false; yol.reserve(uv.size()); for (const Vec2d& p : uv) yol.push_back(dz->donusum.noktaUygula({p.x, p.y, 0.0})); return yol.size() >= 2; } bool PartDesignSistemi::sketchAcikYolu(Varlik sketch, std::vector& yol, Vec3d& normal, DuzlemBilgi& duzlem) const { if (!sketchYolu(sketch, yol)) return false; auto* sk = dynamic_cast(cekirdek_.nesne(sketch)); if (!sk) return false; const Dugum* dz = sahne_.bul(sk->duzlem()); if (!dz) return false; normal = dz->donusum.yonelim.dondur({0, 0, 1}); duzlem.orijin = dz->donusum.konum; duzlem.u = dz->donusum.yonelim.dondur({1, 0, 0}); duzlem.v = dz->donusum.yonelim.dondur({0, 1, 0}); duzlem.normal = normal; return yol.size() >= 2; } bool PartDesignSistemi::sketchNoktalari(Varlik sketch, std::vector& noktalar) const { noktalar.clear(); auto* sk = dynamic_cast(cekirdek_.nesne(sketch)); if (!sk) return false; const Dugum* dz = sahne_.bul(sk->duzlem()); if (!dz) return false; const eskiz::EskizVeri& ev = sk->eskiz(); // Yalnız GeoTip::Nokta geometrileri = konum tanımı (çizgi/daire vb. atlanır). for (const eskiz::Geometri& g : ev.geometriler()) { if (g.tip != eskiz::GeoTip::Nokta) continue; const f64 x = ev.noktaX(g.kimlik, 0), y = ev.noktaY(g.kimlik, 0); noktalar.push_back(dz->donusum.noktaUygula({x, y, 0.0})); // uv → dünya } return !noktalar.empty(); } Varlik PartDesignSistemi::featureBody(u32 fk) { Feature* f = bul(fk); if (!f) return VARLIK_YOK; Varlik v = f->sonucDugum(); for (int koruma = 0; v != VARLIK_YOK && koruma < 64; ++koruma) { if (sahne_.bodyMi(v)) return v; const HiyerarsiBilesen* h = sahne_.dunya().bul(v); v = h ? h->ust : VARLIK_YOK; } return VARLIK_YOK; } Feature* PartDesignSistemi::oncekiAktifFeature(Feature& feat) { int idx = -1; for (size_t i = 0; i < featureler_.size(); ++i) if (featureler_[i] == feat.kimlik()) { idx = (int)i; break; } if (idx <= 0) return nullptr; // CATIA çoklu-body: zincir feature'ın KENDİ body'sinde kalmalı (başka body'nin son katısını // girdi almasın). Body bilinmiyorsa (sonuç düğümü henüz yok) filtre yok → eski davranış (güvenli). const Varlik fbody = featureBody(feat.kimlik()); for (int i = idx - 1; i >= 0; --i) { // en yakın önceki aktif+geçerli feature (AYNI body) Feature* f = bul(featureler_[i]); if (!f || f->bastirildi() || !featureAktifMi(featureler_[i])) continue; if (fbody != VARLIK_YOK && featureBody(featureler_[i]) != fbody) continue; // farklı body → atla Dugum* d = sahne_.bul(f->sonucDugum()); if (d && d->brep && d->brep->gecerliMi()) return f; } return nullptr; } bool PartDesignSistemi::oncekiKatiBrep(Feature& feat, geometri::BrepGovde& cikti) { Feature* onc = oncekiAktifFeature(feat); if (!onc) return false; Dugum* d = sahne_.bul(onc->sonucDugum()); if (!d || !d->brep || !d->brep->gecerliMi()) return false; cikti = geometri::donusumUygula(*d->brep, d->donusum); // sonuç dünya (donusum=identity) return cikti.gecerliMi(); } bool PartDesignSistemi::hedefDunyaBrep(Feature& feat, geometri::BrepGovde& cikti) { // Girdi = feat.hedefDugum ÖNCELİKLİ: DM'de hedef, tıklanan GÖRÜNEN gövdedir (bir feature // sonucuysa o zaten o gövdenin zincirdeki son hali — düğüm brep'i recompute'ta güncellenir). // Eski "zincirde önceki feature" varsayımı tek-PartBody içindi; ÇOK GÖVDELİ belgede (devasa // STEP) başka gövdenin feature sonucunu girdi alıp kenar ordinali uyuşmayınca fillet // "başarısız" oluyordu / yanlış gövdeye işleniyordu. Tek gövdede iki yol eşdeğer. u32 hedefKimlik = feat.hedefDugum(); // CATIA suppress: hedef, BASTIRILMIŞ/rollback-dışı bir feature'ın sonucuysa onun girdisine // in (bastırılan atlanır) — zincir semantiği korunur. for (int koruma = 0; hedefKimlik && koruma < 64; ++koruma) { Feature* uretici = nullptr; for (u32 fk : featureler_) { Feature* f = bul(fk); if (f && f->kimlik() != feat.kimlik() && f->sonucDugum() == hedefKimlik) uretici = f; } if (uretici && (uretici->bastirildi() || !featureAktifMi(uretici->kimlik()))) { hedefKimlik = uretici->hedefDugum(); continue; } break; } if (Dugum* hedef = sahne_.bul(hedefKimlik)) { if (hedef->brep && hedef->brep->gecerliMi()) { cikti = geometri::donusumUygula(*hedef->brep, hedef->donusum); if (cikti.gecerliMi()) return true; } } // Hedef yok/geçersiz (sketch-tabanlı zincir akışı): önceki aktif feature'ın sonucu. if (oncekiKatiBrep(feat, cikti)) return true; feat.hataAyarla("Hedef katı gövde gerekli"); return false; } void PartDesignSistemi::gorunurlukTazele() { gunluk::g_teshisAdim = "PD:gorunurlukTazele"; // CATIA çoklu-body: HER body kendi SON görünür katısını gösterir. Feature'ın body'si = // sonucDugum'un üst zincirinde İLK body (bodyMi). Tek-body'de hepsi aynı body → eski // davranış BİREBİR (tek sonGorunur). Gruplanmamış feature → VARLIK_YOK anahtarı (tek grup). std::unordered_map bodySonGorunur; // body → son görünür feature sonucDugum for (u32 fk : featureler_) { Feature* f = bul(fk); if (!f || f->bastirildi() || !featureAktifMi(fk)) continue; Dugum* d = sahne_.bul(f->sonucDugum()); if (d && d->brep && d->brep->gecerliMi()) bodySonGorunur[featureBody(fk)] = f->sonucDugum(); } for (u32 fk : featureler_) { Feature* f = bul(fk); if (!f) continue; Dugum* sd = sahne_.bul(f->sonucDugum()); const bool sonucVar = sd && sd->brep && sd->brep->gecerliMi(); const auto it = bodySonGorunur.find(featureBody(fk)); const u32 sonGorunur = it != bodySonGorunur.end() ? it->second : 0; // bu feature'ın body'sinin son katısı if (sd) sd->gorunur = (f->sonucDugum() == sonGorunur); // Zincire giren hedef katı YALNIZ feature'ın GEÇERLİ sonucu varsa gizlenir: async apply // sürerken (sonuç henüz inmedi) gövde EKRANDA KALIR — eskiden taslak oluşur oluşmaz // gizlenip worker süresi boyunca sahne boş kalıyordu ("çizim kayboluyor"). if (sonucVar) if (Dugum* h = sahne_.bul(f->hedefDugum())) h->gorunur = false; // Fikri 07-22: feature'ın YUTTUĞU sketch otomatik gizlenir (CATIA absorbed sketch). // Yalnız GEÇERLİ sonuç varken (async apply sürerken sketch ekranda kalır) ve // düzenlenen sketch (muaf) hariç. Ayar kapalı = eski (sketch görünür kalır). if (sonucVar && sketchOtoGizle_ && f->sketch() != VARLIK_YOK && f->sketch() != sketchGizlemeMuaf_) if (Dugum* sk = sahne_.bul(f->sketch())) sk->gorunur = false; } } u64 PartDesignSistemi::featureImza(Feature& feat) const { auto kat = [](u64 h, u64 v) { return (h ^ (v + 0x9e3779b97f4a7c15ull + (h << 6) + (h >> 2))); }; u64 h = 1469598103934665603ull; for (char c : feat.tip()) h = kat(h, (u8)c); h = kat(h, feat.sketch()); h = kat(h, feat.hedefDugum()); auto& depo = sahne_.paramDepo(); // Parametreler: formül varsa çözülmüş efektif değer (formül/depo değişimi de yakalanır). for (const auto& [anahtar, deger] : feat.parametreler()) { for (char c : anahtar) h = kat(h, (u8)c); const f64 ev = feat.formulVarMi(anahtar) ? feat.pEfektif(anahtar, depo, deger) : deger; h = kat(h, std::bit_cast(ev)); } return h ? h : 1; // 0 = "geçersiz" ile çakışmasın } void PartDesignSistemi::gorunuruMesh() { // gorunurlukTazele SON aktif feature sonucunu görünür yaptı — YALNIZ onu tessellate et. // Görünmez ara feature'lar BREP olarak güncel ama mesh/GPU-upload edilmez (perf). for (u32 fk : featureler_) { Feature* f = bul(fk); if (!f) continue; Dugum* d = sahne_.bul(f->sonucDugum()); if (d && d->gorunur && d->brep && d->brep->gecerliMi()) { if (d->brep.get() == d->sonMeshBrep) continue; // aynı brep zaten mesh'li → atla d->yenidenInsaGerek = false; // Kenar kaynağı: girdi gövdesi (hedef-öncelikli; çok gövdede doğru zincir) — değişmeyen // kenar polyline'ları ondan kopyalanır (önbelleği boşsa örneklemeye düşer, zararsız). Dugum* gd = sahne_.bul(f->hedefDugum()); if (!gd || !gd->brep) { if (Feature* onc = oncekiAktifFeature(*f)) gd = sahne_.bul(onc->sonucDugum()); } sahne_.yenidenInsa(*d, gd && gd->brep ? gd->brep.get() : nullptr); } } } // ---- DM ASYNC APPLY (E0d): OCCT hesabı worker'da, sonuç kare pompasında iner ---------- // İleri bildirim (tanım sonucYaz'ın hemen üstünde; varsayılanlar burada). static void provenansKaydet(parca::Feature& feat, geometri::BrepGovde& girdi, geometri::BrepGovde& sonuc, std::vector kesin = {}, std::vector sahipYuzler = {}, bool yalnizSahip = false); // Kesit (split) düzlemini feature parametrelerinden çöz — senkron + async dallar PAYLAŞIR // (tek doğruluk kaynağı). duzlemKaynak: 0=seçili düz yüz/düzlem (kesici SahneNokta: kesicix/y/z // + kesiciNx/Ny/Nz + kesiciVar) · 1=Dünya XY(Z=0) · 2=Dünya YZ(X=0) · 3=Dünya ZX(Y=0). // false → hata feature'a yazıldı (çağıran sessizce döner). Sahne erişimi YOK → worker'da da // çağrılabilir, ama iki dal da MAIN'de çözer (hata mesajı feature'a main'de yazılmalı). static bool splitDuzlemCoz(parca::Feature& feat, Vec3d& nokta, Vec3d& normal) { switch ((int)feat.p("duzlemKaynak")) { case 1: nokta = {0, 0, 0}; normal = {0, 0, 1}; return true; // Dünya XY case 2: nokta = {0, 0, 0}; normal = {1, 0, 0}; return true; // Dünya YZ case 3: nokta = {0, 0, 0}; normal = {0, 1, 0}; return true; // Dünya ZX default: break; } if (feat.p("kesiciVar") < 0.5) { feat.hataAyarla("Kesici düzlem seçin (düz bir yüz veya datum düzlemi)"); return false; } nokta = {feat.p("kesicix"), feat.p("kesiciy"), feat.p("kesiciz")}; normal = {feat.p("kesiciNx"), feat.p("kesiciNy"), feat.p("kesiciNz")}; if (normal.uzunlukKare() <= 1e-9) { // normalsiz seçim (kavisli yüz / serbest nokta) feat.hataAyarla("Seçilen öğe düz değil — kesit için düz yüz/düzlem seçin"); return false; } return true; } bool PartDesignSistemi::featureAsyncUygula(u32 feat, const std::vector>& params, std::function bitti) { Feature* f = bul(feat); if (!f) return false; const std::string tip = f->tip(); const bool draftMi = (tip == "draft" || tip == "draft_reflect"); if (tip != "fillet" && tip != "chamfer" && tip != "yuz_itcek" && tip != "fillet_var" && tip != "shell" && tip != "yuz_tasi" && tip != "remove_face" && tip != "cikinti_kaydir" && tip != "yuz_dondur" && tip != "kenar_eg" && tip != "split" && !draftMi) return false; for (const auto& [anahtar, deger] : params) f->pAyarla(anahtar, deger); AsyncIs is; is.fk = feat; is.tip = tip; is.bitti = std::move(bitti); auto& depo = sahne_.paramDepo(); const auto yuzleriTopla = [&](int enAz1) { const int yn = std::max((int)f->p("yuzSayi"), enAz1); for (int i = 0; i < yn; ++i) { const u32 o = (u32)f->p("yuz" + std::to_string(i)); if (o) is.yuzler.push_back(o); } }; if (tip == "yuz_itcek") { // Çok seçim: yuzSayi + yuz0..N (fillet'in kenarSayi kalıbı); eski dosyada yuzSayi=0 → yuz0. yuzleriTopla(1); is.mesafe = f->pEfektif("mesafe", depo, 0.0); // blendKorur: hareket eden yüze teğet fillet'i SÖK→ötele→AYNI R ile yeniden KUR // (yoksa fillet ötelenmez, yarıçapı değişirdi). tegetTol = teğetlik açı toleransı. is.bayrak = f->p("blendKorur") > 0.5; is.deger = f->pEfektif("tegetTol", depo, 1.0); if (is.yuzler.empty() || std::abs(is.mesafe) <= 1e-6) return false; // senkron yol hatayı üretir } else if (tip == "fillet" || tip == "chamfer") { const int n = (int)f->p("kenarSayi"); for (int i = 0; i < n; ++i) { const u32 o = (u32)f->p("kenar" + std::to_string(i)); if (o) is.kenarlar.push_back(o); } is.deger = f->pEfektif(tip == "fillet" ? "yaricap" : "mesafe", depo, 3.0); if (is.kenarlar.empty() || is.deger <= 1e-6) return false; } else if (tip == "fillet_var") { const u32 kenar = (u32)f->p("kenar0"); is.deger = f->pEfektif("r1", depo, 3.0); is.mesafe = f->pEfektif("r2", depo, 6.0); is.bayrak = f->p("varyasyon") > 0.5; // 0 Linear · 1 Cubic if (kenar == 0 || is.deger <= 1e-6 || is.mesafe <= 1e-6) return false; is.kenarlar.push_back(kenar); } else if (tip == "shell") { yuzleriTopla(0); is.deger = f->pEfektif("kalinlik", depo, 2.0); if (is.yuzler.empty() || is.deger <= 1e-6) return false; } else if (tip == "remove_face") { // Remove Face: yalnız yüz seçimi (parametre yok) yuzleriTopla(0); if (is.yuzler.empty()) return false; } else if (tip == "yuz_tasi") { // move-face: yuzSayi + yuz0..N (eski dosyada yuz0) + vec → v1 yuzleriTopla(1); is.v1 = {f->pEfektif("tasiX", depo, 0.0), f->pEfektif("tasiY", depo, 0.0), f->pEfektif("tasiZ", depo, 0.0)}; if (is.yuzler.empty() || is.v1.uzunlukKare() <= 1e-12) return false; } else if (tip == "cikinti_kaydir") { // blok kaydır: yuz0 + vec (v1) + taban düzlemi (v2 nokta / v3 normal) const u32 yuz = (u32)f->p("yuz0"); is.v1 = {f->pEfektif("tasiX", depo, 0.0), f->pEfektif("tasiY", depo, 0.0), f->pEfektif("tasiZ", depo, 0.0)}; if (yuz == 0 || is.v1.uzunlukKare() <= 1e-12) return false; is.yuzler.push_back(yuz); // Taban parametresizse v3 sıfır kalır → WORKER cikintiTabanBul ile OTOMATİK bulur (bölge // büyütme main'de değil: senkron yolun asıl donma kaynağı buydu). is.v2 = {f->p("tabanX"), f->p("tabanY"), f->p("tabanZ")}; is.v3 = {f->p("tabanNX"), f->p("tabanNY"), f->p("tabanNZ")}; } else if (tip == "yuz_dondur") { // yuz0 + aci + eksen (v4) + taban düzlemi (v2/v3, boşsa worker bulur) const u32 yuz = (u32)f->p("yuz0"); is.deger = f->pEfektif("aci", depo, 0.0); is.v4 = {f->p("eksNX"), f->p("eksNY"), f->p("eksNZ")}; if (yuz == 0 || std::abs(is.deger) < 1e-6 || is.v4.uzunlukKare() <= 1e-9) return false; is.yuzler.push_back(yuz); is.v2 = {f->p("tabanX"), f->p("tabanY"), f->p("tabanZ")}; is.v3 = {f->p("tabanNX"), f->p("tabanNY"), f->p("tabanNZ")}; is.bayrak = f->p("rijit") > 0.5; } else if (tip == "kenar_eg") { // menteşe kenar0 + eğilecek yuz0 + aci const u32 kenar = (u32)f->p("kenar0"); const u32 yuz = (u32)f->p("yuz0"); is.deger = f->pEfektif("aci", depo, 0.0); if (kenar == 0 || yuz == 0 || std::abs(is.deger) < 1e-6) return false; is.kenarlar.push_back(kenar); is.yuzler.push_back(yuz); } else if (tip == "kenar_tasi") { // çekilen kenar0 + düzleminde uzayan yuz0 + mesafe const u32 kenar = (u32)f->p("kenar0"); const u32 yuz = (u32)f->p("yuz0"); is.mesafe = f->pEfektif("mesafe", depo, 0.0); if (kenar == 0 || yuz == 0 || std::abs(is.mesafe) < 1e-9) return false; is.kenarlar.push_back(kenar); is.yuzler.push_back(yuz); } else if (tip == "split") { // kesit: düzlem (v2 nokta / v3 normal) + ters (bayrak) // Düzlem MAIN'de çözülür: hata mesajı feature'a burada yazılır; false → çağıran senkron // yola düşer, o da AYNI yardımcıyla aynı hatayı üretir (tek doğruluk kaynağı). if (!splitDuzlemCoz(*f, is.v2, is.v3)) return false; is.bayrak = f->p("tersYon") > 0.5; } else { // draft / draft_reflect — yön + nötr MAIN'de çözülür (sahne worker'a giremez) yuzleriTopla(0); is.deger = f->pEfektif("aci", depo, 5.0); if (is.yuzler.empty() || std::abs(is.deger) < 1e-6) return false; is.bayrak = (tip == "draft_reflect"); switch ((int)f->p("yon")) { case 0: is.v1 = {1, 0, 0}; break; case 1: is.v1 = {0, 1, 0}; break; case 3: { Dugum* dk = sahne_.bul((u32)f->p("cekmeKenarG")); geometri::KenarBilgi kb; if (dk && dk->brep && dk->brep->kenarBilgi((u32)f->p("cekmeKenarA"), kb) && kb.dogruMu) is.v1 = dk->donusum.yonelim.dondur(kb.yon).birim(); else is.v1 = {0, 0, 1}; break; } default: is.v1 = {0, 0, 1}; break; } if (!is.bayrak) { if (f->p("notralYuzVar") > 0.5) { is.v2 = {f->p("notralYuzx"), f->p("notralYuzy"), f->p("notralYuzz")}; is.v3 = {f->p("notralYuzNx"), f->p("notralYuzNy"), f->p("notralYuzNz")}; } else if (f->p("ayirmaDuzlemVar") > 0.5) { is.v2 = {f->p("ayirmaDuzlemx"), f->p("ayirmaDuzlemy"), f->p("ayirmaDuzlemz")}; is.v3 = {f->p("ayirmaDuzlemNx"), f->p("ayirmaDuzlemNy"), f->p("ayirmaDuzlemNz")}; } } } const bool ilk = asyncKuyruk_.empty(); asyncKuyruk_.push_back(std::move(is)); if (ilk) kuyrukIlerle(); cekirdek_.olaylar().yayinla(FeatureGuncellendi{feat}); return true; } bool PartDesignSistemi::asyncBaslat(AsyncIs& is) { Feature* f = bul(is.fk); if (!f) { if (is.bitti) is.bitti(false); return false; } // BAYAT HEDEF İLERİ-YÜRÜTME (data-kaybı kökü): jest yapılırken görünen gövde, kuyruk // öndeki işler inmeden ESKİ düğümdür — hedef ardıl feature sonuçlarına ilerletilir, // yüz/kenar ordinalleri IsSame (yetmezse geometrik) ile İLERİ taşınır. Taşınamayan // (önceki işlemle değişen) seçim → iş düşer + kullanıcıya net hata. Böylece 2. jest // 1.'in sonucunu EZMEZ (önceki davranış: zincir çatallanıp ilk işlem kayboluyordu). for (int koruma = 0; koruma < 64; ++koruma) { Feature* ardil = nullptr; for (u32 fk2 : featureler_) { Feature* f2 = bul(fk2); if (f2 && fk2 != is.fk && !f2->bastirildi() && featureAktifMi(fk2) && f2->hedefDugum() == f->hedefDugum() && f2->sonucDugum() != 0) ardil = f2; // aynı hedefi işlemiş SON aktif feature } if (!ardil) break; Dugum* eskiD = sahne_.bul(f->hedefDugum()); Dugum* yeniD = sahne_.bul(ardil->sonucDugum()); if (!eskiD || !eskiD->brep || !yeniD || !yeniD->brep || !yeniD->brep->gecerliMi()) break; std::vector geo; // geometrik ileri-eşleme (lazy — yalnız IsSame kaçırırsa) const auto geometrikYuz = [&](u32 eskiOrd) -> u32 { if (geo.empty()) geo = geometri::yuzGeometrikEsle(*eskiD->brep, *yeniD->brep); for (std::size_t j = 0; j < geo.size(); ++j) if ((u32)geo[j] == eskiOrd) return (u32)(j + 1); return 0; }; bool tamam = true; for (std::size_t i = 0; tamam && i < is.yuzler.size(); ++i) { u32 y = eskiD->brep->yuzIleriEsle(*yeniD->brep, is.yuzler[i]); if (!y) y = geometrikYuz(is.yuzler[i]); if (y) { is.yuzler[i] = y; f->pAyarla("yuz" + std::to_string(i), (f64)y); } else tamam = false; } for (std::size_t i = 0; tamam && i < is.kenarlar.size(); ++i) { const u32 y = eskiD->brep->kenarIleriEsle(*yeniD->brep, is.kenarlar[i]); if (y) { is.kenarlar[i] = y; f->pAyarla("kenar" + std::to_string(i), (f64)y); } else tamam = false; } if (!tamam) { f->hataAyarla("Seçilen yüz/kenar önceki işlemle değişti — yeniden seçin"); if (is.bitti) is.bitti(false); return false; } f->hedefDugumAyarla(ardil->sonucDugum()); } geometri::BrepGovde hd; if (!hedefDunyaBrep(*f, hd)) { // hata feature'a yazıldı if (is.bitti) is.bitti(false); return false; } is.girdi = std::make_shared(std::move(hd)); if (Dugum* hn = sahne_.bul(f->hedefDugum())) is.tabanRenk = hn->renk; // Girdi ordinal önbelleklerini MAIN'de kur (ilk çağrıda komple dolar): worker'daki // geometrik eşleme/yuzBilgi, main-thread hover ile aynı mutable önbelleğe YAZMASIN. { geometri::YuzBilgi yb; (void)is.girdi->yuzBilgi(1, yb, /*alanHesapla*/ false); geometri::KenarBilgi kb; (void)is.girdi->kenarBilgi(1, kb); } auto girdi = is.girdi; const std::string tip = is.tip; const auto kenarlar = is.kenarlar; const f64 deger = is.deger, mesafe = is.mesafe; const auto yuzler = is.yuzler; const Vec3f tabanRenk = is.tabanRenk; const Vec3d v1 = is.v1, v2 = is.v2, v3 = is.v3, v4 = is.v4; const bool bayrak = is.bayrak; is.sonuc = std::async(std::launch::async, [girdi, tip, kenarlar, deger, yuzler, mesafe, tabanRenk, v1, v2, v3, v4, bayrak] { geometri::occtSinyalKur(); // OCCT sinyal koruması worker thread'de de kurulmalı AsyncIs::Sonuc s; if (tip == "fillet") s.govde = geometri::yuvarlat(*girdi, kenarlar, deger, &s.remap); else if (tip == "chamfer") s.govde = geometri::pahla(*girdi, kenarlar, deger, &s.remap); else if (tip == "yuz_itcek") s.govde = bayrak ? geometri::yuzItCekBlendKorur(*girdi, yuzler, mesafe, deger > 0 ? deger : 1.0, &s.remap) : geometri::yuzItCekCoklu(*girdi, yuzler, mesafe, &s.remap); else if (tip == "fillet_var") s.govde = geometri::yuvarlatDegisken(*girdi, kenarlar.empty() ? 0 : kenarlar[0], deger, mesafe, bayrak); else if (tip == "shell") s.govde = geometri::kabuk(*girdi, yuzler, deger); else if (tip == "remove_face") s.govde = geometri::yuzKaldir(*girdi, yuzler, &s.remap); else if (tip == "yuz_tasi") s.govde = geometri::yuzTasiCoklu(*girdi, yuzler, v1); else if (tip == "kenar_eg") { s.govde = geometri::kenardanYuzEg(*girdi, kenarlar.empty() ? 0 : kenarlar[0], yuzler.empty() ? 0 : yuzler[0], deger); if (!s.govde.doluMu()) s.hata = "Kenardan eğ başarısız: DÜZ menteşe kenarı + DÜZ komşu yüz gerekli " "(açı geometriye sığmıyor da olabilir)"; } else if (tip == "kenar_tasi") { s.govde = geometri::kenarTasi(*girdi, kenarlar.empty() ? 0 : kenarlar[0], yuzler.empty() ? 0 : yuzler[0], mesafe); if (!s.govde.doluMu()) s.hata = "Kenardan uzat başarısız: kenarın İKİ komşusu da DÜZ olmalı ve karşıda " "paralel menteşe kenarı bulunmalı"; } else if (tip == "split") s.govde = geometri::duzlemleBol(*girdi, v2, v3, bayrak); else if (tip == "cikinti_kaydir" || tip == "yuz_dondur") { // Ortak: taban düzlemi + seed. Taban verilmemişse OTOMATİK bulunur — bölge büyütme // WORKER'da (senkron yolun asıl donma kaynağıydı), ölçüt senkron dalla birebir. const u32 yuz = yuzler.empty() ? 0 : yuzler[0]; Vec3d tNokta = v2, tNormal = v3; if (tNormal.uzunlukKare() <= 1e-9) { const geometri::CikintiTabanSonuc ts = geometri::cikintiTabanBul(*girdi, yuz); if (!ts.gecerli) { s.hata = "Taban düzlemi bulunamadı — seçilen yüzün altında ayrık bir çıkıntı yok " "(model tek parça olabilir). Yüzü eğmek için Draft kullanın."; return s; } tNokta = ts.nokta; tNormal = ts.normal; } geometri::YuzBilgi yb; Vec3d seed{0, 0, 0}; if (girdi->yuzBilgi(yuz, yb)) seed = yb.merkez; // taşınacak/döndürülecek parçayı seçmek için if (tip == "cikinti_kaydir") s.govde = geometri::cikintiKaydir(*girdi, tNokta, tNormal, v1, seed); else { // yuz_dondur: rijit → başarısızsa draft (senkron dalla AYNI sıra/ölçüt) if (bayrak) s.govde = geometri::yuzDondur(*girdi, tNokta, tNormal, tNokta, v4, deger, seed); if (!s.govde.doluMu()) s.govde = geometri::yuzDraft(*girdi, yuzler, deger, tNormal, tNokta, tNormal, false); if (!s.govde.doluMu()) s.hata = "Yüz döndürülemedi: taban üstünde ayrık çıkıntı ayrışmadı ve Draft da " "uygulanamadı — model plaka+çıkıntı biçiminde değil. (Gerçek yüz eğme " "geliştirme yolunda.)"; } } else s.govde = geometri::yuzDraft(*girdi, yuzler, deger, v1, v2, v3, bayrak); if (!s.govde.doluMu()) return s; // Remap'i geometrik eşlemeyle BİRLEŞTİR (provenansKaydet ile aynı ölçüt): BRepFeat kimi // koşularda tüm TShape'leri yeniden kurar → history remap ~boş kalır; renk/yuzOz mirası // ve provenance geometrik dolguyla ayakta kalır (pahalı kısım worker'da — main donmaz). std::size_t dolu = 0; for (int v : s.remap) if (v) ++dolu; const std::size_t kesinDolu = dolu; if (s.remap.empty() || dolu * 2 < s.remap.size()) { std::vector geo = geometri::yuzGeometrikEsle(*girdi, s.govde); if (s.remap.empty()) s.remap = std::move(geo); else for (std::size_t j = 0; j < s.remap.size() && j < geo.size(); ++j) if (s.remap[j] == 0) s.remap[j] = geo[j]; dolu = 0; for (int v : s.remap) if (v) ++dolu; } gunluk::bilgi("[renk] worker {}: girdi {} | remap kesin {} / birleşim {} / {} yüz", tip, girdi->yuzRenkliMi() ? "RENKLİ" : "renksiz", kesinDolu, dolu, s.remap.size()); // Renk sigortası WORKER'da (mesh pişmeden ÖNCE olmalı — vertex tabanı brep renginden): // sonuç renksiz kaldıysa (renkPropagasyonu history bulamadı) girdiden geri-haritayla kur. if (!s.govde.yuzRenkliMi() && girdi->yuzRenkliMi()) { const auto& gr = girdi->yuzRenkleri(); std::vector renkler(s.remap.size(), tabanRenk); for (std::size_t j = 0; j < s.remap.size(); ++j) if (s.remap[j] > 0 && (std::size_t)s.remap[j] <= gr.size()) renkler[j] = gr[(std::size_t)s.remap[j] - 1]; s.govde.yuzRenkleriAta(std::move(renkler)); } // İNCE mesh + kenar/nokta worker'da (pump yalnız GPU upload yapar): değişmeyen yüz/kenar // önbellekleri girdiden akar; rebuild koşulundaki 3-4sn tessellation main'i bloklamaz. s.veri = s.govde.ucgenle(tabanRenk); s.kenarlar = s.govde.kenarMesh(-1.0, girdi.get()); s.noktalar = s.govde.noktaMesh(); return s; }); return true; } void PartDesignSistemi::kuyrukIlerle() { while (!asyncKuyruk_.empty() && !asyncKuyruk_.front().sonuc.valid() && !asyncBaslat(asyncKuyruk_.front())) asyncKuyruk_.pop_front(); } // Zincir yeniden oynarken ardıl feature'ın saklı kenar/yüz ordinallerini ESKİ ara sonuçtan // YENİSİNE taşır (edit topolojiyi değiştirebilir — usd birleşmeleri yüz sayısını oynatıyor). // Eşlenemeyen ordinal olduğu gibi bırakılır (op başarısızsa feature hatası zaten net söyler). static void ardilOrdinalIleriTasi(parca::Feature& f2, const geometri::BrepGovde& eski, const geometri::BrepGovde& yeni) { std::vector geo; // lazy geometrik yüz eşlemesi (yalnız IsSame kaçırırsa kurulur) const auto geoYuz = [&](u32 o) -> u32 { if (geo.empty()) geo = geometri::yuzGeometrikEsle(eski, yeni); for (std::size_t j = 0; j < geo.size(); ++j) if ((u32)geo[j] == o) return (u32)(j + 1); return 0; }; int eslenemeyen = 0; const int kn = (int)f2.p("kenarSayi"); for (int i = 0; i < kn; ++i) { const std::string ad = "kenar" + std::to_string(i); const u32 o = (u32)f2.p(ad); if (!o) continue; if (const u32 y = eski.kenarIleriEsle(yeni, o)) { if (y != o) f2.pAyarla(ad, (f64)y); } else ++eslenemeyen; // kenar IsSame ile eşleşmedi (geometrik kenar eşleme yok) → eski değerde kalır } int yn = (int)f2.p("yuzSayi"); if (yn < 1 && (u32)f2.p("yuz0") != 0) yn = 1; // eski tek-yüz kaydı (yuzSayi yok) for (int i = 0; i < yn; ++i) { const std::string ad = "yuz" + std::to_string(i); const u32 o = (u32)f2.p(ad); if (!o) continue; u32 y = eski.yuzIleriEsle(yeni, o); if (!y) y = geoYuz(o); if (y && y != o) f2.pAyarla(ad, (f64)y); else if (!y) ++eslenemeyen; // yüz ne IsSame ne geometrik eşleşti → eski değerde kalır } // Eşlenemeyen ordinal = ardıl feature yanlış kenar/yüze işlem yapabilir VEYA hata verir // (sessiz veri kaybı değil; op başarısızlığı feature hatasında görünür). Teşhis için logla — // "zincir ortası edit sonrası ardıl bozuldu" şüphesinde [dm] satırı kökü söyler. if (eslenemeyen > 0) gunluk::uyari("[dm] ardıl feature {} ordinal ileri-eşleme: {} öğe taşınamadı " "(edit topolojiyi değiştirdi) — ardıl yanlış hedefe düşebilir", f2.kimlik(), eslenemeyen); } void PartDesignSistemi::asyncSonucUygula(AsyncIs& is) { if (is.iptal) return; // × ile iptal → sonuç uygulanmaz, callback çağrılmaz (ghost/param × işleyende geri alındı) asyncUygulaniyor_ = true; AsyncIs::Sonuc s = is.sonuc.get(); Feature* f = bul(is.fk); if (!f) { asyncUygulaniyor_ = false; return; } // feature silinmiş → sonuç sessizce atılır f->hataAyarla(""); const bool ok = s.govde.doluMu(); // Ardıl ordinal ileri-eşlemesi için ESKİ ara sonucu yakala (sonucYaz düğüm brep'ini değiştirir). std::shared_ptr eskiSonucBrep; if (ok) if (Dugum* dn = sahne_.bul(f->sonucDugum())) if (dn->brep && dn->brep->gecerliMi()) eskiSonucBrep = dn->brep; if (ok) { if (is.tip == "yuz_itcek" || is.tip == "yuz_tasi") provenansKaydet(*f, *is.girdi, s.govde, std::move(s.remap), is.yuzler, /*yalnizSahip*/ true); else provenansKaydet(*f, *is.girdi, s.govde, std::move(s.remap)); sonucYaz(*f, std::move(s.govde), /*ekler*/ false, &s.veri, &s.kenarlar, &s.noktalar); f->surumArtir(); f->insaImzaAyarla(zincirImzaDegeri(is.fk)); // sonraki senkron zincir boş yere hesaplamasın gorunurlukTazele(); featureOnayla(is.fk); // ZİNCİR İLERİ-YÜRÜTME: zincir ORTASINDAKİ feature düzenlendiyse (uzatılan yüzü geri // itmek gibi) ardıl feature'lar ESKİ girdiyle kalır — görünen (son) gövde değişmez // ("yapıldı diyor ama bir şey olmuyor" kökü; senkron yol zincirHesapla ile zaten // yapıyordu). Ardıl = bu feature'ın sonucunu hedef alan aktif feature: async destekliyse // parametresiz işle KUYRUĞA girer (o inince KENDİ ardılını tetikler — zincir kendini // yürütür); desteklenmeyen tip → senkron zincir (imza-atlamalı, yalnız etkilenen dal). for (u32 fk2 : featureler_) { Feature* f2 = bul(fk2); if (!f2 || fk2 == is.fk || f2->bastirildi() || !featureAktifMi(fk2)) continue; if (f2->hedefDugum() != f->sonucDugum()) continue; f2->insaImzaAyarla(0); // girdi değişti → imza geçersiz (senkron yol da atlamasın) // Ordinal ileri-eşleme: edit ara gövdenin topolojisini değiştirdiyse ardılın saklı // kenar/yüz ordinalleri yeni ara gövdeye taşınır (aksi halde yanlış kenara fillet). if (eskiSonucBrep) if (Dugum* dn = sahne_.bul(f->sonucDugum())) if (dn->brep && dn->brep.get() != eskiSonucBrep.get()) ardilOrdinalIleriTasi(*f2, *eskiSonucBrep, *dn->brep); // zincirHesapla drain'i asyncUygulaniyor_ sayesinde ATLANIR (reentrans güvenli) — // kuyruktaki tüketilmiş öndeki iş yeniden başlatılmaz. if (!featureAsyncUygula(fk2, {}, {})) zincirHesapla(0); } } else if (!s.hata.empty()) { f->hataAyarla(s.hata); // worker'ın SOMUT nedeni (A1 teşhis) genel tabloyu ezer featureOnayla(is.fk); } else { f->hataAyarla( is.tip == "yuz_itcek" ? "Yüz it/çek başarısız (mesafe geçersiz olabilir)" : is.tip == "fillet" ? "Yuvarlatma başarısız (yarıçap büyük olabilir)" : is.tip == "chamfer" ? "Pah başarısız (mesafe büyük olabilir)" : is.tip == "fillet_var" ? "Değişken fillet başarısız (yarıçap büyük olabilir)" : is.tip == "shell" ? "Kabuk başarısız (kalınlık büyük veya geometri uygun değil)" : is.tip == "yuz_tasi" ? "Yüz taşı başarısız (yön yüze paralel veya geometri uygun değil)" : is.tip == "cikinti_kaydir" ? "Blok kaydır başarısız (taban/çıkıntı ayrışmadı veya çıkıntı taban dışına taştı)" : is.tip == "yuz_dondur" ? "Yüz döndür başarısız (açı çok büyük veya yüz uygun değil)" : is.tip == "split" ? "Kesit başarısız (düzlem gövdeyi kesmiyor veya tutulan parça yok)" : "Draft başarısız (açı/yön/nötr geometriye uygun değil)"); featureOnayla(is.fk); // hata → taslak kalır (pencereden düzeltilebilir; senkronla aynı) } cekirdek_.olaylar().yayinla(FeatureGuncellendi{is.fk}); if (is.bitti) is.bitti(ok); asyncUygulaniyor_ = false; } bool PartDesignSistemi::featureAsyncGeriAl( u32 feat, const std::vector>& eskiParams) { Feature* f = bul(feat); if (!f) return false; bool bulundu = false; for (AsyncIs& is : asyncKuyruk_) if (is.fk == feat && !is.iptal) { is.iptal = true; bulundu = true; } if (!bulundu) return false; // iş zaten inmiş/yok → geri alınamaz (geometri yeni; param'a dokunma → desync olmasın) for (const auto& [k, v] : eskiParams) f->pAyarla(k, v); // parametreyi eski değere yaz (geometri zaten eski) cekirdek_.olaylar().yayinla(FeatureGuncellendi{feat}); // ağaç/parametre paneli tazele return true; } void PartDesignSistemi::asyncBekleyenUygula() { if (asyncKuyruk_.empty()) return; AsyncIs& is = asyncKuyruk_.front(); if (!is.sonuc.valid()) { kuyrukIlerle(); return; } if (is.sonuc.wait_for(std::chrono::seconds(0)) != std::future_status::ready) return; asyncSonucUygula(is); asyncKuyruk_.pop_front(); kuyrukIlerle(); } void PartDesignSistemi::asyncTumunuBitir() { while (!asyncKuyruk_.empty()) { if (!asyncKuyruk_.front().sonuc.valid()) { kuyrukIlerle(); if (asyncKuyruk_.empty() || !asyncKuyruk_.front().sonuc.valid()) continue; } AsyncIs& is = asyncKuyruk_.front(); is.sonuc.wait(); asyncSonucUygula(is); asyncKuyruk_.pop_front(); } } u64 PartDesignSistemi::zincirImzaDegeri(u32 hedefFk) { u64 girdiImza = 1469598103934665603ull; // zincirHesapla ile BİREBİR aynı tohum for (u32 fk : featureler_) { Feature* f = bul(fk); if (!f || f->bastirildi() || !featureAktifMi(fk)) continue; const u64 kendi = featureImza(*f); const u64 yeni = girdiImza ^ (kendi + 0x9e3779b97f4a7c15ull + (girdiImza << 6) + (girdiImza >> 2)); if (fk == hedefFk) return yeni; girdiImza = yeni; } return 0; } void PartDesignSistemi::zincirHesapla(u32 baslangic) { // Senkron zincir bekleyen async işlerle YARIŞMASIN: önce onları indir (pompa apply'ı // içinden gelindiyse — asyncUygulaniyor_ — atlanır; reentrans/deadlock olmaz). // Ölçüm ("önce ölç"): asyncTumunuBitir() bekleyen worker işini (fillet/shell/vb., 3-4sn // sürebilir) SENKRON is.sonuc.wait() ile bekler → ana thread o iş bitene dek TAMAMEN // DONAR. 2026-08-09 teşhis: "hover-kare" kare-periyot sıçramalarının (877-2530ms) şüpheli // kaynağı budur (asyncBekleyenUygula'nın aksine burada zaman aşımı/erken çıkış yok). if (!asyncUygulaniyor_) { const size_t zhBekleyenIs = asyncKuyruk_.size(); const auto zh0 = std::chrono::steady_clock::now(); asyncTumunuBitir(); if (zhBekleyenIs > 0) { const f64 zhMs = std::chrono::duration(std::chrono::steady_clock::now() - zh0).count(); if (zhMs > 100.0) gunluk::bilgi("[perf] zincirHesapla: {} bekleyen async iş için {:.0f} ms BLOKLANDI " "(ana thread donması)", zhBekleyenIs, zhMs); } } if (baslangic != 0) if (Feature* b = bul(baslangic)) b->insaGecersizKil(); // bu feature kesin hesaplansın meshErtele_ = true; // ara sonuçlar tessellate edilmesin → sonda yalnız görünür mesh'lenir u64 girdiImza = 1469598103934665603ull; // zincir başı (girdi katısı yok) sabiti for (u32 fk : featureler_) { Feature* f = bul(fk); if (!f || f->bastirildi() || !featureAktifMi(fk)) continue; // devre dışı: girdiyi taşımaz const u64 kendi = featureImza(*f); const u64 yeni = girdiImza ^ (kendi + 0x9e3779b97f4a7c15ull + (girdiImza << 6) + (girdiImza >> 2)); Dugum* d = sahne_.bul(f->sonucDugum()); const bool sonucGecerli = d && d->brep && d->brep->gecerliMi(); if (f->insaImza() == yeni && f->insaImza() != 0 && sonucGecerli) { girdiImza = yeni; // girdi + parametreler değişmedi → sonuç korunur (yeniden hesap yok) continue; } const auto ft0 = std::chrono::steady_clock::now(); yenidenInsa(*f); // girdisi (önceki sonuç) güncel → CATIA zincir tutarlı // Faz ölçümü ("önce ölç"): OCCT feature op maliyeti (mesh yenidenInsa ayrı loglanır). const f64 featMs = std::chrono::duration(std::chrono::steady_clock::now() - ft0).count(); if (featMs > 50.0) gunluk::bilgi("[perf] feature '{}' ({}) recompute: {:.0f} ms", f->ad(), f->tip(), featMs); // Başarılıysa (hata yok) imzayı sakla; başarısızsa 0 bırak → sonraki turda tekrar dene. f->insaImzaAyarla(f->hata().empty() ? yeni : 0); girdiImza = yeni; } meshErtele_ = false; gorunurlukTazele(); gorunuruMesh(); } // ---- Yüz provenance (DM akıllı re-edit çekirdeği) -------------------------------------- // Her recompute feature'a iki kayıt yazar: uretilenYuzler (SAHİPLENDİĞİ sonuç yüzleri — // tıklanınca bu feature düzenlenir) + yuzGeriHarita (sonuç→girdi ordinali — yuzFeatureBul // üst feature'lardan geriye yürürken yüzü girdi uzayına taşır). kesin: OCCT history remap'i // (boşsa/hepsi 0 ise yuzGeometrikEsle geometrik eşlemeye düşülür). sahipYuz: girdi uzayında // "taşınmış ama bu feature'ın tutamacı" yüz (it/çek'in ötelediği yüz) — kesin haritada ona // eşlenen sonuç yüzleri de sahipliğe girer. Girdisiz (zincir başı) çağrıda tüm yüzler sahiplenir. static void provenansKaydet(parca::Feature& feat, geometri::BrepGovde& girdi, geometri::BrepGovde& sonuc, std::vector kesin, std::vector sahipYuzler, bool yalnizSahip) { if (!sonuc.gecerliMi()) return; // Kesinlik ölçütü ORAN: BRepFeat kimi koşularda TÜM TShape'leri yeniden kurar → history // remap'i neredeyse boş kalır ama LastShape ataması yüzünden "kesin var" görünürdü; o // zaman geometrik eşleme hiç koşmaz, geri-harita çöker (renk/yuzOz mirası tek renge iner — // kullanıcı: "2-3. it/çekten sonra renkler kayboluyor"). Eşleşme < %50 ise geometrik // eşlemeyle BİRLEŞTİR (kesin nonzero öncelikli; yalnız 0'lar doldurulur). std::size_t dolu = 0; for (int v : kesin) if (v) ++dolu; const bool kesinVar = !kesin.empty() && dolu * 2 >= kesin.size(); std::vector geri = std::move(kesin); if (!kesinVar) { gunluk::g_teshisAdim = "PRV:yuzGeometrikEsle"; std::vector geo = geometri::yuzGeometrikEsle(girdi, sonuc); if (geri.empty()) { geri = std::move(geo); } else { for (std::size_t j = 0; j < geri.size() && j < geo.size(); ++j) if (geri[j] == 0) geri[j] = geo[j]; } } std::vector uretilen; for (std::size_t j = 0; j < geri.size(); ++j) { // yalnizSahip (it/çek): SADECE ötelenen yüz(ler) feature'ı temsil eder — prizmanın yan // duvarları sahiplenilmez (kullanıcı yan duvarı çekince İLK it/çek düzenlenmesin, // YENİ bağımsız it/çek açılsın). Diğer tiplerde (fillet bevel'leri vb.) yeni yüzler sahip. bool sahipEs = false; for (u32 sy : sahipYuzler) if (sy && (u32)geri[j] == sy) { sahipEs = true; break; } const bool sahip = yalnizSahip ? sahipEs : (geri[j] == 0 || sahipEs); if (sahip) uretilen.push_back((u32)(j + 1)); } gunluk::g_teshisAdim = "PRV:uretilenYuzler"; feat.uretilenYuzlerAyarla(std::move(uretilen)); // KYA Faz 2: sonuç yüz KID'lerini girdiden TAŞI (aynı geri-harita ölçütü — renk/provenance ikizi). // Girdi etiketsizse önce doğum-etiketle (lazy base). Yalnız metadata → geometri/seçim/renk değişmez. if (geometri::kaliciKimlikAcikMi()) { gunluk::g_teshisAdim = "PRV:kimlikleriBaslat"; if (!girdi.kimlikliMi()) girdi.kimlikleriBaslat(); gunluk::g_teshisAdim = "PRV:kimlikMiras"; sonuc.kimlikMiras(girdi, geri); // yüz KESİN (geri) + kenar/nokta geometrik } gunluk::g_teshisAdim = "PRV:yuzGeriHarita"; feat.yuzGeriHaritaAyarla(std::move(geri)); gunluk::g_teshisAdim = "PRV:bitti"; } // Rijit dönüşüm/ölçek yüz SIRASINI korur → geri-harita birim (her yüz eski ordinalinde, // sahiplenilen yüz yok; tıklama alt feature'lara doğru geçer). static std::vector kimlikHaritasi(const geometri::BrepGovde& g) { std::vector h; for (u32 i = 1;; ++i) { geometri::YuzBilgi yb; if (!g.yuzBilgi(i, yb, /*alanHesapla*/ false)) break; h.push_back((int)i); } return h; } void PartDesignSistemi::sonucYaz(Feature& feat, geometri::BrepGovde sonuc, bool ekler, const MeshVeri* hazirVeri, const KenarVeri* hazirKenar, const NoktaVeri* hazirNokta) { // GİRDİ düğümü (zincirde önceki aktif feature'ın sonucu; zincir başında hedef): per-yüz // RENK MİRASI için. Sonuç YENİ düğüme yazıldığından renkler kendiliğinden taşınmaz — // brep renkleri (STEP) çoğu geometri op'unda renkPropagasyonu ile gelir ama gelmeyen yol // + kullanıcının yüz düzenlemeleri (yuzOz) burada, provenansKaydet'in az önce yazdığı // KESİN geri-haritayla taşınır ("fillet sonrası tek renk" kök çözümü). // Hedef-öncelikli (hedefDunyaBrep ile TUTARLI): çok gövdeli belgede renk/yuzOz mirası // yanlış gövdenin zincirinden gelmesin. Dugum* girdiD = sahne_.bul(feat.hedefDugum()); // Fallback ölçütü hedefDunyaBrep ile TUTARLI + miras-odaklı: hedef düğüm brep'siz/geçersiz // VEYA taşıyacak rengi-yuzOz'u YOKSA (log kanıtı: hedef kimi koşuda adsız/renksiz ara // düğüme işaret ediyor — worker girdisi renkliyken sonucYaz mirası kopuyordu) önceki // aktif feature'ın sonucuna düşülür. Miras taşıyabilen hedef aynen kullanılır (birebir). if (!girdiD || !girdiD->brep || !girdiD->brep->gecerliMi() || (!girdiD->brep->yuzRenkliMi() && girdiD->yuzOz.empty())) { if (Feature* onc = oncekiAktifFeature(feat)) { Dugum* d2 = sahne_.bul(onc->sonucDugum()); if (d2 && d2->brep && d2->brep->gecerliMi()) girdiD = d2; } } const auto& geri = feat.yuzGeriHarita(); const bool sonucRenkliIlk = sonuc.yuzRenkliMi(); // [renk] teşhisi: sigorta öncesi durum if (girdiD && !geri.empty() && !sonuc.yuzRenkliMi() && girdiD->brep && girdiD->brep->yuzRenkliMi()) { // Kaçak sigortası: geometri katmanı rengi düşürdüyse geri-haritayla yeniden kur // (eşlenmeyen/yeni yüzler girdi düğümünün taban rengine düşer). const auto& gr = girdiD->brep->yuzRenkleri(); std::vector renkler(geri.size(), girdiD->renk); for (std::size_t j = 0; j < geri.size(); ++j) if (geri[j] > 0 && (std::size_t)geri[j] <= gr.size()) renkler[j] = gr[(std::size_t)geri[j] - 1]; sonuc.yuzRenkleriAta(std::move(renkler)); } gunluk::g_teshisAdim = "PD:sonucYaz:dugumEkle"; Dugum* d = sahne_.bul(feat.sonucDugum()); if (!d) { Dugum& yeni = sahne_.ekle(DugumTip::Ithal); yeni.ad = feat.ad().empty() ? feat.tip() : feat.ad(); // Gövde taban rengi girdiden miras (tek-renk gövdede de doğru renk; girdi yoksa eski sabit). yeni.renk = girdiD ? girdiD->renk : (ekler ? Vec3f{0.62f, 0.72f, 0.85f} : Vec3f{0.85f, 0.68f, 0.55f}); feat.sonucDugumAyarla(yeni.kimlik); // Sonuç katısı FEATURE altına nest (CATIA: feature katıyı temsil eder, sonuç düğümü // feature genişletilince görünür — ağaç kök seviyesini kirletmez, 3D'de yine çizilir). sahne_.ustAyarla(yeni.kimlik, feat.kimlik()); d = ¥i; } // Kullanıcı yüz düzenlemeleri (yuzOz: renk/alfa/gizleme) girdi düğümünden KESİN remap ile // taşınır — mesh pişirme (cpuMeshKopyala) yuzOz'u okuduğundan yenidenInsa'dan ÖNCE. if (girdiD && girdiD != d && !geri.empty() && !girdiD->yuzOz.empty()) { std::unordered_map yeniOz; for (std::size_t j = 0; j < geri.size(); ++j) if (geri[j] > 0) { const auto it = girdiD->yuzOz.find((u32)geri[j]); if (it != girdiD->yuzOz.end()) yeniOz[(u32)(j + 1)] = it->second; } if (!yeniOz.empty()) d->yuzOz = std::move(yeniOz); } gunluk::g_teshisAdim = "PD:sonucYaz:brepAta"; d->donusum = Donusumd{}; // sonuc zaten dunya-uzayi d->brep = std::make_shared(std::move(sonuc)); // [renk] zincir teşhisi: hangi halka renk düşürüyor — kullanıcı koşusunda logdan okunur. if (girdiD) { std::size_t geriDolu = 0; for (int v : geri) if (v > 0) ++geriDolu; gunluk::bilgi("[renk] {} '{}'(k{},hedef k{})→'{}': girdi brep {} yuzOz {} | geri {}/{} | " "sonuç brep {}{} yuzOz {}", feat.tip(), girdiD->ad, girdiD->kimlik, feat.hedefDugum(), d->ad, girdiD->brep && girdiD->brep->yuzRenkliMi() ? "RENKLİ" : "renksiz", girdiD->yuzOz.size(), geriDolu, geri.size(), d->brep->yuzRenkliMi() ? "RENKLİ" : "renksiz", (!sonucRenkliIlk && d->brep->yuzRenkliMi()) ? "(sigorta)" : "", d->yuzOz.size()); } if (hazirVeri && hazirKenar && hazirNokta && !hazirVeri->vertexler.empty()) { // Async apply: mesh WORKER'da pişti → yalnız GPU'ya yükle (~25ms) + kaba LOD arka planda. d->yenidenInsaGerek = false; sahne_.tamponlariYukle(*d, *hazirVeri, *hazirKenar, *hazirNokta); d->sonMeshBrep = d->brep.get(); // GÜVENLİK AĞI: worker mesh'i brep renkleri OLMADAN pişmiş olabilir (sigorta yukarıda // sonradan renklendirdi) — vertexTampon'u brep taban + yuzOz ile YENİDEN kur, yoksa // gövde griye düşer (cpuRenkBaz tamponlariYukle'de brep'ten doğru dolduruldu). if (!sonucRenkliIlk && d->brep->yuzRenkliMi()) sahne_.ogeRenkYenile(*d); gunluk::g_teshisAdim = "PD:sonucYaz:kabaMeshUret"; sahne_.kabaMeshUret(*d, /*zorlaArkaplan*/ true); // pompa yolu main'i asla bloklamasın } else if (meshErtele_) { // Zincir modunda mesh ERTELENIR (gorunuruMesh yalnız görünür sonucu tessellate eder). d->yenidenInsaGerek = false; // bekleyenleriYenidenInsa da dokunmasın — gorunuruMesh yönetir } else { gunluk::g_teshisAdim = "PD:sonucYaz:sahneYenidenInsa"; sahne_.yenidenInsa(*d, girdiD && girdiD->brep ? girdiD->brep.get() : nullptr); } // Cikarma feature'i: hedef gorunurlugunu kapat (sonuc onun yerine gecer). if (!ekler) if (Dugum* hedef = sahne_.bul(feat.hedefDugum())) hedef->gorunur = false; cekirdek_.olaylar().yayinla(FeatureHesaplandi{feat.kimlik()}); } void PartDesignSistemi::hedeftenCikar(Feature& feat, geometri::BrepGovde araci) { if (!araci.gecerliMi()) { feat.hataAyarla("Kesme aracı oluşturulamadı"); return; } geometri::BrepGovde hedefDunya; gunluk::g_teshisAdim = "PD:hedeftenCikar:oncekiKati"; if (!hedefDunyaBrep(feat, hedefDunya)) return; gunluk::g_teshisAdim = "PD:hedeftenCikar:cikarma"; geometri::BrepGovde sonuc = geometri::cikarma(hedefDunya, araci); if (!sonuc.gecerliMi()) { feat.hataAyarla("Boolean çıkarma başarısız"); return; } gunluk::g_teshisAdim = "PD:hedeftenCikar:sonucYaz"; provenansKaydet(feat, hedefDunya, sonuc); // kesim yüzleri bu feature'ın (delik/cep re-edit) sonucYaz(feat, std::move(sonuc), /*ekler*/ false); } void PartDesignSistemi::yenidenInsa(Feature& feat) { feat.hataAyarla(""); const std::string& tip = feat.tip(); // ---- Hole: sketch profili GEREKMEZ (sayisal konum + normal) ---- if (tip == "hole") { geometri::HoleParam hp; auto& depo = sahne_.paramDepo(); const int t = (int)feat.p("tip"); // 0 Basit·1 Tapered·2 Counterbore·3 Countersink·4 Counterdrill switch (t) { case 1: hp.tip = geometri::DelikTip::Tapered; break; case 2: hp.tip = geometri::DelikTip::Counterbore; break; case 3: hp.tip = geometri::DelikTip::Countersink; break; case 4: hp.tip = geometri::DelikTip::Counterdrill; break; default: hp.tip = geometri::DelikTip::Basit; break; } hp.cap = feat.pEfektif("cap", depo, 5.0); hp.derinlik = feat.pEfektif("derinlik", depo, 10.0); hp.taperAci = feat.pEfektif("taperAci", depo, 5.0); hp.cbCap = feat.pEfektif("cbCap", depo, 10.0); hp.cbDerinlik = feat.pEfektif("cbDerinlik", depo, 4.0); hp.csCap = feat.pEfektif("csCap", depo, 10.0); hp.csAci = feat.pEfektif("csAci", depo, 90.0); // Bottom (Flat/V-Bottom): dipTip==1 → matkap ucu V-koni + dipAci. hp.vDip = (int)feat.p("dipTip") == 1; hp.vDipAci = feat.pEfektif("dipAci", depo, 118.0); // Dişli: tap-drill çapını nominal çap olarak kullan (modellenmez, gerçek delik). if (feat.p("disli") > 0.5) hp.cap = disliBilgisi((int)feat.p("disliStd")).tapDrill; Vec3d merkez{feat.pEfektif("konumX", depo), feat.pEfektif("konumY", depo), feat.pEfektif("konumZ", depo)}; // Delme ekseni: yüzeyden seçilen normal (eksenX/Y/Z) varsa onu kullan // (delik -normal yönünde ilerler). Yoksa varsayilan ∓Z. tersYon ekseni çevirir. Vec3d eksen{feat.pEfektif("eksenX", depo), feat.pEfektif("eksenY", depo), feat.pEfektif("eksenZ", depo)}; const f64 eLen = std::sqrt(eksen.x * eksen.x + eksen.y * eksen.y + eksen.z * eksen.z); Vec3d normal; if (eLen > 1e-6) normal = {eksen.x / eLen, eksen.y / eLen, eksen.z / eLen}; // yüzeyden gelen yön else normal = {0, 0, -1}; // varsayilan aşağı del if (feat.p("tersYon") > 0.5) normal = {-normal.x, -normal.y, -normal.z}; // ---- Extension (CATIA): derinliği uzatma tipine göre çöz ---- const int uz = (int)feat.p("uzatmaTip"); if (uz == 1 || uz == 2) { // Bir Sonrakine / Sonuncuya → gövdeyi tam delip geç (through) geometri::BrepGovde hd; if (hedefDunyaBrep(feat, hd)) { const Vec3d b = geometri::sinirBoyutu(hd); const f64 diag = std::sqrt(b.x * b.x + b.y * b.y + b.z * b.z); if (diag > 1e-6) hp.derinlik = diag * 1.1 + 1.0; // AABB köşegeni → garantili geçiş } } else if (uz == 3 || uz == 4) { // Düzleme / Yüzeye → seçilen hedefNokta mesafesi if (feat.p("hedefNoktaVar") > 0.5) { const Vec3d h{feat.p("hedefNoktax"), feat.p("hedefNoktay"), feat.p("hedefNoktaz")}; const f64 d = (h.x - merkez.x) * normal.x + (h.y - merkez.y) * normal.y + (h.z - merkez.z) * normal.z; if (std::abs(d) > 1e-6) hp.derinlik = std::abs(d); // eksen üzeri izdüşüm mesafesi } } if (hp.cap <= 1e-6 || hp.derinlik <= 1e-6) { feat.hataAyarla("Çap ve derinlik pozitif olmalı"); return; } hedeftenCikar(feat, geometri::delikAraci(merkez, normal, hp)); return; } // ---- Fillet / Chamfer: sketch GEREKMEZ; hedef katinin secili kenarlarini isler ---- if (tip == "fillet" || tip == "chamfer") { geometri::BrepGovde hedefDunya; if (!hedefDunyaBrep(feat, hedefDunya)) return; // hedef kati yok → hata dolu // Kenar ordinalleri: kenarSayi + kenar0..kenarN-1 (pencere 🎯 ile toplar). // Ordinaller kenarBilgi/pick ile ayni TopExp sirasi; hedef rigid donusum → sira korunur. std::vector kenarlar; const int n = (int)feat.p("kenarSayi"); for (int i = 0; i < n; ++i) { const u32 ord = (u32)feat.p("kenar" + std::to_string(i)); if (ord > 0) kenarlar.push_back(ord); } if (kenarlar.empty()) { feat.hataAyarla("En az bir kenar seçin (🎯 Kenar Ekle)"); return; } const bool fillet = (tip == "fillet"); const f64 deger = feat.pEfektif(fillet ? "yaricap" : "mesafe", sahne_.paramDepo(), 3.0); if (deger <= 1e-6) { feat.hataAyarla("Yarıçap/mesafe pozitif olmalı"); return; } std::vector remap; // KESİN OCCT history: bevel yüzleri 0, kırpılan komşular eski ordinalinde const auto fz0 = std::chrono::steady_clock::now(); geometri::BrepGovde sonuc = fillet ? geometri::yuvarlat(hedefDunya, kenarlar, deger, &remap) : geometri::pahla(hedefDunya, kenarlar, deger, &remap); const auto fz1 = std::chrono::steady_clock::now(); if (!sonuc.doluMu()) { feat.hataAyarla(fillet ? "Yuvarlatma başarısız (yarıçap büyük olabilir)" : "Pah başarısız (mesafe büyük olabilir)"); return; } const auto fz2 = std::chrono::steady_clock::now(); // hacim() da GProp (ölçüme dahil olsun) provenansKaydet(feat, hedefDunya, sonuc, std::move(remap)); const auto fz3 = std::chrono::steady_clock::now(); sonucYaz(feat, std::move(sonuc), /*ekler*/ false); // sonuc hedefin yerine gecer const auto ms = [](auto a, auto b) { return std::chrono::duration(b - a).count(); }; if (ms(fz0, std::chrono::steady_clock::now()) > 100.0) gunluk::bilgi("[perf] fillet/pah dal: op {:.0f}ms + hacim {:.0f}ms + prov {:.0f}ms + yaz {:.0f}ms", ms(fz0, fz1), ms(fz1, fz2), ms(fz2, fz3), ms(fz3, std::chrono::steady_clock::now())); return; } // ---- Yüz İt/Çek (Push/Pull parametrik): tek yüzü İŞARETLİ mesafe ile öteler (BRepFeat) ---- // DM "Yüz İt/Çek" gizmosu yuz0'ı bırakışta ekler. mesafe: + dışa çek, − içe it (yuzItCek // silindir/küre/koni radyal offset dahil işaretli çalışır). fillet/chamfer ile aynı provenance. if (tip == "yuz_itcek") { geometri::BrepGovde hedefDunya; if (!hedefDunyaBrep(feat, hedefDunya)) return; // hedef kati yok → hata dolu // Çok seçim: yuzSayi + yuz0..N (fillet'in kenarSayi kalıbı); eski dosyada yuzSayi=0 → yuz0. std::vector yuzler; const int yn = std::max((int)feat.p("yuzSayi"), 1); for (int i = 0; i < yn; ++i) { const u32 o = (u32)feat.p("yuz" + std::to_string(i)); if (o) yuzler.push_back(o); } if (yuzler.empty()) { feat.hataAyarla("Bir yüz seçin"); return; } const f64 mesafe = feat.pEfektif("mesafe", sahne_.paramDepo(), 0.0); if (std::abs(mesafe) <= 1e-6) { feat.hataAyarla("Mesafe sıfır olamaz"); return; } std::vector remap; // KESİN OCCT history: sonuç ordinali → hedef ordinali (düz-yüz yolu) const auto iz0 = std::chrono::steady_clock::now(); // blendKorur (async yolla birebir): teğet fillet sök→ötele→aynı R ile kur. const bool blendKorur = feat.p("blendKorur") > 0.5; const f64 tegetTol = feat.pEfektif("tegetTol", sahne_.paramDepo(), 1.0); geometri::BrepGovde sonuc = blendKorur ? geometri::yuzItCekBlendKorur(hedefDunya, yuzler, mesafe, tegetTol > 0 ? tegetTol : 1.0, &remap) : geometri::yuzItCekCoklu(hedefDunya, yuzler, mesafe, &remap); const auto iz1 = std::chrono::steady_clock::now(); if (!sonuc.doluMu()) { feat.hataAyarla("Yüz it/çek başarısız (mesafe geçersiz olabilir)"); return; } const auto iz2 = std::chrono::steady_clock::now(); // hacim() GProp da ölçülsün { const auto ms = [](auto a, auto b) { return std::chrono::duration(b - a).count(); }; if (ms(iz0, iz2) > 100.0) gunluk::bilgi("[perf] it/çek dal: op {:.0f}ms + hacim {:.0f}ms", ms(iz0, iz1), ms(iz1, iz2)); } // ÜRETİLEN yüz = YALNIZ ötelenen yüz(ler): prizma yan duvarları sahiplenilmez — // kullanıcı yan duvarı çekince ilk it/çek düzenlenmek yerine YENİ it/çek açılır // ("başka yüzü uzattığımda ilkini yapıyor" bug'ının kökü). Uzayan komşular kesin // history ile zaten eski sahibinde. provenansKaydet(feat, hedefDunya, sonuc, std::move(remap), yuzler, /*yalnizSahip*/ true); sonucYaz(feat, std::move(sonuc), /*ekler*/ false); // sonuc hedefin yerine gecer return; } // ---- Kenardan Eğ (parametrik): kenar0 menteşe + yuz0 açıyla eğilir (DraftAngle) ---- if (tip == "kenar_eg") { geometri::BrepGovde hedefDunya; if (!hedefDunyaBrep(feat, hedefDunya)) return; const u32 kenar = (u32)feat.p("kenar0"); const u32 yuz = (u32)feat.p("yuz0"); auto& depo = sahne_.paramDepo(); const f64 aci = feat.pEfektif("aci", depo, 0.0); if (!kenar || !yuz) { feat.hataAyarla("Menteşe kenarını ve eğilecek yüzü seçin"); return; } if (std::abs(aci) < 1e-6) { feat.hataAyarla("Açı sıfır olamaz"); return; } geometri::BrepGovde sonuc = geometri::kenardanYuzEg(hedefDunya, kenar, yuz, aci); if (!sonuc.doluMu()) { feat.hataAyarla("Kenardan eğ başarısız: DÜZ menteşe kenarı + DÜZ komşu yüz gerekli"); return; } provenansKaydet(feat, hedefDunya, sonuc, {}, {yuz}, /*yalnizSahip*/ true); sonucYaz(feat, std::move(sonuc), /*ekler*/ false); return; } // ---- Kenar Taşı (parametrik): çekilen kenar0, yuz0 düzleminde mesafe kadar dışarı ---- if (tip == "kenar_tasi") { geometri::BrepGovde hedefDunya; if (!hedefDunyaBrep(feat, hedefDunya)) return; const u32 kenar = (u32)feat.p("kenar0"); const u32 yuz = (u32)feat.p("yuz0"); auto& depo = sahne_.paramDepo(); const f64 mesafe = feat.pEfektif("mesafe", depo, 0.0); if (!kenar || !yuz) { feat.hataAyarla("Çekilecek kenarı ve uzayacak yüzü seçin"); return; } if (std::abs(mesafe) < 1e-9) { feat.hataAyarla("Mesafe sıfır olamaz"); return; } geometri::BrepGovde sonuc = geometri::kenarTasi(hedefDunya, kenar, yuz, mesafe); if (!sonuc.doluMu()) { feat.hataAyarla("Kenardan uzat başarısız: kenarın İKİ komşusu da DÜZ olmalı ve " "karşıda paralel menteşe kenarı bulunmalı"); return; } provenansKaydet(feat, hedefDunya, sonuc, {}, {yuz}, /*yalnizSahip*/ true); sonucYaz(feat, std::move(sonuc), /*ekler*/ false); return; } // ---- Yüz Taşı (Move Face parametrik): TEK düz yüzü keyfi vektörle öteler (BRepFeat_MakePrism) // yuz0 + tasiX/Y/Z (brep-yerel yön×mesafe). Eğik yönde komşu duvarlar esner (yuz_itcek'in // genellemesi). Yön yüze paralelse (normal bileşen 0) dejenere → hata. yuz_itcek ile aynı provenance. if (tip == "yuz_tasi") { geometri::BrepGovde hedefDunya; if (!hedefDunyaBrep(feat, hedefDunya)) return; // hedef kati yok → hata dolu // Çok seçim: yuzSayi + yuz0..N (yuz_itcek kalıbı); eski dosyada yuzSayi=0 → yuz0. std::vector yuzler; { const int yn = std::max((int)feat.p("yuzSayi"), 1); for (int i = 0; i < yn; ++i) if (const u32 o = (u32)feat.p("yuz" + std::to_string(i))) yuzler.push_back(o); } if (yuzler.empty()) { feat.hataAyarla("Bir yüz seçin"); return; } auto& depo = sahne_.paramDepo(); const Vec3d vec{feat.pEfektif("tasiX", depo, 0.0), feat.pEfektif("tasiY", depo, 0.0), feat.pEfektif("tasiZ", depo, 0.0)}; if (vec.uzunlukKare() <= 1e-12) { feat.hataAyarla("Taşıma vektörü sıfır olamaz"); return; } geometri::BrepGovde sonuc = geometri::yuzTasiCoklu(hedefDunya, yuzler, vec); if (!sonuc.doluMu()) { feat.hataAyarla("Yüz taşı başarısız (yön yüze paralel veya geometri uygun değil)"); return; } // Move-face history remap yok (yuzTasi düz-yüz BRepFeat) → geometrik eşleme; ötelenen yüz(ler) // sahiplenilir (yuz_itcek ile aynı ilke: yan duvarlar yeni feature değil). provenansKaydet(feat, hedefDunya, sonuc, {}, yuzler, /*yalnizSahip*/ true); sonucYaz(feat, std::move(sonuc), /*ekler*/ false); return; } // ---- Çıkıntı/Blok Kaydır (Move Region): kaynamış katıda çıkıntıyı taban düzlemi boyunca // RİJİT kaydırır (Splitter→ötele→Fuse; footprint kendiliğinden iyileşir). yuz0=çıkıntı üst // yüzü, tasiX/Y/Z=dünya öteleme, taban düzlemi verilmezse cikintiTabanBul ile OTOMATİK. if (tip == "cikinti_kaydir") { geometri::BrepGovde hedefDunya; if (!hedefDunyaBrep(feat, hedefDunya)) return; // hedef kati yok → hata dolu const u32 yuz = (u32)feat.p("yuz0"); if (yuz == 0) { feat.hataAyarla("Bir çıkıntı yüzü seçin"); return; } auto& depo = sahne_.paramDepo(); const Vec3d vec{feat.pEfektif("tasiX", depo, 0.0), feat.pEfektif("tasiY", depo, 0.0), feat.pEfektif("tasiZ", depo, 0.0)}; if (vec.uzunlukKare() <= 1e-12) { feat.hataAyarla("Kaydırma vektörü sıfır olamaz"); return; } Vec3d tNokta{feat.p("tabanX"), feat.p("tabanY"), feat.p("tabanZ")}; Vec3d tNormal{feat.p("tabanNX"), feat.p("tabanNY"), feat.p("tabanNZ")}; if (tNormal.uzunlukKare() <= 1e-9) { // taban verilmemiş → OTOMATİK bul (çıkıntı üst yüzünden) const geometri::CikintiTabanSonuc ts = geometri::cikintiTabanBul(hedefDunya, yuz); if (!ts.gecerli) { feat.hataAyarla("Taban düzlemi bulunamadı (düz üst yüz seçin)"); return; } tNokta = ts.nokta; tNormal = ts.normal; } geometri::YuzBilgi yb; Vec3d seed{0, 0, 0}; if (hedefDunya.yuzBilgi(yuz, yb)) seed = yb.merkez; // taşınacak parçayı seçmek için geometri::BrepGovde sonuc = geometri::cikintiKaydir(hedefDunya, tNokta, tNormal, vec, seed); if (!sonuc.doluMu()) { feat.hataAyarla("Blok kaydır başarısız (taban/çıkıntı ayrışmadı veya çıkıntı taban dışına taştı)"); return; } provenansKaydet(feat, hedefDunya, sonuc, {}, {}, /*yalnizSahip*/ false); // ordinal→geometrik eşleme sonucYaz(feat, std::move(sonuc), /*ekler*/ false); return; } // ---- Yüz Döndür (Rotate Face): çıkıntıyı taban düzlemi üstünde eksen etrafında RİJİT döndür; // rijit kapalı/başarısız → draft'a düş (nötr=taban). Gizmo döndürme yayının parametrik hâli. if (tip == "yuz_dondur") { geometri::BrepGovde hedefDunya; if (!hedefDunyaBrep(feat, hedefDunya)) return; // hedef kati yok → hata dolu const u32 yuz = (u32)feat.p("yuz0"); if (yuz == 0) { feat.hataAyarla("Bir çıkıntı yüzü seçin"); return; } auto& depo = sahne_.paramDepo(); const f64 aci = feat.pEfektif("aci", depo, 0.0); if (std::abs(aci) < 1e-6) { feat.hataAyarla("Açı sıfır olamaz"); return; } const Vec3d eksYon{feat.p("eksNX"), feat.p("eksNY"), feat.p("eksNZ")}; Vec3d tNokta{feat.p("tabanX"), feat.p("tabanY"), feat.p("tabanZ")}; Vec3d tNormal{feat.p("tabanNX"), feat.p("tabanNY"), feat.p("tabanNZ")}; if (tNormal.uzunlukKare() <= 1e-9) { // taban verilmemiş → OTOMATİK (çıkıntı üst yüzünden) const geometri::CikintiTabanSonuc ts = geometri::cikintiTabanBul(hedefDunya, yuz); if (!ts.gecerli) { feat.hataAyarla("Taban düzlemi bulunamadı (düz üst yüz seçin)"); return; } tNokta = ts.nokta; tNormal = ts.normal; } if (eksYon.uzunlukKare() <= 1e-9) { feat.hataAyarla("Döndürme ekseni tanımsız"); return; } geometri::YuzBilgi yb; Vec3d seed{0, 0, 0}; if (hedefDunya.yuzBilgi(yuz, yb)) seed = yb.merkez; // döndürülecek parçayı seçmek için geometri::BrepGovde sonuc; if (feat.p("rijit") > 0.5) sonuc = geometri::yuzDondur(hedefDunya, tNokta, tNormal, tNokta, eksYon, aci, seed); // Rijit kapalı VEYA başarısız (yatay eksende taban footprint kalkar) → draft: nötr=taban. if (!sonuc.doluMu()) sonuc = geometri::yuzDraft(hedefDunya, {yuz}, aci, tNormal, tNokta, tNormal, false); if (!sonuc.doluMu()) { feat.hataAyarla("Yüz döndür başarısız (açı çok büyük veya yüz uygun değil)"); return; } provenansKaydet(feat, hedefDunya, sonuc, {}, {}, /*yalnizSahip*/ false); // ordinal→geometrik eşleme sonucYaz(feat, std::move(sonuc), /*ekler*/ false); return; } // ---- Kesit / Split (Düzlemle Böl): hedef katıyı düzlemle böler, bir taraf kalır. // Düzlem kaynağı: 0=seçili düz yüz/düzlem (kesici*), 1/2/3 = Dünya XY/YZ/ZX. if (tip == "split") { geometri::BrepGovde hedefDunya; if (!hedefDunyaBrep(feat, hedefDunya)) return; // hedef kati yok → hata dolu Vec3d nokta{0, 0, 0}, normal{0, 0, 0}; if (!splitDuzlemCoz(feat, nokta, normal)) return; // hata feature'a yazıldı geometri::BrepGovde sonuc = geometri::duzlemleBol(hedefDunya, nokta, normal, feat.p("tersYon") > 0.5); if (!sonuc.doluMu()) { feat.hataAyarla("Kesit başarısız (düzlem gövdeyi kesmiyor veya tutulan parça yok)"); return; } provenansKaydet(feat, hedefDunya, sonuc, {}, {}, /*yalnizSahip*/ false); // ordinal→geometrik eşleme sonucYaz(feat, std::move(sonuc), /*ekler*/ false); return; } // ---- Fillet Varyantları: Variable / Face-Face / Tritangent ------------- // fillet_var: TEK kenar (kenar0) + r1/r2 değişken yarıçap. // fillet_ff : İKİ yüz (yuz0/yuz1) + yaricap → ortak kenar üzerinden fillet. // fillet_tri: 2 tut yüz (yuz0/yuz1) + 1 kaldır yüz (yuz2) → R otomatik. if (tip == "fillet_var" || tip == "fillet_ff" || tip == "fillet_tri") { geometri::BrepGovde hedefDunya; if (!hedefDunyaBrep(feat, hedefDunya)) return; // hedef kati yok → hata dolu auto& depo = sahne_.paramDepo(); geometri::BrepGovde sonuc; if (tip == "fillet_var") { // Tek kenar ordinali (kenar0). kenarSayi ön-doldurmada dolar; ilk kenarı kullan. const u32 kenar = (u32)feat.p("kenar0"); if (kenar == 0) { feat.hataAyarla("Bir kenar seçin (🎯 Kenar Ekle)"); return; } const f64 r1 = feat.pEfektif("r1", depo, 3.0); const f64 r2 = feat.pEfektif("r2", depo, 6.0); if (r1 <= 1e-6 || r2 <= 1e-6) { feat.hataAyarla("R1 ve R2 pozitif olmalı"); return; } const bool kubik = feat.p("varyasyon") > 0.5; // 0 Linear · 1 Cubic sonuc = geometri::yuvarlatDegisken(hedefDunya, kenar, r1, r2, kubik); if (!sonuc.doluMu()) { feat.hataAyarla("Değişken fillet başarısız (yarıçap büyük olabilir)"); return; } } else if (tip == "fillet_ff") { const u32 y1 = (u32)feat.p("yuz0"); const u32 y2 = (u32)feat.p("yuz1"); if (y1 == 0 || y2 == 0) { feat.hataAyarla("İki yüz seçin (🎯 Yüz Ekle)"); return; } const f64 yaricap = feat.pEfektif("yaricap", depo, 3.0); if (yaricap <= 1e-6) { feat.hataAyarla("Yarıçap pozitif olmalı"); return; } sonuc = geometri::yuvarlatYuzYuz(hedefDunya, y1, y2, yaricap); if (!sonuc.doluMu()) { feat.hataAyarla("Yüz-Yüz fillet başarısız (ortak kenar yok veya yarıçap büyük)"); return; } } else { // fillet_tri const u32 y1 = (u32)feat.p("yuz0"); const u32 y2 = (u32)feat.p("yuz1"); const u32 kald = (u32)feat.p("yuz2"); if (y1 == 0 || y2 == 0 || kald == 0) { feat.hataAyarla("3 yüz seçin: 2 tutulacak + 1 kaldırılacak (🎯 Yüz Ekle)"); return; } sonuc = geometri::yuvarlatTritangent(hedefDunya, y1, y2, kald); if (!sonuc.doluMu()) { feat.hataAyarla("Tritangent fillet başarısız (yüz seçimi/geometri uygun değil)"); return; } } provenansKaydet(feat, hedefDunya, sonuc); sonucYaz(feat, std::move(sonuc), /*ekler*/ false); // sonuc hedefin yerine gecer return; } // ---- Shell / Draft / Draft Reflect: sketch GEREKMEZ; hedef katinin secili YÜZlerini isler ---- if (tip == "shell" || tip == "draft" || tip == "draft_reflect" || tip == "remove_face") { geometri::BrepGovde hedefDunya; if (!hedefDunyaBrep(feat, hedefDunya)) return; // hedef kati yok → hata dolu // Yüz ordinalleri: yuzSayi + yuz0..yuzN-1 (pencere 🎯 ile toplar). Kenar deseninin // yüz ikizi; ordinaller kabuk/yuzDraft/yuzKaldir'ın TopExp FACE sirasi ile eslesir. std::vector yuzler; const int n = (int)feat.p("yuzSayi"); for (int i = 0; i < n; ++i) { const u32 ord = (u32)feat.p("yuz" + std::to_string(i)); if (ord > 0) yuzler.push_back(ord); } if (yuzler.empty()) { feat.hataAyarla("En az bir yüz seçin (🎯 Yüz Ekle)"); return; } geometri::BrepGovde sonuc; if (tip == "remove_face") { // Remove Face: seçili yüzleri sil + komşu uzat (katı korunur) sonuc = geometri::yuzKaldir(hedefDunya, yuzler); if (!sonuc.doluMu()) { feat.hataAyarla("Yüz kaldır başarısız (komşular kapanamadı — temel yüz olabilir)"); return; } } else if (tip == "shell") { const f64 kalinlik = feat.pEfektif("kalinlik", sahne_.paramDepo(), 2.0); if (kalinlik <= 1e-6) { feat.hataAyarla("Kalınlık pozitif olmalı"); return; } sonuc = geometri::kabuk(hedefDunya, yuzler, kalinlik); if (!sonuc.doluMu()) { feat.hataAyarla("Kabuk başarısız (kalınlık büyük veya geometri uygun değil)"); return; } } else { // draft / draft_reflect const bool reflectHat = (tip == "draft_reflect"); // yansıma hattı otomatik nötr const f64 aci = feat.pEfektif("aci", sahne_.paramDepo(), 5.0); if (std::abs(aci) < 1e-6) { feat.hataAyarla("Açı sıfırdan farklı olmalı"); return; } // Pulling Direction (çekme yönü): Dünya X/Y/Z veya Seçili Kenar (sahneden — dünya yönü). Vec3d yon{0, 0, 1}; switch ((int)feat.p("yon")) { case 0: yon = {1, 0, 0}; break; case 1: yon = {0, 1, 0}; break; case 3: { // Seçili Kenar → kenar dünya yönü (geçersizse Dünya Z'ye düş). const u32 kg = (u32)feat.p("cekmeKenarG"); const u32 ka = (u32)feat.p("cekmeKenarA"); Dugum* dk = sahne_.bul(kg); geometri::KenarBilgi kb; if (dk && dk->brep && dk->brep->kenarBilgi(ka, kb) && kb.dogruMu) yon = dk->donusum.yonelim.dondur(kb.yon).birim(); else yon = {0, 0, 1}; break; } default: yon = {0, 0, 1}; break; } // Neutral Element (nötr yüz) → notralYuz; yoksa Parting Element (ayırma düzlemi) → // ayirmaDuzlem nötr olarak kullanılır (MVP: gerçek iki-yönlü parting TODO). İkisi de // seçilmezse geometri sınır-kutu merkezini kullanır (notralNormal=0 → geriye-uyum). Vec3d nNokta{}, nNormal{}; if (!reflectHat) { // reflect modunda nötr otomatik (yansıma hattı) → param yok sayılır if (feat.p("notralYuzVar") > 0.5) { nNokta = {feat.p("notralYuzx"), feat.p("notralYuzy"), feat.p("notralYuzz")}; nNormal = {feat.p("notralYuzNx"), feat.p("notralYuzNy"), feat.p("notralYuzNz")}; } else if (feat.p("ayirmaDuzlemVar") > 0.5) { nNokta = {feat.p("ayirmaDuzlemx"), feat.p("ayirmaDuzlemy"), feat.p("ayirmaDuzlemz")}; nNormal = {feat.p("ayirmaDuzlemNx"), feat.p("ayirmaDuzlemNy"), feat.p("ayirmaDuzlemNz")}; } } sonuc = geometri::yuzDraft(hedefDunya, yuzler, aci, yon, nNokta, nNormal, reflectHat); if (!sonuc.doluMu()) { feat.hataAyarla(reflectHat ? "Yansıma hattı draft başarısız (yüz kavisli/geometriye uygun mu?)" : "Draft başarısız (açı/yön/nötr geometriye uygun değil)"); return; } } provenansKaydet(feat, hedefDunya, sonuc); sonucYaz(feat, std::move(sonuc), /*ekler*/ false); // sonuc hedefin yerine gecer return; } // ---- Sew Surface / Replace Face: hedef katı + kaynak yüzeyle böl, bir tarafı tut ---- // İki gövde semantiği: A = hedef katı (hedefDunyaBrep), yüzey = kaynakDugum param düğümü // (komut yazar). geometri::yuzeyDik katıyı yüzeyle böler; "Kesim Yönü" (ok) hangi tarafın // tutulacağını belirler. Replace Face: yön = seçili eski yüzün DIŞ normali (o taraf atılır). // Sonuç hedefin yerine geçer (ekler=false) + kaynak yüzey düğümü gizlenir. if (tip == "sew_surface" || tip == "replace_face") { geometri::BrepGovde hedefDunya; if (!hedefDunyaBrep(feat, hedefDunya)) return; // hedef katı yok → hata dolu const u32 ky = (u32)feat.p("kaynakDugum"); Dugum* dy = sahne_.bul(ky); if (!dy || !dy->brep || !dy->brep->gecerliMi()) { feat.hataAyarla("Kaynak yüzey gövdesi seçin (açık yüzey)"); return; } geometri::BrepGovde yuzeyW = geometri::donusumUygula(*dy->brep, dy->donusum); const bool ters = feat.p("tersYon") > 0.5; // Kesim yönü (ok): tutulacak taraf. Yüzey normali için kaynak yüzeyin 1. yüzü kullanılır. const auto yuzeyNormali = [&]() -> Vec3d { geometri::YuzBilgi yb; if (yuzeyW.yuzBilgi(1, yb)) { if (yb.duzlemMi && (yb.normal.x || yb.normal.y || yb.normal.z)) return yb.normal.birim(); Vec3d n; if (yuzeyW.yuzNoktaNormali(1, yb.merkez, n) && (n.x || n.y || n.z)) return n.birim(); } return {0, 0, 1}; }; Vec3d yon{0, 0, 1}; if (tip == "sew_surface") { switch ((int)feat.p("yon")) { case 1: yon = {1, 0, 0}; break; case 2: yon = {0, 1, 0}; break; case 3: yon = {0, 0, 1}; break; case 4: { // Seçili Kenar → kenar dünya yönü (geçersizse yüzey normali) const u32 kg = (u32)feat.p("yonKenarG"); const u32 ka = (u32)feat.p("yonKenarA"); Dugum* dk = sahne_.bul(kg); geometri::KenarBilgi kb; if (dk && dk->brep && dk->brep->kenarBilgi(ka, kb) && kb.dogruMu) yon = dk->donusum.yonelim.dondur(kb.yon).birim(); else yon = yuzeyNormali(); break; } default: yon = yuzeyNormali(); break; // Yüzey Normali } } else { // replace_face: yön = seçili eski yüzün DIŞ normali → o taraf atılır (keep opposite) const u32 eskiYuz = (u32)feat.p("yuz0"); geometri::YuzBilgi yb; Vec3d disNormal{}; if (eskiYuz > 0 && hedefDunya.yuzBilgi(eskiYuz, yb)) { if (yb.duzlemMi && (yb.normal.x || yb.normal.y || yb.normal.z)) disNormal = yb.normal.birim(); else { Vec3d n; if (hedefDunya.yuzNoktaNormali(eskiYuz, yb.merkez, n)) disNormal = n.birim(); } } if (disNormal.x || disNormal.y || disNormal.z) yon = {-disNormal.x, -disNormal.y, -disNormal.z}; // iç tarafı tut → eski yüz bölgesi kapanır else yon = yuzeyNormali(); } geometri::BrepGovde sonuc = geometri::yuzeyDik(hedefDunya, yuzeyW, yon, ters); if (!sonuc.doluMu()) { feat.hataAyarla(tip == "sew_surface" ? "Yüzey dikme başarısız (yüzey katıyı tam kesmeli)" : "Yüz değiştirme başarısız (yeni yüzey katıyı tam kesmeli)"); return; } provenansKaydet(feat, hedefDunya, sonuc); sonucYaz(feat, std::move(sonuc), /*ekler*/ false); // sonuc hedefin yerine gecer if (Dugum* dy2 = sahne_.bul(ky)) dy2->gorunur = false; // kaynak yüzey gizlenir return; } // ---- Thickness (Kalınlık): sketch/kenar/yüz GEREKMEZ; hedef katıyı uniform offset'ler ---- // Pozitif kalınlık dışa büyütür, negatif içe küçültür (kabuk DEĞİL — içi dolu). Sonuç // hedefin yerine geçer (ekler=false → hedef gizlenir). Shell kalıbı, ama yüz seçimsiz. if (tip == "thickness") { geometri::BrepGovde hedefDunya; if (!hedefDunyaBrep(feat, hedefDunya)) return; // hedef katı yok → hata dolu const f64 mesafe = feat.pEfektif("kalinlik", sahne_.paramDepo(), 2.0); if (std::abs(mesafe) < 1e-6) { feat.hataAyarla("Kalınlık sıfırdan farklı olmalı"); return; } geometri::BrepGovde sonuc = geometri::kalinlastir(hedefDunya, mesafe); if (!sonuc.doluMu()) { feat.hataAyarla("Kalınlık başarısız (mesafe geometriye uygun değil)"); return; } provenansKaydet(feat, hedefDunya, sonuc); sonucYaz(feat, std::move(sonuc), /*ekler*/ false); // sonuc hedefin yerine gecer return; } // ---- Boolean Bodies (Add/Remove/Intersect): 2 katı gövde arası boolean → tek gövde ---- // A = hedefDugum (1. seçili gövde, chain DEĞİL — iki bağımsız gövde semantiği). B = // "ikinciGovde" param düğümü. birlesim/cikarma/kesisim OCCT ops yeniden kullanılır. Sonuç // A'nın yerine geçer (ekler=false → hedef gizli) + B düğümü de gizlenir (sonuç onu kapsar). if (tip == "bool_add" || tip == "bool_remove" || tip == "bool_intersect") { Dugum* da = sahne_.bul(feat.hedefDugum()); if (!da || !da->brep || !da->brep->gecerliMi()) { feat.hataAyarla("1. gövde (hedef) geçersiz"); return; } const u32 ikinciK = (u32)feat.p("ikinciGovde"); Dugum* db = sahne_.bul(ikinciK); if (!db || !db->brep || !db->brep->gecerliMi()) { feat.hataAyarla("2. gövde seçin (🎯)"); return; } geometri::BrepGovde a = geometri::donusumUygula(*da->brep, da->donusum); geometri::BrepGovde b = geometri::donusumUygula(*db->brep, db->donusum); geometri::BrepGovde sonuc = tip == "bool_add" ? geometri::birlesim(a, b) : tip == "bool_remove" ? geometri::cikarma(a, b) : geometri::kesisim(a, b); if (!sonuc.doluMu()) { feat.hataAyarla("Boolean işlem başarısız (gövdeler kesişmiyor olabilir)"); return; } provenansKaydet(feat, a, sonuc); // A'da eşleşmeyen yüzler (B'den gelen/kesim) bu feature'ın sonucYaz(feat, std::move(sonuc), /*ekler*/ false); // sonuc A'nın (hedef) yerine geçer if (Dugum* db2 = sahne_.bul(ikinciK)) db2->gorunur = false; // 2. gövde de gizlenir return; } // ---- Union Trim (Birleşim-Kırp): Fuse(A,B) + (opsiyonel) sonuç parçalarından seçileni at ---- // A = hedefDugum, B = ikinciGovde. MVP: birleşim; yuzSayi>0 ise fused sonucun ayrık katı // parçalarından, seçili yüzü içeren lump'ı geometri::lumpAyikla(tut=false) ile atar. Faces- // to-keep tam kırpması (CATIA) → TODO. Yüz ordinalleri fused SONUÇ üzerinde seçilir. if (tip == "union_trim") { Dugum* da = sahne_.bul(feat.hedefDugum()); if (!da || !da->brep || !da->brep->gecerliMi()) { feat.hataAyarla("1. gövde (hedef) geçersiz"); return; } const u32 ikinciK = (u32)feat.p("ikinciGovde"); Dugum* db = sahne_.bul(ikinciK); if (!db || !db->brep || !db->brep->gecerliMi()) { feat.hataAyarla("2. gövde seçin (🎯)"); return; } geometri::BrepGovde a = geometri::donusumUygula(*da->brep, da->donusum); geometri::BrepGovde b = geometri::donusumUygula(*db->brep, db->donusum); geometri::BrepGovde sonuc = geometri::birlesim(a, b); if (!sonuc.doluMu()) { feat.hataAyarla("Birleşim başarısız (geometri geçersiz)"); return; } // Kaldırılacak parça yüzleri: fused sonuç üzerinde seçili (yuzSayi + yuz0..N; pencere 🎯). std::vector yuzler; const int ny = (int)feat.p("yuzSayi"); for (int i = 0; i < ny; ++i) { const u32 ord = (u32)feat.p("yuz" + std::to_string(i)); if (ord > 0) yuzler.push_back(ord); } if (!yuzler.empty()) { geometri::BrepGovde kirp = geometri::lumpAyikla(sonuc, yuzler, /*tut*/ false); if (kirp.gecerliMi() && kirp.hacim() > 1e-9) sonuc = std::move(kirp); } provenansKaydet(feat, a, sonuc); sonucYaz(feat, std::move(sonuc), /*ekler*/ false); // sonuc A'nın (hedef) yerine geçer if (Dugum* db2 = sahne_.bul(ikinciK)) db2->gorunur = false; // 2. gövde de gizlenir return; } // ---- Remove Lump (Parça Ayıkla): tek gövdenin ayrık katı parçalarından tut/at ---- // Hedef katı disconnected solids içeriyorsa (ör. import/boolean sonrası), seçili yüzün ait // olduğu parçayı ayıklar. mod: 0 Seçileni At · 1 Sadece Seçileni Tut. Sonuç hedefin yerine geçer. if (tip == "remove_lump") { geometri::BrepGovde hedefDunya; if (!hedefDunyaBrep(feat, hedefDunya)) return; // hedef katı yok → hata dolu std::vector yuzler; const int ny = (int)feat.p("yuzSayi"); for (int i = 0; i < ny; ++i) { const u32 ord = (u32)feat.p("yuz" + std::to_string(i)); if (ord > 0) yuzler.push_back(ord); } if (yuzler.empty()) { feat.hataAyarla("En az bir parça yüzü seçin (🎯 Yüz Ekle)"); return; } const bool tut = feat.p("mod") > 0.5; // 0 At · 1 Tut geometri::BrepGovde sonuc = geometri::lumpAyikla(hedefDunya, yuzler, tut); if (!sonuc.doluMu()) { feat.hataAyarla("Parça ayıklanamadı (gövde tek parça veya seçim geçersiz)"); return; } provenansKaydet(feat, hedefDunya, sonuc); sonucYaz(feat, std::move(sonuc), /*ekler*/ false); // sonuc hedefin yerine geçer return; } // ---- Pattern (Dik/Dairesel/Kullanıcı Dizi) & Mirror: sketch/kenar/yüz GEREKMEZ; hedef katıyı çoğaltır ---- // Sonuç = tüm kopyaların (orijinal DAHİL) birleşimi → hedefin yerine geçer (ekler=false → hedef // gizlenir; render'da çift geometri/z-fighting olmaz). Kenar/yüz seçimi yok, sadece sayısal param. if (tip == "pattern_dik" || tip == "pattern_dairesel" || tip == "pattern_user" || tip == "mirror") { geometri::BrepGovde hedefDunya; if (!hedefDunyaBrep(feat, hedefDunya)) return; // hedef katı yok → hata dolu auto& depo = sahne_.paramDepo(); const auto dunyaEksen = [](int idx) -> Vec3d { switch (idx) { case 0: return {1, 0, 0}; case 1: return {0, 1, 0}; default: return {0, 0, 1}; } }; // Yön/eksen referansı: idx 0..2 Dünya X/Y/Z; 3 Seçili Kenar (SahneEksen G/A → dünya yönü). const auto yonCoz = [&](const std::string& secK, const std::string& kenarK, f64 varsayilanIdx) -> Vec3d { const int s = (int)feat.p(secK, varsayilanIdx); if (s == 3) { const u32 kg = (u32)feat.p(kenarK + "G"); const u32 ka = (u32)feat.p(kenarK + "A"); Dugum* dk = sahne_.bul(kg); geometri::KenarBilgi kb; if (dk && dk->brep && dk->brep->kenarBilgi(ka, kb) && kb.dogruMu) return dk->donusum.yonelim.dondur(kb.yon).birim(); } return dunyaEksen(s); }; geometri::BrepGovde sonuc; if (tip == "pattern_dik") { // Her yön için param modu (mod
): 0 Adet&Aralık · 1 Adet&Uzunluk · 2 Aralık&Uzunluk.
      const auto dikCoz = [&](const std::string& pre, f64 adetVars, f64& adimO, u32& adetO) {
        const int mod = (int)feat.p("mod" + pre);
        f64 adim = feat.pEfektif("adim" + pre, depo, 20.0);
        u32 adet = (u32)std::max(1.0, feat.p("adet" + pre, adetVars));
        const f64 uzun = feat.pEfektif("uzunluk" + pre, depo, 40.0);
        if (mod == 1) { if (adet > 1) adim = uzun / (f64)(adet - 1); }        // Adet&Uzunluk
        else if (mod == 2) { if (adim > 1e-9) adet = (u32)std::floor(uzun / adim + 1e-6) + 1; }
        adimO = adim; adetO = std::max(1, adet);
      };
      Vec3d y1 = yonCoz("yon1", "yon1Kenar", 0.0);
      Vec3d y2 = yonCoz("yon2", "yon2Kenar", 1.0);
      if (feat.p("ters1") > 0.5) y1 = {-y1.x, -y1.y, -y1.z};
      if (feat.p("ters2") > 0.5) y2 = {-y2.x, -y2.y, -y2.z};
      f64 a1, a2; u32 n1, n2;
      dikCoz("1", 3.0, a1, n1);
      dikCoz("2", 1.0, a2, n2);
      if (n1 * n2 <= 1) { feat.hataAyarla("Dizi için toplam adet > 1 olmalı"); return; }
      sonuc = geometri::diziDik(hedefDunya, y1, a1, n1, y2, a2, n2);
      if (!sonuc.gecerliMi()) { feat.hataAyarla("Dik dizi başarısız (fuse geçersiz)"); return; }
    } else if (tip == "pattern_dairesel") {
      // Mod: 0 Adet&Toplam Açı · 1 Adet&Açısal Aralık · 2 Açısal Aralık&Toplam Açı · 3 Tam Taç.
      Vec3d eks = yonCoz("eksen", "eksenKenar", 2.0);
      if (feat.p("tersYon") > 0.5) eks = {-eks.x, -eks.y, -eks.z};
      const int mod = (int)feat.p("mod");
      f64 aci = feat.pEfektif("aci", depo, 360.0);
      u32 adet = (u32)std::max(1.0, feat.p("adet", 4.0));
      const f64 aralik = feat.pEfektif("aciAralik", depo, 90.0);
      if (mod == 1) aci = aralik * (f64)adet;                                   // Adet&Açısal Aralık
      else if (mod == 2) { if (aralik > 1e-9) adet = (u32)std::max(1.0, std::floor(aci / aralik + 1e-6)); }
      else if (mod == 3) aci = 360.0;                                           // Tam Taç
      const bool tac = feat.p("tac") > 0.5;
      if (adet <= 1 && !tac) { feat.hataAyarla("Dairesel dizi için adet > 1 olmalı"); return; }
      if (tac) {
        // Crown Definition: radyal iç içe halkalar. Radyal referans = eksene ~dik dünya ekseni.
        const u32 halkaAdet = (u32)std::max(1.0, feat.p("halkaAdet", 2.0));
        const f64 halkaKal = feat.pEfektif("halkaKalinlik", depo, 20.0);
        const Vec3d radyal = std::abs(eks.z) > 0.5 ? Vec3d{1, 0, 0}
                             : std::abs(eks.x) > 0.5 ? Vec3d{0, 1, 0} : Vec3d{0, 0, 1};
        bool ilk = true;
        for (u32 r = 0; r < halkaAdet; ++r) {
          Donusumd rt;
          rt.konum = radyal * (halkaKal * (f64)r);  // r. halka radyal ofset
          geometri::BrepGovde halkaHedef = geometri::donusumUygula(hedefDunya, rt);
          geometri::BrepGovde halka = geometri::diziDairesel(halkaHedef, {0, 0, 0}, eks, aci, adet);
          if (!halka.gecerliMi()) continue;
          if (ilk) { sonuc = std::move(halka); ilk = false; continue; }
          geometri::BrepGovde b = geometri::birlesim(sonuc, halka);
          if (b.gecerliMi()) sonuc = std::move(b);
        }
        if (!sonuc.gecerliMi()) { feat.hataAyarla("Taç dizisi başarısız (fuse geçersiz)"); return; }
      } else {
        sonuc = geometri::diziDairesel(hedefDunya, {0, 0, 0}, eks, aci, adet);
        if (!sonuc.gecerliMi()) { feat.hataAyarla("Dairesel dizi başarısız (fuse geçersiz)"); return; }
      }
    } else if (tip == "pattern_user") {
      // Konum sketch'indeki NOKTA'lar → dünya konumu. İlk nokta = tutamak (anchor); her nokta için
      // kopya = hedef ötelenmiş (nokta − ilkNokta). ofset(ilk)=0 → orijinali içerir.
      const u32 konumSk = (u32)feat.p("konumSketch");
      std::vector noktalar;
      if (konumSk == 0 || !sketchNoktalari(konumSk, noktalar)) {
        feat.hataAyarla("Konum sketch'i seçin (nokta içermeli)");
        return;
      }
      if (noktalar.size() < 2) { feat.hataAyarla("Konum sketch'inde en az 2 nokta olmalı"); return; }
      const Vec3d tutamak = noktalar.front();
      std::vector ofsetler;
      ofsetler.reserve(noktalar.size());
      for (const Vec3d& p : noktalar) ofsetler.push_back(p - tutamak);
      sonuc = geometri::diziKonum(hedefDunya, ofsetler);
      if (!sonuc.gecerliMi()) { feat.hataAyarla("Kullanıcı dizi başarısız (fuse geçersiz)"); return; }
    } else {  // mirror: seçili düzleme göre aynala (düzlem normali orijinden geçer)
      Vec3d normal;
      switch ((int)feat.p("duzlem")) {
        case 1: normal = {1, 0, 0}; break;  // YZ düzlemi (X=0)
        case 2: normal = {0, 1, 0}; break;  // ZX düzlemi (Y=0)
        default: normal = {0, 0, 1}; break; // XY düzlemi (Z=0)
      }
      sonuc = geometri::aynala(hedefDunya, {0, 0, 0}, normal);
      if (!sonuc.gecerliMi()) { feat.hataAyarla("Aynalama başarısız"); return; }
    }
    provenansKaydet(feat, hedefDunya, sonuc);  // kopya/ayna yüzleri bu feature'ın
    sonucYaz(feat, std::move(sonuc), /*ekler*/ false);  // sonuç orijinali içerir → hedefi gizle
    return;
  }

  // ---- Transformation (Öteleme/Döndürme/Simetri/Ölçek): hedef katıyı feature-seviye dönüştürür ----
  // CATIA Transformation Features — sketch/kenar/yüz GEREKMEZ (yalnız sayısal + opsiyonel yön/eksen/
  // referans seçimi). Sonuç = önceki katının DÖNÜŞTÜRÜLMÜŞÜ; orijinal KALMAZ (malzemeEkler=false →
  // hedef gizlenir). Symmetry doğru/nokta referansı orijinali içermez (Mirror'dan farkı budur).
  if (tip == "translation" || tip == "rotation" || tip == "symmetry" || tip == "scaling") {
    geometri::BrepGovde hedefDunya;
    if (!hedefDunyaBrep(feat, hedefDunya)) return;  // hedef katı yok → hata dolu
    auto& depo = sahne_.paramDepo();
    const auto dunyaEksen = [](int idx) -> Vec3d {
      switch (idx) { case 0: return {1, 0, 0}; case 1: return {0, 1, 0}; default: return {0, 0, 1}; }
    };
    // Yön/eksen referansı: idx 0..2 Dünya X/Y/Z; 3 Seçili Kenar (SahneEksen G/A → dünya yönü).
    const auto yonCoz = [&](const std::string& secK, const std::string& kenarK,
                            f64 varsayilanIdx) -> Vec3d {
      const int s = (int)feat.p(secK, varsayilanIdx);
      if (s == 3) {
        const u32 kg = (u32)feat.p(kenarK + "G");
        const u32 ka = (u32)feat.p(kenarK + "A");
        Dugum* dk = sahne_.bul(kg);
        geometri::KenarBilgi kb;
        if (dk && dk->brep && dk->brep->kenarBilgi(ka, kb) && kb.dogruMu)
          return dk->donusum.yonelim.dondur(kb.yon).birim();
      }
      return dunyaEksen(s);
    };
    geometri::BrepGovde sonuc;
    if (tip == "translation") {
      Vec3d yon = yonCoz("yon", "yonKenar", 0.0);
      if (feat.p("tersYon") > 0.5) yon = {-yon.x, -yon.y, -yon.z};
      const f64 mes = feat.pEfektif("mesafe", depo, 20.0);
      Donusumd d;  // sadece öteleme (yönelim birim, ölçek 1)
      d.konum = yon * mes;
      sonuc = geometri::donusumUygula(hedefDunya, d);
      if (!sonuc.gecerliMi()) { feat.hataAyarla("Öteleme başarısız"); return; }
    } else if (tip == "rotation") {
      Vec3d eks = yonCoz("eksen", "eksenKenar", 2.0);
      if (feat.p("tersYon") > 0.5) eks = {-eks.x, -eks.y, -eks.z};
      const f64 aci = feat.pEfektif("aci", depo, 45.0) * std::numbers::pi / 180.0;  // radyan
      Vec3d pivot{0, 0, 0};  // eksen başlangıcı: Seçili Kenar ise kenar başı (dünya), aksi orijin
      if ((int)feat.p("eksen", 2.0) == 3) {
        Dugum* dk = sahne_.bul((u32)feat.p("eksenKenarG"));
        geometri::KenarBilgi kb;
        if (dk && dk->brep && dk->brep->kenarBilgi((u32)feat.p("eksenKenarA"), kb) && kb.dogruMu)
          pivot = dk->donusum.noktaUygula(kb.bas);
      }
      Donusumd d;  // eksenNokta etrafında dön: p' = R*(p - P) + P → konum = P − R*P
      d.yonelim = Quatd::eksenAci(eks, aci);
      d.konum = pivot - d.yonelim.dondur(pivot);
      sonuc = geometri::donusumUygula(hedefDunya, d);
      if (!sonuc.gecerliMi()) { feat.hataAyarla("Döndürme başarısız"); return; }
    } else if (tip == "symmetry") {
      const int refTip = (int)feat.p("refTip");
      if (refTip == 1) {  // Doğru (Seçili Kenar) → eksen simetrisi
        Dugum* dk = sahne_.bul((u32)feat.p("kenarG"));
        geometri::KenarBilgi kb;
        if (!(dk && dk->brep && dk->brep->kenarBilgi((u32)feat.p("kenarA"), kb) && kb.dogruMu)) {
          feat.hataAyarla("Simetri için bir doğru (kenar) seçin"); return;
        }
        const Vec3d nokta = dk->donusum.noktaUygula(kb.bas);
        const Vec3d yon = dk->donusum.yonelim.dondur(kb.yon).birim();
        sonuc = geometri::simetrik(hedefDunya, nokta, yon, 1, /*birlestir*/ false);
      } else if (refTip == 2) {  // Nokta → nokta simetrisi (SahneNokta noktax/y/z)
        const Vec3d p{feat.p("noktax"), feat.p("noktay"), feat.p("noktaz")};
        sonuc = geometri::simetrik(hedefDunya, p, {0, 0, 1}, 2, false);
      } else {  // Düzlem (XY/YZ/ZX) → düzlem simetrisi (orijinden geçen)
        Vec3d normal;
        switch ((int)feat.p("duzlem")) {
          case 1: normal = {1, 0, 0}; break;  // YZ (X=0)
          case 2: normal = {0, 1, 0}; break;  // ZX (Y=0)
          default: normal = {0, 0, 1}; break; // XY (Z=0)
        }
        sonuc = geometri::simetrik(hedefDunya, {0, 0, 0}, normal, 0, false);
      }
      if (!sonuc.gecerliMi()) { feat.hataAyarla("Simetri başarısız"); return; }
    } else {  // scaling: merkez noktası referansıyla tekduze ölçek
      const f64 oran = feat.pEfektif("oran", depo, 1.5);
      if (oran <= 1e-9) { feat.hataAyarla("Ölçek oranı pozitif olmalı"); return; }
      const Vec3d merkez{feat.p("merkezx"), feat.p("merkezy"), feat.p("merkezz")};
      sonuc = geometri::olcekle(hedefDunya, merkez, oran);
      if (!sonuc.gecerliMi()) { feat.hataAyarla("Ölçekleme başarısız"); return; }
    }
    // Rijit dönüşüm/ölçek TopExp yüz sırasını korur → birim geri-harita (yüz sahiplenmez;
    // tıklama alttaki gerçek üreticiye geçer). Geometrik eşleme burada YANLIŞ olurdu (her
    // yüz taşındı → hepsi "yeni" görünür ve bu feature her yüzü sahiplenirdi).
    provenansKaydet(feat, hedefDunya, sonuc, kimlikHaritasi(sonuc));
    sonucYaz(feat, std::move(sonuc), /*ekler*/ false);  // dönüştürülmüş katı → hedefi gizle
    return;
  }

  // ---- Stiffener (Nervür / Destek): AÇIK profil kesitinden destek katısı ----
  // Açık profil düzlem-içinde kalınlaştırılıp (±kalinlik/2) profil düzlemine dik "derinlik"
  // boyunca prizma edilir → hedef katı ile Fuse (malzeme ekler). Kapalı profil GEREKMEZ (açık
  // kesit çizgisi); bu yüzden sketchProfilleri değil sketchAcikYolu kullanılır.
  if (tip == "stiffener") {
    std::vector yol;
    Vec3d nrm;
    DuzlemBilgi dzS;
    if (!sketchAcikYolu(feat.sketch(), yol, nrm, dzS)) {
      feat.hataAyarla("Nervür için açık profil sketch'i gerekli");
      return;
    }
    auto& depo = sahne_.paramDepo();
    const f64 kalinlik = feat.pEfektif("kalinlik", depo, 5.0);
    const f64 derinlik = feat.pEfektif("derinlik", depo, 20.0);
    if (kalinlik <= 1e-6) { feat.hataAyarla("Kalınlık pozitif olmalı"); return; }
    if (derinlik <= 1e-6) { feat.hataAyarla("Derinlik pozitif olmalı"); return; }
    const bool simetrik = feat.p("simetrik") > 0.5;
    Vec3d yonB = nrm.birim();
    if (feat.p("tersYon") > 0.5) yonB = {-yonB.x, -yonB.y, -yonB.z};
    // Açık kesiti düzlem-içi kalınlaştır → kapalı kontur → düzleme dik prizma.
    std::vector kontur = acikYolKalinlastir(yol, yonB, kalinlik);
    if (kontur.size() < 3) { feat.hataAyarla("Nervür kesiti oluşturulamadı"); return; }
    geometri::BrepGovde araci = geometri::prizma({kontur}, yonB, derinlik, simetrik);
    if (!araci.gecerliMi()) { feat.hataAyarla("Nervür prizması oluşturulamadı"); return; }
    // Hedef katı (önceki zincir sonucu; yoksa bağımsız hedefDugum) ile Fuse — hataya düşmeden.
    geometri::BrepGovde hedefW;
    bool hedefVar = oncekiKatiBrep(feat, hedefW);
    if (!hedefVar) {
      Dugum* h = sahne_.bul(feat.hedefDugum());
      if (h && h->brep && h->brep->gecerliMi()) {
        hedefW = geometri::donusumUygula(*h->brep, h->donusum);
        hedefVar = hedefW.gecerliMi();
      }
    }
    if (hedefVar) {
      geometri::BrepGovde b = geometri::birlesim(hedefW, araci);
      if (b.gecerliMi()) araci = std::move(b);  // fuse başarısız → yalnız nervür katısı kalır
    }
    provenansKaydet(feat, hedefW, araci);  // hedef yoksa (geçersiz girdi) tüm yüzler bu feature'ın
    sonucYaz(feat, std::move(araci), /*ekler*/ true);
    return;
  }

  // ---- Sketch tabanli feature'lar (pad/pocket/shaft/groove) ----
  // Gerçek-geometri profil: daire/yay gerçek OCCT edge (köşesiz silindir yüzeyi).
  std::vector konturlar;
  Vec3d normal{};
  DuzlemBilgi dz{};
  gunluk::g_teshisAdim = "PD:sketchProfilleri";
  if (!sketchProfilleri(feat.sketch(), konturlar, normal, dz)) {
    feat.hataAyarla("Kapalı bir sketch profili gerekli");
    return;
  }
  const bool ters = feat.p("tersYon") > 0.5;
  const FeatureTanim* td = featureTanimBul(tip);
  const bool ekler = td ? td->malzemeEkler : true;

  geometri::BrepGovde araci;
  gunluk::g_teshisAdim = "PD:profilCozuldu";

  if (tip == "loft" || tip == "loft_cikar") {
    // Çok-kesitli katı (Multi-sections Solid): 1. kesit = aktif sketch dış konturu (konturlar[0]);
    // ek kesitler kesitSayi + kesit0..N param'larında (sketch düğüm kimliği; pencere "➕ Kesit Ekle").
    // Her ek kesit sketch'inin DIŞ konturu (sketchProfilleri → [0]) alınır → ThruSections.
    std::vector kesitler;
    kesitler.push_back(konturlar[0]);  // aktif sketch dış konturu = 1. kesit
    const int kesitSayi = (int)feat.p("kesitSayi");
    for (int i = 0; i < kesitSayi; ++i) {
      const u32 sk = (u32)feat.p("kesit" + std::to_string(i));
      if (sk == 0) continue;
      std::vector kk;
      Vec3d nn{};
      DuzlemBilgi dzz{};
      if (sketchProfilleri(sk, kk, nn, dzz) && !kk.empty()) kesitler.push_back(kk[0]);
    }
    if (kesitler.size() < 2) {
      feat.hataAyarla("Loft için en az 2 kesit gerekli (➕ Kesit Ekle ile sketch ekleyin)");
      return;
    }
    araci = geometri::loftKati(kesitler, feat.p("kapali") > 0.5);
    if (!araci.gecerliMi()) {
      feat.hataAyarla("Loft başarısız (kesitler uyumsuz/kesişiyor olabilir)");
      return;
    }
  } else if (tip == "solid_combine") {
    // Solid Combine (CATIA Combine): 2 profil, her biri KENDİ normalinde prizmalanır (öteleme) →
    // iki prizmanın kesişimi (Common) katı. 1. profil = feature sketch'i (konturlar/normal yukarıda);
    // 2. profil = "ikinciSketch" param (pencere 🎯). Derinlik iki profilin dünya sınır-kutusundan.
    const u32 sk2 = (u32)feat.p("ikinciSketch");
    std::vector kont2;
    Vec3d normal2{};
    DuzlemBilgi dz2{};
    if (sk2 == 0 || !sketchProfilleri(sk2, kont2, normal2, dz2)) {
      feat.hataAyarla("2. profil sketch seçin (kapalı) — 🎯 2. Profil");
      return;
    }
    // Öteleme derinliği: her iki profilin polyline nokta bulutunun dünya sınır-kutusu köşegeni.
    // Her prizma kendi düzleminden ±derin uzatılır → kesişim bölgesi (tünel çakışması) kapsanır.
    f64 derin = 100.0;
    {
      std::vector> pl1, pl2;
      Vec3d nn{};
      if (sketchKonturlari(feat.sketch(), pl1, nn) && sketchKonturlari(sk2, pl2, nn)) {
        Vec3d mn{1e18, 1e18, 1e18}, mx{-1e18, -1e18, -1e18};
        const auto katil = [&](const std::vector>& pl) {
          for (const auto& k : pl)
            for (const Vec3d& p : k) {
              mn = {std::min(mn.x, p.x), std::min(mn.y, p.y), std::min(mn.z, p.z)};
              mx = {std::max(mx.x, p.x), std::max(mx.y, p.y), std::max(mx.z, p.z)};
            }
        };
        katil(pl1);
        katil(pl2);
        const f64 kosegen = (mx - mn).uzunluk();
        if (kosegen > 1e-6) derin = kosegen;  // her iki yöne yeterli açıklık
      }
    }
    geometri::BrepGovde p1 = geometri::prizmaMultiEgri(konturlar, normal.birim(), derin, derin);
    geometri::BrepGovde p2 = geometri::prizmaMultiEgri(kont2, normal2.birim(), derin, derin);
    if (!p1.gecerliMi() || !p2.gecerliMi()) {
      feat.hataAyarla("Solid Combine prizma başarısız (profil geçersiz olabilir)");
      return;
    }
    araci = geometri::kesisim(p1, p2);
    if (!araci.doluMu()) {
      feat.hataAyarla("Solid Combine başarısız (profiller öteleme yönünde kesişmiyor)");
      return;
    }
  } else if (tip == "shaft" || tip == "groove") {
    const f64 aci = feat.pEfektif("aci", sahne_.paramDepo(), 360.0);
    if (aci <= 1e-6) { feat.hataAyarla("Açı pozitif olmalı"); return; }
    // Eksen secimi: 0=Sketch U, 1=Sketch V, 2=Dünya X, 3=Dünya Y, 4=Dünya Z,
    // 5=Seçili Kenar (sahneden — kenar dünya yönü + başlangıç noktası).
    const int es = (int)feat.p("eksen");
    Vec3d eksenNokta = dz.orijin, eksenYon;
    switch (es) {
      case 0: eksenYon = dz.u; break;
      case 1: eksenYon = dz.v; break;
      case 2: eksenYon = {1, 0, 0}; eksenNokta = {0, 0, 0}; break;
      case 3: eksenYon = {0, 1, 0}; eksenNokta = {0, 0, 0}; break;
      case 5: {  // Seçili Kenar → kenar dünya yönü + başlangıç noktası dünyada.
        const u32 kg = (u32)feat.p("eksenKenarG");
        const u32 ka = (u32)feat.p("eksenKenarA");
        Dugum* dk = sahne_.bul(kg);
        geometri::KenarBilgi kb;
        if (dk && dk->brep && dk->brep->kenarBilgi(ka, kb) && kb.dogruMu) {
          eksenYon = dk->donusum.yonelim.dondur(kb.yon).birim();
          eksenNokta = dk->donusum.noktaUygula(kb.bas);
        } else {  // geçersiz kenar → varsayılan Dünya Z
          eksenYon = {0, 0, 1};
          eksenNokta = {0, 0, 0};
        }
        break;
      }
      default: eksenYon = {0, 0, 1}; eksenNokta = {0, 0, 0}; break;
    }
    if (ters) eksenYon = {-eksenYon.x, -eksenYon.y, -eksenYon.z};
    araci = geometri::dondurKatiEgri(konturlar, eksenNokta, eksenYon, aci);
    if (!araci.gecerliMi()) {
      feat.hataAyarla("Döndürme başarısız (eksen profili kesiyor olabilir)");
      return;
    }
  } else if (tip == "rib" || tip == "slot") {
    // Rib/Slot: kapalı profili kılavuz eğri (center curve) boyunca süpür (BRepOffsetAPI_MakePipe).
    // Kılavuz = ayrı sketch; kimliği kilavuzSketch param'ında (pencere "🎯 Kılavuz Sketch Seç").
    const u32 kilavuzSk = (u32)feat.p("kilavuzSketch");
    std::vector yol;
    if (kilavuzSk == 0 || !sketchYolu(kilavuzSk, yol)) {
      feat.hataAyarla("Kılavuz eğri (center curve) sketch seçin");
      return;
    }
    araci = geometri::supurmeKatiEgri(konturlar, yol);
    if (!araci.gecerliMi()) {
      feat.hataAyarla("Süpürme başarısız (kılavuz/profil uyumsuz olabilir)");
      return;
    }
  } else {
    // pad / pocket: CATIA 1. Limit Tipi (limitTip):
    //   0 Boyut · 1 Bir Sonrakine · 2 Sonuncuya · 3 Düzleme · 4 Yüzeye.
    const int limitTip = (int)feat.p("limitTip");
    // Uzatma yönü ÖNCE çözülür — Up To mesafe hesabı bu yön boyunca yapılır.
    // Profil normali (varsayılan), Dünya X/Y/Z veya Seçili Kenar (sahneden).
    Vec3d yon = normal;
    switch ((int)feat.p("yonRef")) {
      case 1: yon = {1, 0, 0}; break;
      case 2: yon = {0, 1, 0}; break;
      case 3: yon = {0, 0, 1}; break;
      case 4: {  // Seçili Kenar → kenar dünya yönü (geçersizse profil normaline düş).
        const u32 kg = (u32)feat.p("yonKenarG");
        const u32 ka = (u32)feat.p("yonKenarA");
        Dugum* dk = sahne_.bul(kg);
        geometri::KenarBilgi kb;
        if (dk && dk->brep && dk->brep->kenarBilgi(ka, kb) && kb.dogruMu)
          yon = dk->donusum.yonelim.dondur(kb.yon).birim();
        else
          yon = normal;
        break;
      }
      default: yon = normal; break;
    }
    if (ters) yon = {-yon.x, -yon.y, -yon.z};
    const Vec3d yonB = yon.birim();

    // İleri = birinci limit mesafesi; geri = ikinci yön (yalnız Boyut modunda).
    f64 ileri = 0.0, geri = 0.0;
    if (limitTip == 3 || limitTip == 4) {
      // Düzleme / Yüzeye: seçili nokta (+ yüz normali) → profil düzleminden yön-boyu mesafe.
      if (feat.p("hedefNoktaVar") < 0.5) {
        feat.hataAyarla("Hedef düzlem/yüz seçilmedi (🎯)");
        return;
      }
      const Vec3d hp{feat.p("hedefNoktax"), feat.p("hedefNoktay"), feat.p("hedefNoktaz")};
      const Vec3d N{feat.p("hedefNoktaNx"), feat.p("hedefNoktaNy"), feat.p("hedefNoktaNz")};
      const f64 nd = N.nokta(yonB);
      f64 dist;
      if (N.uzunlukKare() > 1e-9 && std::fabs(nd) > 1e-6)
        dist = N.nokta(hp - dz.orijin) / nd;  // gerçek düzlem-izdüşümü (BREP yüz normali)
      else
        dist = (hp - dz.orijin).nokta(yonB);  // normal yoksa yön-doğrultusu izdüşümü
      if (dist <= 1e-6) {
        feat.hataAyarla("Hedef, uzatma yönünün önünde değil");
        return;
      }
      ileri = dist;
    } else if (limitTip == 1 || limitTip == 2) {
      // Bir Sonrakine / Sonuncuya: hedef katının yön-boyu açıklığıyla YAKLAŞIK (TODO: BRepFeat).
      geometri::BrepGovde hedefW;
      if (!hedefDunyaBrep(feat, hedefW)) return;  // hata hedefDunyaBrep içinde dolar
      f64 yakin, uzak;
      if (!hedefYonAraligi(hedefW, dz.orijin, yonB, yakin, uzak) || uzak <= 1e-6) {
        feat.hataAyarla("Hedef katı yön boyunca ölçülemedi");
        return;
      }
      // Bir Sonrakine ≈ ilk pozitif yüz (yoksa uzak); Sonuncuya ≈ en uzak yüz.
      ileri = (limitTip == 1) ? (yakin > 1e-6 ? yakin : uzak) : uzak;
    } else {
      // Boyut (varsayılan): çift-yön prizma. uzunluk/derinlik formülle sürülebilir.
      const bool simetrik = feat.p("simetrik") > 0.5;
      const bool limit2 = feat.p("limit2Aktif") > 0.5;
      const f64 mesafe = feat.pEfektif(ekler ? "uzunluk" : "derinlik", sahne_.paramDepo(), 10.0);
      if (mesafe <= 1e-6) { feat.hataAyarla("Uzunluk/derinlik pozitif olmalı"); return; }
      ileri = mesafe;
      if (simetrik)
        ileri = geri = mesafe / 2.0;
      else if (limit2)
        geri = feat.pEfektif(ekler ? "uzunluk2" : "derinlik2", sahne_.paramDepo(), 10.0);
    }
    // Çok-bölgeli profil (even-odd): yan yana konturlar → ayrı katılar, iç içe → delik.
    gunluk::g_teshisAdim = "PD:prizma:basla";
    araci = geometri::prizmaMultiEgri(konturlar, yonB, ileri, geri,
                                      feat.pEfektif("draftAci", sahne_.paramDepo()));
    gunluk::g_teshisAdim = "PD:prizma:bitti";
    if (!araci.gecerliMi()) {
      feat.hataAyarla("Prizma oluşturulamadı (profil geçersiz olabilir)");
      return;
    }
  }

  if (ekler) {
    // CATIA additive: hedef katı (DM: sketch'in kaynak gövdesi — ÇOK GÖVDELİ belgede doğru
    // gövdeye fuse) varsa ÖNCE onunla; yoksa zincirde önceki katı (klasik pad zinciri,
    // hedefDugum=0). Tek gövdede iki yol eşdeğer; önceki sıralama çok gövdede yanlış
    // gövdeye fuse ediyordu.
    geometri::BrepGovde onceki;
    bool fuseVar = false;
    if (Dugum* h = sahne_.bul(feat.hedefDugum())) {
      if (h->brep && h->brep->gecerliMi()) {
        onceki = geometri::donusumUygula(*h->brep, h->donusum);
        fuseVar = onceki.gecerliMi();
      }
    }
    if (!fuseVar) fuseVar = oncekiKatiBrep(feat, onceki);
    gunluk::g_teshisAdim = "PD:ekler:oncekiKati";
    if (fuseVar) {
      gunluk::g_teshisAdim = "PD:ekler:birlesim";
      geometri::BrepGovde birlesmis = geometri::birlesim(onceki, araci);
      if (!birlesmis.gecerliMi()) {
        feat.hataAyarla("Birleşim başarısız (geometri geçersiz)");
        return;
      }
      araci = std::move(birlesmis);
    }
    // Provenance: fuse'lu pad'de öncekinde eşleşmeyen (prizma) yüzler; zincir başında
    // (onceki geçersiz) TÜM yüzler bu feature'ın.
    gunluk::g_teshisAdim = "PD:ekler:provenans";
    provenansKaydet(feat, onceki, araci);
    gunluk::g_teshisAdim = "PD:ekler:sonucYaz";
    sonucYaz(feat, std::move(araci), /*ekler*/ true);
  } else {
    hedeftenCikar(feat, std::move(araci));
  }
}

u32 PartDesignSistemi::featureBaslat(const std::string& tip, Varlik aktifSketch,
                                     u32 hedefDugum) {
  // Lisans/salt-okunur kapısı: feature üreten TEK giriş noktası (komut penceresi,
  // gizmo sürüklemesi, akıllı yeniden düzenleme — hepsi buradan geçer).
  if (!duzenlemeIzinli()) return 0;
  const FeatureTanim* td = featureTanimBul(tip);
  if (!td) return 0;
  // Hole sayisal konum; Fillet/Chamfer hedef kati kenarlari → sketch GEREKMEZ.
  // Thickness (hedef katı uniform offset) + Boolean Bodies (2 gövde) da sketchsiz.
  const bool sketchGerek = (tip != "hole" && tip != "fillet" && tip != "chamfer" &&
                            tip != "fillet_var" && tip != "fillet_ff" &&
                            tip != "fillet_tri" && tip != "shell" && tip != "draft" &&
                            tip != "pattern_dik" &&
                            tip != "pattern_dairesel" && tip != "mirror" &&
                            tip != "thickness" && tip != "bool_add" &&
                            tip != "bool_remove" && tip != "bool_intersect" &&
                            tip != "union_trim" && tip != "remove_lump" &&
                            tip != "translation" && tip != "rotation" &&
                            tip != "symmetry" && tip != "scaling" &&
                            tip != "draft_reflect" && tip != "sew_surface" &&
                            tip != "replace_face");
  const auto sketchMi = [&](Varlik s) {
    return s != 0 && dynamic_cast(cekirdek_.nesne(s)) != nullptr;
  };
  // aktif (düzenlenen) sketch yoksa SEÇİLİ sketch'e düş (CATIA: sketch seç → Pad/Pocket).
  if (sketchGerek && !sketchMi(aktifSketch)) aktifSketch = sahne_.seciliSketch();
  // Hâlâ yoksa: sahnedeki SON sketch'e düş (Pocket/Groove için tek-sketch kolaylığı —
  // pencere sessizce açılmama sorununu giderir; kullanıcı doğru sketch'i sonra seçebilir).
  if (sketchGerek && !sketchMi(aktifSketch)) {
    for (auto& d : sahne_.dugumler())
      if (d.tip == DugumTip::Sketch && sketchMi(d.kimlik)) aktifSketch = d.kimlik;
  }
  // CATIA akışı: pencere HER ZAMAN açılır (Pad gibi). Eksik sketch/hedef → feature yine
  // taslak oluşturulur; recompute "sketch/hedef seçin" hatası verir, kullanıcı pencere
  // içindeki 🎯 seçim araçlarıyla tamamlar. Böylece tüm feature'lar penceresini açar.
  // (Eski davranış: eksikse return 0 → pencere hiç açılmazdı.)

  // Rollback aktifken yeni feature: rollback'i iptal et. Yeni feature zincir SONUNA
  // eklenir → tüm zincir yeniden aktif+görünür olmalı (MVP: "insert after marker"
  // yerine basit tutarlı davranış — sona ekle + sona sar).
  if (rollbackFeat_ != 0) rollbackAyarla(0);

  const Varlik v = cekirdek_.dunya().olustur();
  auto feat = std::make_unique(v, this, tip);
  feat->adAyarlaDirekt(td->ad);
  feat->sketchAyarla(aktifSketch);
  feat->hedefDugumAyarla(hedefDugum);
  for (const FeatureParam& p : td->parametreler) {
    if (p.tip == ParamTip::SahneNokta) {  // taban ad → x/y/z ayrı anahtar (GSD kalıbı)
      feat->pAyarla(p.anahtar + "x", p.varsayilan);
      feat->pAyarla(p.anahtar + "y", p.minVeya);
      feat->pAyarla(p.anahtar + "z", p.maksVeya);
    } else {
      feat->pAyarla(p.anahtar, p.varsayilan);
    }
  }
  Feature* ref = feat.get();
  cekirdek_.nesneEkle(std::move(feat));
  cekirdek_.gorunumAta(std::make_unique(v, this, td->simge));
  // CATIA spec-tree: feature aktif PartBody altında yaşar (sketch/düzlemle aynı grup).
  sahne_.ustAyarla(v, sahne_.partBodyAl());
  featureler_.push_back(v);
  taslak_ = v;
  gunluk::g_teshisAdim = "PD:featureBaslat:yenidenInsa";
  // İlk recompute çökme/deadlock (OSD_ThreadPool nested mesh vb.) atarsa → hataya çevir,
  // feature yine de oluşturulmuş kalır ve PENCERE AÇILIR (kullanıcı girdi tamamlar/tekrar dener).
  try {
    yenidenInsa(*ref);   // yeni feature = zincir sonu (girdisi önceki katı, fuse/cut orada)
    gunluk::g_teshisAdim = "PD:featureBaslat:gorunurlukTazele";
    gorunurlukTazele();  // CATIA: yeni sonuç görünür, önceki feature sonucu gizlenir (tek volume)
  } catch (const std::exception& e) {
    ref->hataAyarla(std::string("İlk hesaplama hatası: ") + e.what());
  } catch (...) {
    ref->hataAyarla("İlk hesaplama hatası (bilinmeyen)");
  }
  gunluk::g_teshisAdim = "PD:featureBaslat:event";
  cekirdek_.olaylar().yayinla(FeatureEklendi{v}, td->ad.c_str());
  gunluk::g_teshisAdim = "PD:featureBaslat:bitti";
  return v;
}

void PartDesignSistemi::bastirmaAyarla(u32 featKimlik, bool bastir) {
  Feature* f = bul(featKimlik);
  if (!f || f->bastirildi() == bastir) return;
  f->bastirmaAyarla(bastir);
  // Suppress/unsuppress zinciri kaydırır (sonraki feature'ların girdisi değişir) → bu
  // noktadan itibaren yeniden hesapla + görünürlük tazele (CATIA tek-volume tutarlı).
  zincirHesapla(featKimlik);
  cekirdek_.olaylar().yayinla(FeatureGuncellendi{featKimlik});
}

void PartDesignSistemi::kilitAyarla(u32 featKimlik, bool kilit) {
  if (Feature* f = bul(featKimlik)) {
    f->kilitAyarla(kilit);
    cekirdek_.olaylar().yayinla(FeatureGuncellendi{featKimlik});
  }
}

bool PartDesignSistemi::featureAktifMi(u32 feat) const {
  if (rollbackFeat_ == 0) return true;  // rollback yok → hepsi aktif (O(1) sıcak yol)
  // Tek geçiş: feat ve rollback noktasının sıra indekslerini birlikte bul (iki std::find yerine).
  const auto& lst = featureler();
  int idxF = -1, idxR = -1;
  for (int i = 0; i < (int)lst.size(); ++i) {
    if (lst[i] == feat) idxF = i;
    if (lst[i] == rollbackFeat_) idxR = i;
    if (idxF >= 0 && idxR >= 0) break;
  }
  if (idxR < 0 || idxF < 0) return true;  // biri kayıp → güvenli: aktif
  return idxF <= idxR;  // rollback noktası DAHİL aktif; sonrakiler devre dışı
}

bool PartDesignSistemi::featureSiraGecerliMi(const std::vector& sira) const {
  std::unordered_map pos;
  for (int i = 0; i < (int)sira.size(); ++i) pos[sira[i]] = i;
  auto* self = const_cast(this);
  const auto uretici = [&](u32 dugum) -> u32 {  // sonucDugum==dugum olan feature (girdiyi üretir)
    if (!dugum) return 0;
    u32 son = 0;
    for (u32 fk : sira)
      if (Feature* g = self->bul(fk); g && g->sonucDugum() == dugum) son = fk;
    return son;
  };
  for (u32 fk : sira) {
    Feature* f = self->bul(fk);
    if (!f) continue;
    const u32 p = uretici(f->hedefDugum());
    if (p && pos.count(p) && pos.at(p) >= pos.at(fk)) return false;  // üretici sonrada → geçersiz
  }
  return true;
}

bool PartDesignSistemi::featureSiraOner(u32 tasinan, u32 hedef, bool oncesine,
                                        std::vector& out) const {
  if (tasinan == hedef) return false;
  out = featureler();
  if (std::find(out.begin(), out.end(), tasinan) == out.end() ||
      std::find(out.begin(), out.end(), hedef) == out.end())
    return false;
  out.erase(std::remove(out.begin(), out.end(), tasinan), out.end());
  auto itH = std::find(out.begin(), out.end(), hedef);
  out.insert(oncesine ? itH : itH + 1, tasinan);
  return featureSiraGecerliMi(out);
}

void PartDesignSistemi::featureSirasiniAyarla(std::vector yeni) {
  featureler_ = std::move(yeni);
  // Tree display (urun_agaci (sira,kimlik) sıralar): feature ECS sira = 1000+index → taban gövde/
  // sketch (sira~0) sonrasında, aralarında featureler_ sırasında dizilir. Recompute+display senkron.
  for (int i = 0; i < (int)featureler_.size(); ++i)
    if (auto* h = sahne_.dunya().bul(featureler_[i])) h->sira = 1000 + i;
  zincirHesapla(0);
  cekirdek_.olaylar().yayinla(FeatureGuncellendi{featureler_.empty() ? 0u : featureler_[0]});
}

void PartDesignSistemi::rollbackAyarla(u32 feat) {
  rollbackFeat_ = feat;
  // Aktif (rollback-öncesi) zinciri baştan yeniden hesapla + tek-volume görünürlük tazele:
  // rollback noktasındaki SON aktif feature sonucu görünür, gerisi (sonrası + ara) gizli.
  zincirHesapla(0);
  cekirdek_.olaylar().yayinla(FeatureGuncellendi{feat});
}

void PartDesignSistemi::iptalOncesi(Feature& feat) {
  // Cikarma feature'i silinince hedef gorunurlugunu geri ac.
  const FeatureTanim* td = featureTanimBul(feat.tip());
  if (td && !td->malzemeEkler)
    if (Dugum* hedef = sahne_.bul(feat.hedefDugum())) hedef->gorunur = true;
}

// Feature'i kalici olarak sil (undo). Base silOrtak: iptalOncesi (hedef
// gorunurluk) + sonuc dugum + nesne + listeden cikar + olay.
void PartDesignSistemi::featureSilKalici(u32 feat) {
  silOrtak(feat);
  zincirHesapla(0);  // silinen feature sonrası kalan zincir + tek-volume görünürlük tazele
}

PartDesignSistemi::FeatureYedek PartDesignSistemi::yedekle(u32 feat) const {
  FeatureYedek y;
  auto* f = const_cast(this)->bul(feat);
  if (!f) return y;
  y.tip = f->tip();
  y.ad = f->ad();
  y.sketch = f->sketch();
  y.hedefDugum = f->hedefDugum();
  y.parametreler = f->parametreler();
  return y;
}

bool PartDesignSistemi::geriYukle(const FeatureYedek& y, u32 kimlik) {
  if (y.tip.empty()) return false;
  const FeatureTanim* td = featureTanimBul(y.tip);
  if (!td) return false;
  // Ayni kimlikle diril (undo/redo tutarliligi icin). Dunya slotu zaten serbest.
  cekirdek_.dunya().dirilt(kimlik);
  auto feat = std::make_unique(kimlik, this, y.tip);
  feat->adAyarlaDirekt(y.ad.empty() ? td->ad : y.ad);
  feat->sketchAyarla(y.sketch);
  feat->hedefDugumAyarla(y.hedefDugum);
  for (const auto& [k, v] : y.parametreler) feat->pAyarla(k, v);
  Feature* ref = feat.get();
  cekirdek_.nesneEkle(std::move(feat));
  cekirdek_.gorunumAta(std::make_unique(kimlik, this, td->simge));
  featureler_.push_back(kimlik);
  yenidenInsa(*ref);
  cekirdek_.olaylar().yayinla(FeatureEklendi{kimlik}, td->ad.c_str());
  return true;
}

void PartDesignSistemi::parametreleriUygula(u32 feat,
                                            const std::unordered_map& p) {
  Feature* f = bul(feat);
  if (!f) return;
  if (f->parametreler() == p) return;  // değişiklik yok → recompute + event atla
  f->parametreler() = p;
  zincirHesapla(feat);  // bu feature + SONRAKİLER (input değişti) → CATIA zincir tutarlı
  cekirdek_.olaylar().yayinla(FeatureGuncellendi{feat});
}

std::vector PartDesignSistemi::kaliciCikar() const {
  std::vector cikti;
  auto* self = const_cast(this);
  self->asyncTumunuBitir();  // bekleyen async sonuçlar dosyaya EKSİKSİZ girsin (kaydet anı)
  for (u32 fk : featureler_) {
    Feature* f = self->bul(fk);
    if (!f) continue;
    FeatureKalici k;
    k.kimlik = fk;
    k.tip = f->tip();
    k.ad = f->ad();
    k.sketch = f->sketch();
    k.hedefDugum = f->hedefDugum();
    k.sonucDugum = f->sonucDugum();
    k.bastirildi = f->bastirildi();
    k.kilitli = f->kilitli();
    k.parametreler = f->parametreler();
    k.formuller = f->formuller();
    k.uretilenYuzler = f->uretilenYuzler();
    k.yuzGeriHarita = f->yuzGeriHarita();
    cikti.push_back(std::move(k));
  }
  return cikti;
}

u32 PartDesignSistemi::kaliciGeriYukle(const FeatureKalici& k) {
  if (k.tip.empty()) return 0;
  const FeatureTanim* td = featureTanimBul(k.tip);
  if (!td) return 0;
  // YENI Varlik (dosya kimlikleri yuklenen dunya ile cakismasin — remap zaten yapildi).
  const Varlik v = cekirdek_.dunya().olustur();
  auto feat = std::make_unique(v, this, k.tip);
  feat->adAyarlaDirekt(k.ad.empty() ? td->ad : k.ad);
  feat->sketchAyarla(k.sketch);
  feat->hedefDugumAyarla(k.hedefDugum);
  feat->sonucDugumAyarla(k.sonucDugum);  // zaten yuklenmis geometri dugumu (remap edilmis)
  feat->bastirmaAyarla(k.bastirildi);
  feat->kilitAyarla(k.kilitli);
  for (const auto& [a, d] : k.parametreler) feat->pAyarla(a, d);
  for (const auto& [a, i] : k.formuller) feat->formulAyarla(a, i);
  feat->uretilenYuzlerAyarla(k.uretilenYuzler);
  feat->yuzGeriHaritaAyarla(k.yuzGeriHarita);
  cekirdek_.nesneEkle(std::move(feat));
  cekirdek_.gorunumAta(std::make_unique(v, this, td->simge));
  // CATIA spec-tree: feature aktif PartBody altinda; sonuc dugumu feature altinda nest.
  sahne_.ustAyarla(v, sahne_.partBodyAl());
  if (k.sonucDugum && sahne_.bul(k.sonucDugum)) sahne_.ustAyarla(k.sonucDugum, v);
  featureler_.push_back(v);
  // recompute YOK — yuklenmis mesh korunur (hizli + cokme yok). Parametre duzenlenince
  // featureGuncelle → yenidenInsa taze uretir (sketch/hedef/param hepsi kurulu).
  cekirdek_.olaylar().yayinla(FeatureEklendi{v}, td->ad.c_str());
  return v;
}

}  // namespace fiko::parca