#include <windows.h>
#include <io.h>  // _dup2/_fileno — senaryo log stderr→stdout birleştirme

#include <algorithm>
#include <array>
#include <cctype>
#include <atomic>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <thread>
#include <cfloat>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <memory>
#include <numbers>
#include <string>
#include <unordered_map>

#include "cam_paneli.hpp"
#include "cam_operasyon_penceresi.hpp"
#include "cam_simulasyon_penceresi.hpp"
#include "acilis_splash.hpp"
#include "belge.hpp"
#include "belge_ciz.hpp"
#include "surec_kopru.hpp"
#include "belge_yonetici.hpp"
#include "cam_parca_op_penceresi.hpp"
#include "cam_takim_penceresi.hpp"
#include "cam_takim_ekseni_penceresi.hpp"
#include "cam_urun_agaci.hpp"
#include "feature_penceresi.hpp"
#include "wtec_kafa_penceresi.hpp"
#include "referans_penceresi.hpp"
#include "gsd_penceresi.hpp"
#include "hizli3d_penceresi.hpp"
#include "workbench_secici.hpp"
#include "analiz_penceresi.hpp"
#include "analiz_urun_agaci.hpp"
#include "dokum_penceresi.hpp"
#include "fiko/dokum/dokum_simulasyonu.hpp"
#include "fiko/dokum/sph_gpu.hpp"  // senaryo: GPU SPH dispatch doğrulaması
#include "fiko/analiz/analiz_sistemi.hpp"
#include "fiko/betik/makro_motor.hpp"
#include "fiko/parca/part_design_sistemi.hpp"
#include "fiko/gsd/gsd_sistemi.hpp"
#include "fiko/cam/apt.hpp"
#include "fiko/cam/cam_islemleri.hpp"
#include "fiko/cam/cam_secim.hpp"
#include "fiko/cam/cam_sistemi.hpp"
#include "fiko/cekirdek/cekirdek.hpp"
#include "fiko/cekirdek/guncelleme.hpp"
#include "guncelleme_kontrol.hpp"
#include "guncelleme_indir.hpp"
#include "fiko/cekirdek/surum.hpp"
#include "fiko/cekirdek/fiko_kisayol.hpp"
#include "fiko/cekirdek/fiko_son_dosyalar.hpp"
#include "fiko/cekirdek/urun_agaci.hpp"
#include "fiko/girdi/baglam_politika.hpp"
#include "fiko/girdi/fare_jest.hpp"
#include "fiko/girdi/hover_probu.hpp"
#include "fiko/eklenti/eklenti_yonetici.hpp"
#include "fiko/core/gunluk.hpp"
#include "fiko/core/kare_istek.hpp"
#include "fiko/platform/pencere.hpp"
#include "fiko/platform/sistem.hpp"
#include "fiko/render/boru_hatti.hpp"
#include "fiko/render/orbit_kamera.hpp"
#include "fiko/render/referans_olcek.hpp"
#include "fiko/render/secim_gpu.hpp"
#include "fiko/render/vertex.hpp"
#include "fiko/render/vulkan_baglam.hpp"
#include "fiko/sahne/kutuk_hesap.hpp"
#include "fiko/sahne/alan_secim.hpp"
#include "fiko/sahne/olcum_sistemi.hpp"
#include "fiko/sahne/sahne.hpp"
#include "fiko/sahne/sahne_islem.hpp"
#include "fiko/sahne/sahne_ref_secim.hpp"
#include "fiko/sahne/secim_sistemi.hpp"
#include "fiko/sahne/sketch_nesne.hpp"
#include "fiko/sketch/snap.hpp"
#include "fiko/ui/fikoui.hpp"
#include "fiko/ui/imgui_katman.hpp"
#include "fiko/ui/simge.hpp"  // arka plan görseli (bulut+logo) GPU dokusu
#include "imgui.h"
#include "imgui_internal.h"
#include "kesit_gizmo.hpp"
#include "pencere_util.hpp"
#include "pusula.hpp"
#include "secim_yigini.hpp"
#include "uygulama_baglam.hpp"
#include "ui_ortak.hpp"
#include "ayar_kalici.hpp"
#include "ayar_kayitlari.hpp"
#include "ayarlar_penceresi.hpp"
#include "kontrol_merkezi.hpp"
#include "fiko/ayar/ayar_sistemi.hpp"
#include "dosya_islemleri.hpp"
#include "fiko_panel.hpp"
#include "surec_yonetici_panel.hpp"  // FikoSurecYoneticiPanel (merkezi süreç yöneticisi)
#include "isik_durum.hpp"
#include "isik_penceresi.hpp"
#include "ozellikler_paneli.hpp"
#include "sketch_ui.hpp"
#include "urun_agaci_paneli.hpp"
#include "yardimci_penceler.hpp"

namespace {

using namespace fiko;

// Canlı önizleme sürükleme kimliği (kalıcı değil) — hem setup hem frame kullanır.
constexpr u32 SURUKLE_CANLI = 0xFFFFFFFFu;

// ---- Bağlam motoru → sağ-tık halkası köprüsü (Dalga 3) ----
// Motor sunumdan bağımsız BaglamOnerisi üretir; araç halkası girdi::MenuOgeSecim bekler
// (yapısal olarak birebir). Bu köprü kaynak tampondan çevirir; simge işaretçileri KAYNAK
// vektöre bakar → kaynak (sonMotorOneri_) menü ömrü boyunca yaşamalı (çağıran garanti eder).
girdi::MenuOgeSecim baglamOneriCevir(const baglam::BaglamOnerisi& b) {
  girdi::MenuOgeSecim m;
  m.komutKimlik = b.komutKimlik;  // std::string (sahiplenir)
  m.etiket = b.etiket;            // std::string (sahiplenir)
  m.simge = b.simge.c_str();      // STABİL: b kaynak vektörde yaşar (çerçeve boyu)
  m.etkin = b.etkin;
  m.yon = b.yon;
  m.cocuklar.reserve(b.cocuklar.size());
  for (const auto& c : b.cocuklar) m.cocuklar.push_back(baglamOneriCevir(c));
  return m;
}
void baglamPolitikaYap(const std::vector<baglam::BaglamOnerisi>& kaynak,
                       girdi::PolitikaKarar& out) {
  out = girdi::PolitikaKarar{};
  if (kaynak.empty()) return;
  out.menu = girdi::MenuTur::AracHalkasi;
  out.radyal = true;  // motor önerileri nested radyal halka (yaprak + ara-öğe)
  out.ogeler.reserve(kaynak.size());
  for (const auto& b : kaynak) out.ogeler.push_back(baglamOneriCevir(b));
}
// Motorun ZENGİN kapsadığı bağlamlar (Dalga 3 kapsamı: gövde/boş/yüz/kenar/vertex).
// Datum/GSD/CAM/ölçüm/sketch bağlamları Dalga 5'e kadar mevcut BaglamPolitika'ya düşer
// (REGRESYON YOK). Bu kategoriler dışında motor sonucu KULLANILMAZ.
bool baglamMotorKapsar(baglam::HedefKategori k) {
  using K = baglam::HedefKategori;
  switch (k) {
    case K::Bos: case K::Govde: case K::Vertex:
    case K::YuzDuz: case K::YuzSilindir: case K::YuzKonik: case K::YuzKure: case K::YuzEgri:
    case K::KenarCizgi: case K::KenarYay: case K::KenarCember: case K::KenarSpline:
      return true;
    default:
      return false;
  }
}
// İmleç-altı semantik kategorinin kısa Türkçe adı (HUD/ipucu başlığı).
const char* baglamKategoriAd(baglam::HedefKategori k) {
  using K = baglam::HedefKategori;
  switch (k) {
    case K::Bos: return "Boşluk";
    case K::Govde: return "Gövde";
    case K::YuzDuz: return "Düz Yüz";
    case K::YuzSilindir: return "Silindirik Yüz";
    case K::YuzKonik: return "Konik Yüz";
    case K::YuzKure: return "Küresel Yüz";
    case K::YuzEgri: return "Eğri Yüz";
    case K::KenarCizgi: return "Doğru Kenar";
    case K::KenarYay: return "Yay";
    case K::KenarCember: return "Çember Kenar";
    case K::KenarSpline: return "Spline Kenar";
    case K::Vertex: return "Köşe";
    case K::RefDuzlem: return "Referans Düzlem";
    case K::RefEksen: return "Referans Eksen";
    case K::Orijin: return "Orijin";
    case K::SketchEntity: return "Eskiz Öğesi";
    case K::GsdTel: return "Tel-Kafes";
    case K::GsdYuzey: return "Yüzey";
    case K::OlcumNesne: return "Ölçüm";
    case K::CamGeometri: return "CAM Geometri";
    case K::TakimYolu: return "Takım Yolu";
    default: return "";
  }
}

// ---- 3D görünüm arka planı: bulut fotoğrafı + ortada FikoYT logosu (web'deki
// fikoyt-arka-1.webp'in birebiri, tek birleşik görsel). Görsel GPU dokusuna bir
// kez (ilk karede) yüklenir, ömrü uygulama boyunca; her kare tek AddImage çizimi
// (allocation yok). Görsel yoksa güvenli koyu gradyana düşer. ImGui pencere
// drawlist'ine, offscreen sahne dokusundan (ImGui::Image) ÖNCE çizilir → sahne
// nesneleri (saydam-temiz viewport sayesinde) arka planın önünde kalır.
constexpr float kArkaGorselEn = 1920.0f;   // kaynak görsel doğal en (cover için)
constexpr float kArkaGorselBoy = 1200.0f;  // kaynak görsel doğal boy
void arkaPlanCiz(ImDrawList* dl, ImVec2 sol, ImVec2 boyut) {
  static ImTextureID s_doku = 0;
  static bool s_denendi = false;
  if (!s_denendi) {
    s_denendi = true;  // tek deneme; başarısızsa gradyana düşülür, tekrar yüklenmez
    s_doku = ui::kitaplik::yukle("arka3d",
                                 sistem::calistirilabilirDizin() + "resimler\\fikoyt-arka.png");
  }
  const ImVec2 sagAlt{sol.x + boyut.x, sol.y + boyut.y};
  if (!s_doku) {
    // Fallback: web brightness(60%) hissini veren koyu dikey gradyan.
    const ImU32 ust = IM_COL32(28, 30, 40, 255), alt = IM_COL32(12, 13, 18, 255);
    dl->AddRectFilledMultiColor(sol, sagAlt, ust, ust, alt, alt);
    return;
  }
  // Cover: en-boyu koruyarak viewport'u tamamen kapla, taşan kenarı kırp (UV).
  const float hedefOran = boyut.x / std::max(boyut.y, 1.0f);
  const float gorselOran = kArkaGorselEn / kArkaGorselBoy;
  ImVec2 uv0{0, 0}, uv1{1, 1};
  if (hedefOran > gorselOran) {  // pencere daha geniş → dikeyden kırp
    const float k = gorselOran / hedefOran;
    uv0.y = (1.0f - k) * 0.5f;
    uv1.y = 1.0f - uv0.y;
  } else {  // pencere daha dar → yataydan kırp
    const float k = hedefOran / gorselOran;
    uv0.x = (1.0f - k) * 0.5f;
    uv1.x = 1.0f - uv0.x;
  }
  // Web efekt yığını = SADECE `filter: brightness(60%)` (overlay/vignette/blur YOK
  // — fiko-3d-deneme/.component.scss:26; fikoyt-ui varyantı yorumda). CSS brightness
  // sRGB uzayında çarpar; doku sRGB örneklenip lineerleştiğinden ImGui tint LİNEER
  // uzayda çarpar. sRGB takas zincirinde eşdeğer: t = 0.6^2.2 ≈ 0.323 → 82/255.
  dl->AddImage(s_doku, sol, sagAlt, uv0, uv1, IM_COL32(82, 82, 82, 255));
}

// Itme push-constant struct'ları + TUMU/floatBits → belge_ciz.cpp (MDI Dalga 2).

struct FareDurum {
  bool sol = false, orta = false, sag = false;
  f32 sonX = 0, sonY = 0;
  f32 solBasX = 0, solBasY = 0, ortaBasX = 0, ortaBasY = 0;
  f32 sagBasX = 0, sagBasY = 0;
  std::chrono::steady_clock::time_point solBasZaman, ortaBasZaman, sagBasZaman;
  bool uzunBasIslendi = false;
  bool kameraKullandi = false;  // bu sürüklemede kamera hareketi yapıldı mı (seçim/merkez baskıla)
  int kip = -1;                 // aktif kamera kipi: -1 yok, 0 döndür, 1 zoom, 2 pan (web durum makinesi)
};

// ============================================================================
// PencereOrtami — MDI Alt-proje 3 (docs mdi_blueprint / fikatia-mdi-alt3-plani):
// bir PENCERENİN tüm per-window UI durumu tek nesnede. calistir()'daki dev döngü
// gövdesi bare local'ları REFERANS-ALIAS ile bu nesneden alır (hizliDurum/sketchDurum
// deseninin aynısı) → gövde BİREBİR kalır, davranış değişmez. Stage 2'de belge+bglm+
// yardımcılar da buraya girer; Stage 3-4'te her belge penceresi kendi PencereOrtami'siyle
// aynı UI'yi (menü/toolbar/ağaç/panel) çalıştırır. Değer üye = pencere-başına sahiplik.
// ============================================================================
struct PencereOrtami {
  // Belge: bu pencerenin dökümanı (Cekirdek+Sahne+kamera+tüm sistemler). Pencere-başına
  // sahiplik — 2. belge penceresi kendi Belge'siyle. unique_ptr: PencereOrtami erken
  // default-construct edilir, belge shaderDizin hazır olunca kurulur (belgeKur).
  std::unique_ptr<Belge> belgePtr;
  // bglm: bu pencerenin UygulamaBaglam'ı (alt sistem + UI-state + helper referansları).
  // unique_ptr: UygulamaBaglam tüm-referans üyeli (default ctor yok) → belgeKur'da kurulur.
  // kareGovdesi/belgeKur helper'lara bunun üzerinden erişir (per-window doğru bağlanma).
  std::unique_ptr<UygulamaBaglam> bglmPtr;
  // Bu pencerenin ImGui katmanı (context+backend). Ana pencerede calistir'daki arayuz'a
  // işaret eder; ek pencereler kendi ImGuiKatman'ını sahiplenir (Stage: çok-pencere).
  ImGuiKatman* imguiPtr = nullptr;
  // Komut paleti (Ctrl+Space): universal (komut + sahne nesnesi) tek fuzzy aramada.
  // paletKayit komut kaynagini besler; universalArama iki kaynagi (Komut/Nesne) toplar.
  fiko::KomutKaydi paletKayit;
  fiko::ui::FikoUniversalArama universalArama;
  fiko::KomutPaleti komutPalet;
  u32 menuKimlik = 0;
  bool menuAc = false;
  HizliUIDurum hizliDurum;
  bool pickFiltreYedekVar = false;
  bool pickYedYuz = false, pickYedKenar = false, pickYedNokta = false, pickYedGovde = false;
  PusulaDurum pusula;
  KesitGizmoDurum kesitGizmo;
  SecimYigini secimYigini;  // CATIA "Other Selection" — Alt+tık aday yığını
  bool paletAcik = false, paletOdakla = false;
  bool hizliGorunumAcik = false;
  IsikDurum isikDurum;
  bool panelIsik = false, isikUstte = true;
  bool panelAgac = true, panelOzellik = false, panelOlay = false;
  bool agacIcinde = true;
  bool ozellikAcikOnceki = false;
  bool agacKaydirGerek = false;
  bool agacUstte = true, ozellikUstte = true, olayUstte = true, islerUstte = true,
       olcumUstte = true, camUstte = true;
  bool panelIsler = true;
  bool panelMakro = false, panelEklenti = false;
  SketchUIDurum sketchDurum;
  bool pah3DAcik = false, pah3DYuvarlat = false;
  char pah3DGiris[32] = "1.0";
  size_t oncekiIsSayi = 0;
  u32 olcumSurukle = 0;
  int olcumSurukleMod = 0;
  DugumPusula ozellikEski;
  char paletSorgu[128] = {};
  std::string durumMesaj;
  f32 durumMesajBitis = 0, simdikiZaman = 0;
  f32 sonEnBoy = 16.0f / 9.0f;
  f32 sonG = 1280, sonY_ = 720;
  uint64_t vpDoku = 0;
  VkImageView vpKayitliGorunum = VK_NULL_HANDLE;
  u32 istenenVpG = 1280, istenenVpY = 720;
  f32 vpRectMinX = 0, vpRectMinY = 0;
  bool vpHover = false;
  bool dockKuruldu = false;
  bool hoverBilgiAcik = true;
  bool hoverHudAcik = false;  // bağlam HUD (imleç-yanı "ne + ne yapabilirsin"); opsiyonel
  FareDurum fare;
};

// tipAdi / tipSimge / olayRengi → ui_ortak.hpp (fiko namespace; using ile erişilir).
// "Üstte tut" yardımcıları pencere_util.hpp'de (CAM paneli ile paylaşılır).

// dosyaSec / aptDosyaSec → dosya_islemleri.hpp (fiko namespace; using ile erişilir).

// ============================================================================
// FikoBelgePenceresi — OOP Dalga 1 (fikatia-oop-mimari-operasyonu / mdi-alt3):
// bir belge penceresinin TAMAMI (kendi Win32 penceresi + Vulkan bağlamı + ImGui
// katmanı + Belge + tüm UI durumu + komut/wb/olay kaydı) tek SINIF örneğinde.
// Eski calistir() god-function'ının per-window durumu ve gövdesi buraya taşındı;
// böylece "Yeni Belge" = yeni FikoBelgePenceresi örneği (gerçek ayrı pencere).
//
// TASARIM (member-alias trick): kur()/kareCiz() metod GÖVDELERİ eski calistir
// kodunun BİREBİR aynısı — yalnız kapsayan scope sınıf oldu. Setup-yerel yardımcı
// nesneler/lambda'lar ÜYE (jestSis_/g_araclar_/hatlar_/... ve helper lambda'lar) →
// komut lambda'ları `[&]` ile `this`'i yakalar; nesne pencere ömrü yaşadığı için
// dangling YOKTUR (transient-lambda yaklaşımının çözemediği sorun burada çözülür).
// Ana pencere de bir örnektir; ek pencereler kendi örneğini alır (çok-pencere).
// ============================================================================
class FikoBelgePenceresi {
 public:
  // Ana pencere (paylasilan=nullptr): kendi VulkanBaglam+BoruHatlari'nı yaratır+sahiplenir
  // (uygulama-global aygıt + ana yüzey). Ek pencere (paylasilan!=nullptr): AYNI VulkanBaglam'ı
  // ve BoruHatlari'nı PAYLAŞIR (ImGuiKatman global efekt/simge altyapısı tek aygıta bağlı →
  // ayrı aygıt ÇÖKME olur) + kendi PencereYuzeyi/ViewportHedef'ini yaratır (render retarget).
  FikoBelgePenceresi(int otoTestKare, bool senaryoDokum, bool anaMi,
                     VulkanBaglam* paylasilanVulkan, BoruHatlari* paylasilanHatlar)
      : otoTestKare_(otoTestKare), senaryoDokum_(senaryoDokum), anaMi_(anaMi),
        pencere_(pencereAyarKur(anaMi)),
        vulkanSahip_(paylasilanVulkan ? nullptr : std::make_unique<VulkanBaglam>(pencere_)),
        vulkan_(paylasilanVulkan ? *paylasilanVulkan : *vulkanSahip_),
        arayuz_(pencere_, vulkan_),
        shaderDizin_(sistem::calistirilabilirDizin() + "shader\\"),
        olayLogYol_(sistem::calistirilabilirDizin() + "fikoyt-olaylar.fikolog"),
        hatlarSahip_(paylasilanHatlar ? nullptr
                                      : std::make_unique<BoruHatlari>(vulkan_, shaderDizin_)),
        hatlar_(paylasilanHatlar ? *paylasilanHatlar : *hatlarSahip_) {
    // Ek pencere: kendi sunum yüzeyi + offscreen 3D hedefi (paylaşılan aygıta bağlı).
    if (!anaMi_) {
      yuzeySahip_ = std::make_unique<PencereYuzeyi>(vulkan_.aygitNesne(), pencere_);
      vpSahip_ = std::make_unique<ViewportHedef>(vulkan_);
    }
  }

  // Paylaşımlı kaynaklara erişim (ek pencere yaratımı için — ana pencereden alınır).
  VulkanBaglam& vulkanBaglam() { return vulkan_; }
  BoruHatlari& boruHatlari() { return hatlar_; }

  // kur(): eski setup (komut/wb/olay kaydı + helper'lar). Bir kez çağrılır.
  void kur();
  // baslat(): kare döngüsü öncesi tek-sefer init (zaman/senaryo durumu).
  void baslat();
  // kareCalistir(): bir kare (zaman + kareCiz + senaryo tail). false = bu pencere
  // çıkmak istiyor (oto-test bitti / aygıt kayıp / senaryo tamam / pencere kapandı).
  bool kareCalistir();
  // kareCiz(): eski kareGovdesi (bir kare çizim). return false = kare atlandı.
  bool kareCiz();
  // Aygıt idle bekle (kapanış öncesi kaynak yıkımı güvenli olsun diye).
  void bekle() { vulkan_.bekle(); }

  bool anaMi() const { return anaMi_; }
  bool kapatildi() const { return pencere_.kapatildi(); }

 private:
  static PencereAyar pencereAyarKur(bool anaMi) {
    PencereAyar ayar;
    // Sürüm tek kaynaktan (surum.hpp). SURUM_METIN ASCII → karakter-karakter genişlet.
    static const std::wstring anaBaslik =
        L"FikoYT Engineering Platform " +
        std::wstring(SURUM_METIN,
                     SURUM_METIN + std::char_traits<char>::length(SURUM_METIN));
    ayar.baslik = anaMi ? anaBaslik : L"FikoYT — Yeni Belge";
    return ayar;
  }

  // ---- CLI bayrakları ----
  int otoTestKare_ = 0;
  bool senaryoDokum_ = false;
  bool anaMi_ = true;

  // ---- Pencere altyapısı ----
  // pencere_ + arayuz_ HER örnekte kendine ait. VulkanBaglam+BoruHatlari yalnız ANA
  // pencerede yaratılır (uygulama-global aygıt); ek pencereler REFERANSLA paylaşır.
  Pencere pencere_;
  std::unique_ptr<VulkanBaglam> vulkanSahip_;  // yalnız ana pencerede dolu
  VulkanBaglam& vulkan_;                       // her örnek: paylaşılan/sahip olunan
  ImGuiKatman arayuz_;                         // pencere-başına ImGui context (multi-context)
  const std::string shaderDizin_;
  const std::string olayLogYol_;
  const std::string makroDizin_ = sistem::calistirilabilirDizin() + "makrolar";

  // ---- Render altyapısı ----
  std::unique_ptr<BoruHatlari> hatlarSahip_;   // yalnız ana pencerede dolu
  BoruHatlari& hatlar_;                        // paylaşılan (device-global pipeline'lar)
  // Ek pencere: kendi sunum yüzeyi + offscreen 3D hedefi (ana pencere vulkan_ üzerinden).
  std::unique_ptr<PencereYuzeyi> yuzeySahip_;  // yalnız ek pencerede dolu
  std::unique_ptr<ViewportHedef> vpSahip_;     // yalnız ek pencerede dolu
  CizimOnbellek anaCizim_;
  BelgeYonetici belgeYon_;  // ek belgeler (senaryo smoke; MDI Dalga 2 legacy)

  // ---- Per-window UI durumu + Belge + bglm (kur()'da doldurulur) ----
  PencereOrtami ort_;

  // ---- Girdi jest/hover/bağlam politika sistemleri ----
  girdi::FareJestSistemi jestSis_;
  girdi::SahneHoverProbu hoverProbu_;
  girdi::BaglamPolitika baglamPol_;
  girdi::PolitikaKarar sonPolitika_;
  ImVec2 sonPolitikaKonum_{};
  u32 sonPolitikaHedef_ = 0;
  u32 sonPolitikaHedefAlt_ = 0;  // hedefin alt ordinali (datum düzlem 4/5/6 veya yüz) — Eskiz Aç için
  bool baglamMenuAcildi_ = false;
  // Bağlam motoru sağ-tık halkası kaynağı (Dalga 3): motor önerilerinin DERİN kopyası;
  // sonPolitika_ öğelerinin simge işaretçileri BU tampona bakar (menü ömrü boyunca stabil).
  std::vector<baglam::BaglamOnerisi> sonMotorOneri_;

  // ---- Merkezi klavye kısayolu kaydı (BASİT/GLOBAL kısayollar) ----
  // Bağlamsal (sketch/CAM/ölçüm-moduna bağlı) kısayollar tusBasildi if-zincirinde
  // KALIR; yalnız mod-bağımsız globaller (Ctrl+Z/Ctrl+Y) burada merkezileşir.
  fiko::FikoKisayolKaydi kisayollar_;

  // ---- Dinamik araç çubuğu + ayarlar penceresi durumu ----
  ui::AracCubuguSistemi g_araclar_;
  ui::SeritCubugu g_serit_;  // dinamik durum-farkında alt şerit toolbar
  AyarlarPencereDurum ayarlarDurum_;

  // ---- Polimorfik panel yöneticileri (per-window). kur()'da kaydedilir, kareCiz()'de
  //      cizTumu() ile sürülür. İki grup (çizim SIRASI korunur, BİREBİR davranış):
  //   panelYon_    : erken paneller (Ürün Ağacı/Işık/Özellikler/Olay/Makro/Eklenti).
  //   panelYonGec_ : geç, bitişik batch (CAM Programı → Op → ParçaOp → Takım →
  //                  TakımEkseni → Feature → GSD → Analiz → Kesit → Döküm → CAM Sim).
  //   Batch-arası dağınık paneller (hizli3d/ayarlar/sketcher/sketchAraclari) İşlemler
  //   ve pah3D satır-içi bloklarıyla çevrili → yerlerinde tekil ciz() ile polimorfize.
  FikoPencereYonetici panelYon_;
  FikoPencereYonetici panelYonGec_;
  // Ctor-ref panelleri üye (kur()'da bağlanır, dağınık konumda ciz() çağrılır):
  std::unique_ptr<FikoHizli3dPanel> hizli3dPanel_;
  FikoSurecYoneticiPanel* surecYonPanel_ = nullptr;  // panelYon_ sahiplenir (forward drain besler)
  std::unique_ptr<FikoAyarlarPanel> ayarlarPanel_;
  std::unique_ptr<FikoSketcherPanel> sketcherPanel_;
  std::unique_ptr<FikoSketchAraclariPanel> sketchAraclariPanel_;

  // ---- Kare döngüsü zaman durumu ----
  std::chrono::steady_clock::time_point baslangic_;
  std::chrono::steady_clock::time_point oncekiKare_;
  f32 kareSuresiMs_ = 0.0f;
  int kare_ = 0;
  f32 dt_ = 0.0f, zaman_ = 0.0f;  // kareCiz için (loop'ta hesaplanır)
  std::chrono::steady_clock::time_point simdi_;  // bu karenin zaman damgası (basış süreleri)
  std::vector<Vec3f> hizliDunyaNokta_;  // özel-yön kütük tamponu

  // ---- Görsel oto-senaryo durumu (--senaryo-dokum) ----
  std::filesystem::path senaryoDizin_;
  int snAdim_ = -1;
  f32 snBekle_ = 0, snTimeout_ = 0;
  u32 snKutu_ = 0, snSim_ = 0;
  int snScrub_ = 0;
};

// ---- Pencere sürücüsü ----------------------------------------------------------
// "Yeni Belge" = yeni fikocad.exe SÜRECİ (süreç izolasyonu; aynı-süreç çok-pencere
// MDI'den vazgeçildi). Panel dışarı-taşıma ImGui multi-viewport ile (ViewportsEnable).

// Çökme teşhisi: en son geçilen kare-içi adım (SEH handler cokme.log'a yazar; tampon
// yok → çökmede kaybolmaz). Kare gövdesinde anahtar noktalarda güncellenir.
const char* g_sonAdim = "baslangic";

// Son kullanılan dosyalar (MRU) — UYGULAMA-GLOBAL (belge değil app düzeyi; tüm
// pencereler paylaşır). Startup'ta ayar'dan yüklenir (kur()), aç/kaydet'te güncellenir
// + ayar'a kalıcı yazılır. Universal palet "Dosya" kaynağını besler.
FikoSonDosyalar g_sonDosyalar;
bool g_sonDosyalarYuklendi = false;

// Lisans + komut denetim — UYGULAMA-GLOBAL (belge değil app düzeyi; tüm pencereler tek
// lisans + toplu kullanım takibi paylaşır). Her belge penceresi kur()'da g_lisans.bagla
// (kendi cekirdek'ini gate+takibe bağlar). Lisans uygulama-geneli olduğundan belge-başına DEĞİL.
LisansSistemi g_lisans;
bool g_lisansYuklendi = false;  // kümülatif kullanım ayar'dan bir kez yüklendi mi

// Otomatik güncelleme servisi — UYGULAMA-GLOBAL (sürüm altyapısı scaffold; §C).
// Dalga 1'de yalnız bagla + SemVer/manifest mantığı; network/indirme SONRAKİ dalga.
// Tek örnek guncelleme_kontrol.cpp'de (fiko::guncellemeServisi tekil); burada
// anon-namespace referans alias'ı — mevcut g_guncelleme kullanımları değişmez.
GuncellemeServisi& g_guncelleme = guncellemeServisi();

// Süreçler-arası merkezi komut köprüsü (app-global; her fikocad.exe bir örnek). Açılışta
// baslat, kare-başına drain → komutCalistir("ipc"), çıkışta durdur.
SurecKopru g_surecKopru;

// MRU'ya ekle + ayar'a kalıcı yaz (dosya aç/kaydet başarı yollarından çağrılır).
static void sonDosyaKaydet(const std::string& yol) {
  g_sonDosyalar.ekle(yol);
  ayarYaz("son_dosyalar", g_sonDosyalar.serialize());
}

int calistir(int otoTestKare, bool senaryoDokum) {
  // OOP Dalga 1 + MDI: pencereler vector'ü. [0] = ana pencere; "Yeni Belge" ek örnek.
  // Ana pencere VulkanBaglam+BoruHatlari'nı sahiplenir; ek pencereler paylaşır.
  // Açılış göstergesi: ağır init (Vulkan aygıt/pipeline/shader + ImGui font atlası
  // kurucuda; kur() = komut/tezgah + originKur; baslat() = kare-öncesi) ImGui
  // ilk-karesinden ÖNCE olduğundan hafif GDI splash gerçek adımlarla ilerletilir.
  // Oto-test/senaryo modunda gösterilmez (görünmez/otomatik akış).
  const bool splashGoster = otoTestKare == 0 && !senaryoDokum;
  // Oto-test/senaryo: her kare koşulsuz çizilmeli (deterministik akış + PNG dökümü);
  // on-demand uyku sonlandırmayı bozar → bu modlarda kapat.
  if (otoTestKare || senaryoDokum) kareIstekAc(false);
  AcilisSplash splash;
  if (splashGoster) {
    splash.goster();
    splash.ilerle(0.08f, "Vulkan aygıtı ve arayüz hazırlanıyor...");
  }
  std::vector<std::unique_ptr<FikoBelgePenceresi>> pencereler;
  pencereler.push_back(std::make_unique<FikoBelgePenceresi>(
      otoTestKare, senaryoDokum, /*anaMi=*/true, /*paylasilanVulkan=*/nullptr,
      /*paylasilanHatlar=*/nullptr));
  if (splashGoster) splash.ilerle(0.60f, "Komutlar ve çalışma tezgahı kuruluyor...");
  pencereler[0]->kur();
  if (splashGoster) splash.ilerle(0.85f, "Sahne hazırlanıyor...");
  pencereler[0]->baslat();
  if (splashGoster) {
    splash.ilerle(1.0f, "Hazır");
    splash.kapat();  // yumuşak fade-out + yok et (yalnız açılışta)
  }
  // Süreçler-arası merkezi komut köprüsü: bu süreci kaydet + komut sunucusunu başlat.
  g_surecKopru.baslat("Adsız");  // belge adı dosya aç/kaydet ile güncellenir (heartbeat yazar)

  // Ayar yüklemesi on-demand'ı açmış olabilir → oto-test/senaryo'da kesin kapat
  // (bu modlar her kareyi koşulsuz çizmeli; uyku deterministik akışı bozar).
  if (otoTestKare || senaryoDokum) kareIstekAc(false);

  int cikisKodu = 0;
  bool anaKapandi = false;
  auto sonrakiKare = std::chrono::steady_clock::now();  // FPS tavanı kadans zamanlayıcısı
  while (!anaKapandi) {
    // On-demand render: hiçbir pencere kare istemiyorsa girdiye kadar uyu. Girdi
    // (fare/klavye/boyut) anında uyandırır; gelmezse ~100 ms sonra bir yoklama karesi
    // (anim/sim/async durumu yeniden değerlendirilir → gerekirse tam hıza döner).
    // Boştaki GPU/pil yükünü 200+ FPS'ten düşürür; oto-test/senaryo'da devre dışı.
    if (kareIstekAcik() && kareIstek() == 0) {
      MsgWaitForMultipleObjectsEx(0, nullptr, 100, QS_ALLINPUT, 0);
      kareIste(1);  // en az bir yoklama karesi
    }

    // TEK Win32 mesaj pompası → thread'in TÜM pencerelerine dağıtır.
    Pencere::mesajPompala();

    // Her açık pencere için bir kare çiz. kareCalistir false → o pencere çıkmak ister.
    // GUARD: bir kare içindeki istisna (feature/render/pick) UYGULAMAYI ÇÖKERTMEZ — yakala,
    // logla, devam. (Aksi halde exception main loop'tan çıkar → stack-unwind teardown'da
    // std::thread → "resource deadlock". Bir kare atlanır, uygulama açık kalır.)
    for (auto& p : pencereler) {
      if (p->kapatildi()) continue;
      try {
        if (!p->kareCalistir()) {
          if (p->anaMi()) { anaKapandi = true; break; }
          // Ek pencere: kapanmayı işaretle (aşağıda vector'den çıkar).
        }
      } catch (const std::exception& e) {
        gunluk::hata("Kare istisnasi (yakalandi, devam): {}", e.what());
      } catch (...) {
        gunluk::hata("Kare bilinmeyen istisnasi (yakalandi, devam)");
      }
    }
    if (kareIstekAcik()) kareTuketildi();  // bir kare işlendi → istek bir azalır
    // Güncelleme "Kur & Yeniden Başlat" istendi → uygulamayı kapat (updater EN SON,
    // yıkım sonrası başlatılır; dosya kilitleri bırakılınca değiştirir + yeniden açar).
    if (g_guncelleme.kurulumIstendi()) anaKapandi = true;
    if (anaKapandi) break;

    // Kapatılan (WM_CLOSE) veya çıkmak isteyen ek pencereleri kaldır. Ana pencere
    // kapanınca döngü biter (uygulama çıkışı). Kapanan pencere yıkımı ~FikoBelgePenceresi.
    for (auto& p : pencereler)
      if (p->anaMi() && p->kapatildi()) anaKapandi = true;
    if (!anaKapandi) {
      std::erase_if(pencereler, [](const std::unique_ptr<FikoBelgePenceresi>& p) {
        if (p->anaMi()) return false;
        return p->kapatildi();
      });
    }

    // NOT: aynı-süreç "Yeni Belge" penceresi KALDIRILDI — yeni belge artık yeni
    // fikocad.exe süreci (Dosya → Yeni Belge komutu CreateProcessW).

    // FPS TAVANI: aktif renderde GPU'yu coşturmayı önle (idle zaten WaitMessage'la
    // ~10fps). sleep_until sabit kadans; geride kalınca sıfırla (spiral-of-death önle).
    // Oto-test/senaryo (kareIstekAcik()==false) sınırsız kalır.
    const int fps = hedefFps();
    if (kareIstekAcik() && fps > 0) {
      sonrakiKare += std::chrono::microseconds(1000000 / fps);
      const auto simdi = std::chrono::steady_clock::now();
      if (sonrakiKare > simdi) std::this_thread::sleep_until(sonrakiKare);
      else sonrakiKare = simdi;  // geride kaldık → kadansı sıfırla
    } else {
      sonrakiKare = std::chrono::steady_clock::now();
    }
  }

  g_sonAdim = "yikim: surecKopru.durdur";
  g_surecKopru.durdur();  // IPC sunucusunu durdur + kayıttan çık

  // Ana pencere kapandı → aygıtı idle bekle (güvenli yıkım).
  g_sonAdim = "yikim: pencere bekle";
  for (auto& p : pencereler) p->bekle();
  // WTEC kafa singleton'ının Vulkan kaynaklarını (ViewportHedef/Belge) aygıt canlıyken
  // ve VulkanBaglam (ana pencere) yıkılmadan ÖNCE serbest bırak (yıkım sırası kritik).
  g_sonAdim = "yikim: wtec kafa reset";
  wtecKafaDurum().hedef.reset();
  wtecKafaDurum().belge.reset();

  // Kümülatif komut kullanımını kalıcıya yaz (oturumlar arası telemetri; app-global tek yazım).
  g_sonAdim = "yikim: ayarYaz";
  ayarYaz("lisans_kullanim", g_lisans.kullanimSerialize());

  // YIKIM SIRASI KRİTİK: ek pencereler VulkanBaglam+BoruHatlari'nı ana pencereden
  // PAYLAŞIR (referans). Ana pencere (pencereler[0]) yıkılırsa paylaşılan aygıt gider
  // → ek pencerelerin ~ImGuiKatman/~PencereYuzeyi dangling olur. Bu yüzden önce
  // ek pencereleri (sondan başa), en son ana pencereyi yık.
  g_sonAdim = "yikim: ek pencereler pop_back";
  while (pencereler.size() > 1) pencereler.pop_back();  // ek pencereler önce
  g_sonAdim = "yikim: ana pencere clear (~FikoBelgePenceresi/~ImGuiKatman)";
  pencereler.clear();                                    // ana pencere en son

  // Güncelleme kurulumu EN SON: manuel "Kur" ya da Otomatik mod + doğrulanmış paket.
  // Vulkan/pencere yıkımı bitti, süreç çıkmak üzere → fiko_updater PID'imizi bekler,
  // kilitler bırakılınca dosyaları değiştirir + uygulamayı yeniden başlatır.
  if (g_guncelleme.kurulumIstendi() ||
      (g_guncelleme.durum() == GuncellemeServisi::Durum::Dogrulandi &&
       g_guncelleme.mod() == GuncellemeServisi::Mod::Otomatik &&
       !g_guncelleme.indirilenYol().empty())) {
    g_sonAdim = "yikim: guncelleme kur (fiko_updater)";
    guncellemeKurBaslat(g_guncelleme);
  }
  g_sonAdim = "yikim: tamamlandi (return)";
  return cikisKodu;
}

// ---- FikoBelgePenceresi::kur() — eski calistir setup gövdesi (BİREBİR) ---------
void FikoBelgePenceresi::kur() {
  // Üye altyapıya eski isimlerle alias (gövde bare isimleri değişmesin).
  const int otoTestKare = otoTestKare_;
  const bool senaryoDokum = senaryoDokum_;
  Pencere& pencere = pencere_;
  VulkanBaglam& vulkan = vulkan_;
  ImGuiKatman& arayuz = arayuz_;
  const std::string& shaderDizin = shaderDizin_;
  const std::string& olayLogYol = olayLogYol_;
  BoruHatlari& hatlar = hatlar_;
  CizimOnbellek& anaCizim = anaCizim_;
  BelgeYonetici& belgeYon = belgeYon_;
  girdi::FareJestSistemi& jestSis = jestSis_;
  girdi::SahneHoverProbu& hoverProbu = hoverProbu_;
  girdi::BaglamPolitika& baglamPol = baglamPol_;
  ui::AracCubuguSistemi& g_araclar = g_araclar_;
  ui::SeritCubugu& g_serit = g_serit_;
  (void)otoTestKare; (void)senaryoDokum; (void)hatlar; (void)anaCizim;

  PencereOrtami& ort = ort_;
  ort.imguiPtr = &arayuz;  // ana pencerenin ImGui katmanı

  // Kalıcı tema: son seçilen tema yeniden başlatmada geri yüklenir
  // (ImGuiKatman öntanımlı "Koyu" uygular; ayardan farklıysa üstüne seç).
  ui::temaSec(ayarOku("tema", "Koyu").c_str());

  // shaderDizin/olayLogYol/hatlar/anaCizim/belgeYon artık SINIF ÜYESİ (yukarıda
  // member-alias'landı) → burada yeniden tanımlanmaz. Viewport pipeline + ana
  // belgenin çizim önbelleği + BelgeYonetici pencere ömrü boyunca yaşar.

  // ---- BELGE (MDI Dalga 1b, docs/mdi_blueprint.md): dökümanın tüm durumu tek
  // nesnede (Cekirdek + Sahne + sistemler + kamera). Aşağıdaki referans takma-
  // adları gövdeyi DEĞİŞTİRMEDEN eski isimlerle çalıştırır; Dalga 2'de
  // BelgeYonetici N örnek + belge pencereleri bu nesneyi çoğaltır. -----------
  ort.belgePtr = std::make_unique<Belge>(vulkan, shaderDizin);
  Belge& belge = *ort.belgePtr;
  SecimGpu& secim = belge.secim;
  Cekirdek& cekirdek = belge.cekirdek;
  Sahne& sahne = belge.sahne;
  SecimSistemi& secimSis = belge.secimSis;
  OlcumSistemi& olcum = belge.olcum;
  AlanSecim& alan = belge.alan;
  betik::MakroMotor& makro = belge.makro;
  eklenti::EklentiYonetici& eklentiler = belge.eklentiler;
  cam::CamSistemi& camSis = belge.camSis;
  parca::PartDesignSistemi& partD = belge.partD;
  gsd::GsdSistemi& gsdSis = belge.gsdSis;
  analiz::AnalizSistemi& analizSis = belge.analizSis;
  DokumSistemi& dokumSis = belge.dokumSis;
  SahneRefSecim& refSecim = belge.refSecim;
  OrbitKamera& kamera = belge.kamera;
  const std::string& makroDizin = makroDizin_;  // sınıf üyesi
  FareDurum& fare = ort.fare;

  u32& menuKimlik = ort.menuKimlik;
  bool& menuAc = ort.menuAc;
  // "Hızlı 3D" ağırlık/kütük widget'ı (long-press ile açılır, nesneye özel)
  // Durum TEK struct'ta toplanır (HizliUIDurum → hizli3d_penceresi.hpp); aşağıdaki
  // bare local'lar üyelerine REFERANS bağlanır → dev döngü gövdesi AYNEN kalır,
  // pencere çizimi modüle taşınır (sketchDurum tasarım deseninin aynısı).
  HizliUIDurum& hizliDurum = ort.hizliDurum;
  bool& hizli3dAcik = hizliDurum.hizli3dAcik;
  u32& hizli3dKimlik = hizliDurum.hizli3dKimlik;
  // hizli3dUstte / hizliOzkutle yalnız pencere modülünce kullanılır (hizliDurum
  // üyesi) → main.cpp'de bare local referans yok (ölü referans üretmemek için).
  int& hizliKutukMod = hizliDurum.hizliKutukMod;       // 0=eksen-hizalı, 1=min-OBB, 2=özel yön
  bool& hizliKutukSahne = hizliDurum.hizliKutukSahne;  // hesaplanan kütüğü 3D sahnede tel-kafes göster
  f32& hizliKutukSaydam = hizliDurum.hizliKutukSaydam; // sahne kütük dolgu saydamlığı (0=tel, 0.6=dolu)
  int& hizliYonSecim = hizliDurum.hizliYonSecim;       // yön seçim modu: 0=kapalı, 1=1.yön bekliyor, 2=2.yön bekliyor
  // Pick filtresi yedeği: refSecim/camSecim/olcum modları filtreyi zorla ezer;
  // mod bitince kullanıcının 1/2/3/4 seçimi geri yüklenir (aksi halde sızar).
  bool& pickFiltreYedekVar = ort.pickFiltreYedekVar;
  bool& pickYedYuz = ort.pickYedYuz;
  bool& pickYedKenar = ort.pickYedKenar;
  bool& pickYedNokta = ort.pickYedNokta;
  bool& pickYedGovde = ort.pickYedGovde;
  Vec3f& hizliYon1 = hizliDurum.hizliYon1;   // seçilen yönler (dünya-uzay, yüz normali)
  Vec3f& hizliYon2 = hizliDurum.hizliYon2;
  bool& hizliYon1Var = hizliDurum.hizliYon1Var;
  bool& hizliYon2Var = hizliDurum.hizliYon2Var;
  Vec3f (&hizliKose)[8] = hizliDurum.hizliKose;
  bool& hizliKutuVar = hizliDurum.hizliKutuVar;
  Vec3f& hizliBoyut = hizliDurum.hizliBoyut;  // kare-başı hesap
  PusulaDurum& pusula = ort.pusula;
  KesitGizmoDurum& kesitGizmo = ort.kesitGizmo;  // 3D sürüklemeli kesit düzlemi tutamacı
  SecimYigini& secimYigini = ort.secimYigini;  // Selection Stack (Alt+tık aday yığını)
  bool& paletAcik = ort.paletAcik;
  bool& paletOdakla = ort.paletOdakla;
  bool& hizliGorunumAcik = ort.hizliGorunumAcik;
  // Dockable panel görünürlüğü (X ile kapanır, Pencereler menüsünden açılır).
  // Özellikler ayrı yüzen pencere (ana pencerenin üstünde), Olay Denetçi ve
  // Özellikler varsayılan kapalı — sürekli göz önünde olmasın.
  // ---- Işık durumu (IsikNesnesi → shader ModelItme). Varsayılanlar eski sabit
  // shader değerlerini birebir üretir; ışık pencereleri/ağaç bunları ayarlar. ----
  IsikDurum& isikDurum = ort.isikDurum;
  bool& panelIsik = ort.panelIsik;
  bool& isikUstte = ort.isikUstte;

  bool& panelAgac = ort.panelAgac;
  bool& panelOzellik = ort.panelOzellik;
  bool& panelOlay = ort.panelOlay;
  bool& agacIcinde = ort.agacIcinde;  // CATIA spec tree: ağaç 3B görünümün içinde (overlay)
  bool& ozellikAcikOnceki = ort.ozellikAcikOnceki;  // Özellikler penceresi açılış kenarı (öne getirme)
  // K2: Seçim değişince ürün ağacında seçili satıra bir kez kaydır (her kare değil).
  bool& agacKaydirGerek = ort.agacKaydirGerek;
  // Ayrık paneller için "üstte tut" (pin) durumları — varsayılan AÇIK: ayrık pencereler
  // ana pencerenin arkasına düşmesin (başlık çubuğu düğmesiyle kapatılabilir).
  bool& agacUstte = ort.agacUstte;
  bool& ozellikUstte = ort.ozellikUstte;
  bool& olayUstte = ort.olayUstte;
  bool& islerUstte = ort.islerUstte;
  bool& olcumUstte = ort.olcumUstte;
  bool& camUstte = ort.camUstte;
  // CAM üretim ağacı artık ANA Ürün Ağacı'nda yaşar (camAgacTopla sağlayıcı).
  bool& panelIsler = ort.panelIsler;          // İşlemler (görev ilerleme) penceresi görünürlüğü
  bool& panelMakro = ort.panelMakro;
  bool& panelEklenti = ort.panelEklenti;  // Makro Konsolu / Eklenti Yöneticisi
  // ---- Sketcher (2B eskiz) UI durumu → sketch_ui.hpp'ye taşındı (SketchUIDurum) ----
  // Durum TEK struct'ta toplanır; aşağıdaki bare local'lar SketchUIDurum üyelerine
  // REFERANS'tır → dev döngü gövdesi + tüm yardımcı lambda'lar AYNEN kalır (davranış
  // birebir), ama modüller (UygulamaBaglam::sketch) aynı durumu paylaşır.
  SketchUIDurum& sketchDurum = ort.sketchDurum;
  Varlik& aktifSketch = sketchDurum.aktifSketch;                 // düzenlenen sketch (Sketcher workbench)
  bool& panelSketch = sketchDurum.panelSketch;                   // Sketcher paneli görünürlüğü
  std::string& sketchArac = sketchDurum.sketchArac;              // "" | "cizgi" | "dikdortgen" | "daire" | "nokta"
  std::vector<std::array<f64, 2>>& sketchNoktalar = sketchDurum.sketchNoktalar;  // bekleyen tık uv'leri
  std::vector<u32>& sketchSecili = sketchDurum.sketchSecili;     // kısıt için seçilen eskiz geometri kimlikleri
  std::vector<u32>& sketchSeciliKisit = sketchDurum.sketchSeciliKisit;  // sahnede seçilen kısıt sembolleri
  // ---- CATIA tarzı Sketch Tools çekirdeği (SmartPick snap + canlı giriş) ----
  // NOT: grid/snap/yapı/giriş vb. UI durum alanları artık sketch_ui.cpp (SketchUIDurum)
  // üzerinden erişilir; yalnız main.cpp'de kalan (tık-dispatch/oto-kısıt) alanlara alias.
  eskiz::SnapSonuc& sonSnap = sketchDurum.sonSnap;               // son çözülen snap (tık-dispatch)
  bool& sketchYapiMod = sketchDurum.sketchYapiMod;              // çizilen eleman construction (sketchYeniEleman)
  bool& sketchOtoGeoKisit = sketchDurum.sketchOtoGeoKisit;      // oto geometrik kısıt (sketchOtoCakisik)
  int& sketchPolyKenar = sketchDurum.sketchPolyKenar;          // çokgen kenar sayısı (sketchAracTikSayi/Tamamla)
  // ---- 2B Düzenleme (Operations) durumu ----
  // sketchArac düzenleme moduna geçince (trim/fillet/ayna/otele/...) bu değişkenler
  // çoklu-tık akışını yürütür. sketchDuzenFaz: 0=eleman seç, 1=eksen/2.eleman/ref seç.
  int& sketchDuzenFaz = sketchDurum.sketchDuzenFaz;            // düzenleme akış fazı (araç bazlı)
  std::array<f64, 2>& sketchDuzenRef = sketchDurum.sketchDuzenRef;  // referans nokta (otele/döndür/ölçek/ofset)
  char (&sketchDuzGiris)[4][32] = sketchDurum.sketchDuzGiris;  // düzenleme değer metinleri (sketchDuzenSec temizler)
  // ---- 3B Pah / Kenar Yuvarlatma (CATIA Chamfer/Fillet) ----
  bool& pah3DAcik = ort.pah3DAcik;          // 3B pah/yuvarlat değer penceresi açık mı
  bool& pah3DYuvarlat = ort.pah3DYuvarlat;  // true=fillet(yarıçap), false=chamfer(mesafe)
  char (&pah3DGiris)[32] = ort.pah3DGiris;  // değer metni
  size_t& oncekiIsSayi = ort.oncekiIsSayi;  // yeni iş gelince pencereyi otomatik geri getir
  // SURUKLE_CANLI namespace-scope sabit (yukarıda) → burada yeniden tanımlanmaz.
  u32& olcumSurukle = ort.olcumSurukle;     // etiketi sürüklenen ölçüm kimliği (0=yok, SURUKLE_CANLI=canlı)
  int& olcumSurukleMod = ort.olcumSurukleMod;  // 0=ofset kaydır, 1=döndür (sağ sürükleme)

  // Özellik düzenleme undo: widget aktifleşince pusula alınır, bırakışta DuzenleIslem
  DugumPusula& ozellikEski = ort.ozellikEski;
  char (&paletSorgu)[128] = ort.paletSorgu;
  std::string& durumMesaj = ort.durumMesaj;
  f32& durumMesajBitis = ort.durumMesajBitis;
  f32& simdikiZaman = ort.simdikiZaman;

  // Kamera icin son karenin izdusum durumu (girdi geri cagrimlarindan erisim)
  f32& sonEnBoy = ort.sonEnBoy;
  f32& sonG = ort.sonG;
  f32& sonY_ = ort.sonY_;

  // Viewport (MDI 3D pencere) durumu
  uint64_t& vpDoku = ort.vpDoku;
  VkImageView& vpKayitliGorunum = ort.vpKayitliGorunum;
  u32& istenenVpG = ort.istenenVpG;
  u32& istenenVpY = ort.istenenVpY;
  f32& vpRectMinX = ort.vpRectMinX;
  f32& vpRectMinY = ort.vpRectMinY;
  bool& vpHover = ort.vpHover;
  bool& dockKuruldu = ort.dockKuruldu;

  // ---- Girdi jest / hover / bağlam politika sistemleri (A) ----
  // Anayasa: girdi modülü saf durum makinesi (sahne/ui bilmez). main her kare ham
  // girdiyi besler; sistemler tipli olaylar yayar. GİRDİ TÜKETİLMEZ → mevcut CATIA
  // kamera/seçim davranışı korunur (jest/hover yalnız gözlemcidir).
  // jestSis/hoverProbu/baglamPol SINIF ÜYESİ (yukarıda alias'landı); dinleyiciler
  // burada bağlanır (this yakalanır → nesne ömrü boyunca geçerli).
  // Hover değişince cekirdek olayı yay (VurguDegisti'ye ek: HedefTur soyutlaması).
  hoverProbu.dinle([&](const girdi::HoverBilgi& yeni, const girdi::HoverBilgi&) {
    kareIste(4);  // on-demand: hover değişimi anında çizilsin (highlight gecikmesin)
    cekirdek.olaylar().yayinla(
        HoverDegisti{yeni.govdeId, static_cast<u32>(yeni.tur)});
  });
  // Jest çözülünce olay yay + bağlam politikası (menü kararı) — çözüm main döngüde.
  girdi::PolitikaKarar& sonPolitika = sonPolitika_;  // jest listener doldurur; döngü çizer (guarded)
  ImVec2& sonPolitikaKonum = sonPolitikaKonum_;
  u32& sonPolitikaHedef = sonPolitikaHedef_;       // bağlam menüsünün hedef gövdesi (inline seç/gizle/sil)
  u32& sonPolitikaHedefAlt = sonPolitikaHedefAlt_; // hedef alt ordinali (datum düzlem/yüz → Eskiz Aç)
  bool& baglamMenuAcildi = baglamMenuAcildi_;  // bu jest bağlam menüsü açtı → legacy sahneMenu bastırılır
  jestSis.dinle([&](const girdi::FareJesti& j) {
    cekirdek.olaylar().yayinla(FareJestiOldu{
        static_cast<u32>(j.tur), static_cast<u32>(j.hedef.tur), j.hedef.govdeId});
    // Bağlam menüsü kararı: yalnız GUI üstünde değilken + GERÇEK sağ-tık/uzun-bas.
    // CATIA KORUMASI: Tik = kısa bas-birak (kamera döndürme SurukleBasla/Bitti üretir,
    // Tik ÜRETMEZ) → sağ+sürükle kamera menüyü AÇMAZ. karar saklanır; ana döngü guarded
    // çizer, jest sistemi girdiyi TÜKETMEZ (mevcut kamera/seçim davranışı korunur).
    // Orta tuş basılıyken (zoom/pan kamera) sağ-tık bağlam menüsü AÇILMASIN.
    // TUTARLILIK: normal sağ-Tik jest yolundan ÇIKARILDI → yalnız doğrudan yol (sahne.vurgu,
    // GARANTİ, tutarlı) açar. Jest zamanlaması "ara sıra farklı menü" yapıyordu. UzunBas kalır.
    if (!j.guiUstunde && !ImGui::IsMouseDown(ImGuiMouseButton_Middle) &&
        j.tur == girdi::JestTur::UzunBas) {
      // Dalga 3: sağ-tık halkası bağlam MOTORUNDAN beslenir (registry meta + tip/nesne).
      // Motorun zengin kapsadığı bağlamlar (gövde/boş/yüz/kenar/vertex) → motor; diğerleri
      // (datum/GSD/CAM/ölçüm/sketch) mevcut BaglamPolitika'ya düşer → REGRESYON YOK.
      // TUTARLILIK: fallback hedefi sahne.vurgu'dan kurulur (doğrudan sağ-tık yolu ile AYNI
      // kaynak) → "hover ile seç+sağtık farklı menü" tutarsızlığı biter. jest.hedef hover
      // zamanlaması yerine sahne.vurgu güvenilir çözümü kullanılır.
      girdi::FareJesti js = j;
      js.hedef.govdeId = sahne.vurgu.govde;
      // DATUM (referans düzlem / Origin eksen-sistemi / eksen) → Govde menüsü (Eskiz Aç).
      // Kip Kenar/Yüz olsa BİLE datum'a kenar/pah menüsü açılmaz (kip = filtre, tür değil).
      Dugum* vd = sahne.bul(sahne.vurgu.govde);
      const bool datumDugum = vd && (vd->tip == DugumTip::Duzlem || vd->tip == DugumTip::Eksen ||
                                     vd->tip == DugumTip::EksenSistemi ||
                                     vd->tip == DugumTip::Nokta || vd->tip == DugumTip::Cizgi);
      if (!sahne.vurgu.govde) {
        js.hedef.tur = girdi::HedefTur::Hic;
      } else if (datumDugum) {
        js.hedef.tur = girdi::HedefTur::Govde;
      } else {
        switch (sahne.kip()) {
          case SecimKip::Yuz:   js.hedef.tur = girdi::HedefTur::Yuz;   break;
          case SecimKip::Kenar: js.hedef.tur = girdi::HedefTur::Kenar; break;
          case SecimKip::Nokta: js.hedef.tur = girdi::HedefTur::Nokta; break;
          default:              js.hedef.tur = girdi::HedefTur::Govde; break;
        }
      }
      girdi::PolitikaKarar k;
      const auto& mk = belge.baglamMotoru.sonKarar();
      if (!datumDugum && baglamMotorKapsar(mk.hedef.kategori) && !mk.ogeler.empty()) {
        sonMotorOneri_ = mk.ogeler;  // derin kopya (simge işaretçileri buna bakar)
        baglamPolitikaYap(sonMotorOneri_, k);
      } else {
        k = baglamPol.karar(js, cekirdek.workbenchler().aktif());  // datum → politika (Eskiz Aç)
      }
      if (k.gecerli()) {
        // Menü zaten açıkken başka yere sağ-tık → yeni konumda YENİDEN aç.
        // Eski halka durumunu (açık alt-halka + animasyon) sıfırla, yoksa yeni menü
        // eski alt-halkayla gelir.
        ui::aracHalkasiSifirla("baglam");
        sonPolitika = std::move(k);
        sonPolitikaKonum = ImVec2{j.ekranNokta.x, j.ekranNokta.y};
        sonPolitikaHedef = sahne.vurgu.govde;  // inline eylemler için hedef gövde (tutarlı)
        sonPolitikaHedefAlt = sahne.vurgu.alt;  // datum düzlem 4/5/6 veya yüz ordinali (Eskiz Aç)
        baglamMenuAcildi = true;  // legacy sahneMenu bu tık için açılmasın
        // Tüm bağlamlar aracHalkasi (radyal, nested) ile çizilir; ayrı açılış gerekmez.
      }
    }
  });

  // ---- Dinamik araç çubuğu sistemi (B) ----
  // g_araclar SINIF ÜYESİ (yukarıda alias'landı); içeriği komut kaydından SONRA
  // (ana döngü öncesi) doldurulur (bkz. "g_araclar kurulum").
  bool& hoverBilgiAcik = ort.hoverBilgiAcik;  // sahne hover bilgi kartı görünürlüğü (Görünüm'den toggle)

  // Referans geometri (Origin: XY/YZ/ZX düzlemleri + X/Y/Z eksenleri) — her belgede
  // kök referans; sketch/feature bunlara bağlanır. Undo'ya girmez.
  sahne.originKur();
  // Açılış sahnesi BOŞ (yalnız Origin datum'ları + grid). Demo primitifler
  // (kutu/silindir/küre) kaldırıldı — kullanıcı tercihi. Ekleme komutları
  // (Geometri menüsü Kutu/Silindir/Küre) aynen durur.

  // Yerel helper lambda'ları bu blokta yaşar → bglm inşasında KOPYALANIR (kalıcı ort.bglm'e),
  // sonra blok kapanır. Bloktan sonra bglm.helper'a re-alias → komut'lar KALICI'yı yakalar
  // (belgeKur transient olsa da dangling YOK). boolIslem bilgiVer'e bağımlı → blok sonrası yeniden atanır.
  {
  const auto bilgiVer = [&](std::string m) {
    durumMesaj = std::move(m);
    durumMesajBitis = simdikiZaman + 4.0f;
  };
  const auto tumunuSigdir = [&] {
    Aabbf tum;
    for (auto& d : sahne.dugumler()) {
      if (!d.yerelSinir.gecerliMi()) continue;  // bos mesh (datum vb.) → NaN'a girme
      // Ekran-sabit referans (düzlem/eksen) fit'e girmez: görsel boyutu zoom'a bağlı
      // → dahil edilirse zoom-out sarmalı; ayrıca küçük parça sahnesinde baskın olur.
      if (d.tip == DugumTip::Duzlem || d.tip == DugumTip::Eksen ||
          d.tip == DugumTip::EksenSistemi || d.tip == DugumTip::Nokta) continue;
      const Vec3f m = d.dunyaMerkez();
      const f32 r = d.dunyaYaricap();
      tum.genislet(m - Vec3f{r, r, r});
      tum.genislet(m + Vec3f{r, r, r});
    }
    if (tum.gecerliMi()) kamera.sigdir(tum.merkez(), tum.kosegen() * 0.5f + 0.5f);
  };
  const auto secimiSigdir = [&] {
    const auto govdeler = sahne.seciliGovdeler();
    if (govdeler.empty()) return;
    Aabbf tum;
    for (u32 g : govdeler) {
      if (Dugum* d = sahne.bul(g); d && d->yerelSinir.gecerliMi()) {
        const Vec3f m = d->dunyaMerkez();
        const f32 r = d->dunyaYaricap();
        tum.genislet(m - Vec3f{r, r, r});
        tum.genislet(m + Vec3f{r, r, r});
      }
    }
    if (tum.gecerliMi()) kamera.sigdir(tum.merkez(), tum.kosegen() * 0.5f + 0.3f);
  };
  const auto seciliSil = [&] {
    const auto govdeler = sahne.seciliGovdeler();
    if (!govdeler.empty()) cekirdek.uygula(std::make_unique<SilIslem>(sahne, govdeler));
  };
  // K1: görünürlük tek yoldan — GorunumSaglayici.gorunurAyarla + GorunurlukDegisti
  // yayını (abone/makro/disk günlüğü tutarlı). Doğrudan d.gorunur yazımı YASAK.
  const auto gorunurDegistir = [&](u32 kimlik, bool deger) {
    GorunumSaglayici* gv = cekirdek.gorunum(kimlik);
    if (!gv) return;
    gv->gorunurAyarla(deger);
    const char* ad = "";
    if (DokumanNesnesi* n = cekirdek.nesne(kimlik)) ad = n->ad().c_str();
    cekirdek.olaylar().yayinla(GorunurlukDegisti{kimlik, deger}, ad);
  };
  const auto boolIslem = [&](DugumTip islem) {
    if (sahne.seciliGovdeler().size() != 2) {
      bilgiVer("Boolean için tam 2 gövde seçin (Ctrl+tık)");
      return;
    }
    cekirdek.uygula(std::make_unique<BooleanIslem>(sahne, islem));
  };
  // ---- Uygulama bağlamı: modül dosyalarının paylaştığı yerel durum görünümü ----
  // Üyeler main.cpp local'larına REFERANS bağlanır → tek doğruluk kaynağı burada;
  // dev döngü gövdesi aynen bare local'ları kullanır (davranış değişikliği yok).
  ort.bglmPtr = std::make_unique<UygulamaBaglam>(UygulamaBaglam{
      cekirdek, sahne, kamera, secimSis, olcum, alan, refSecim, makro, eklentiler,
      camSis, partD, gsdSis, analizSis, pencere,
      olayLogYol, makroDizin,
      panelOzellik, panelOlay, panelMakro, panelEklenti, ozellikAcikOnceki,
      ozellikUstte, olayUstte, ozellikEski,
      panelAgac, agacUstte, agacKaydirGerek, agacIcinde,
      isikDurum, panelIsik, isikUstte,
      sketchDurum,
      hizliDurum,
      vpRectMinX, vpRectMinY, istenenVpG, istenenVpY,
      hoverBilgiAcik,
      bilgiVer, sonDosyaKaydet,
      [](const std::string& ad) { g_surecKopru.belgeAdiAyarla(ad); },  // belgeAdiAyarla
      tumunuSigdir, secimiSigdir, gorunurDegistir,
      seciliSil, boolIslem});
  }  // yerel helper lambda'ları burada ölür (kopyaları ort.bglm'de yaşar)
  UygulamaBaglam& bglm = *ort.bglmPtr;
  // Helper'ları KALICI bglm kopyalarına re-alias'la: komut lambda'ları bunları [&] yakalar →
  // setup transient bir kapsamda (belgeKur) çalışsa bile ort.bglm'de yaşadıkları için dangling
  // OLMAZ (ort pencere ömrü boyunca yaşar). Ana pencerede davranış birebir (bglm.X == yerel lambda kopyası).
  auto& bilgiVer = bglm.bilgiVer;
  auto& tumunuSigdir = bglm.tumunuSigdir;
  auto& secimiSigdir = bglm.secimiSigdir;
  auto& seciliSil = bglm.seciliSil;
  auto& gorunurDegistir = bglm.gorunurDegistir;
  auto& boolIslem = bglm.boolIslem;
  // boolIslem yerel bilgiVer'i yakalamıştı (blokta öldü) → KALICI bglm.bilgiVer ile yeniden tanımla.
  bglm.boolIslem = [&bglm, &sahne, &cekirdek](DugumTip islem) {
    if (sahne.seciliGovdeler().size() != 2) {
      bglm.bilgiVer("Boolean için tam 2 gövde seçin (Ctrl+tık)");
      return;
    }
    cekirdek.uygula(std::make_unique<BooleanIslem>(sahne, islem));
  };
  // sketchGir sketchDikBak'a bağlı (aşağıda tanımlanır) → orada atanır.
  // Sketch mantık delegeleri sketchDikBak/sketchNesne (aşağıda tanımlanır) → orada bağlanır.

  // Dosya işlemleri (STEP/APT içe-dışa + .fkcache) dosya_islemleri.*'ye taşındı; komutlar kalıcı
  // bglm'i yakalar (thin lambda değil) → belgeKur transient olsa da dangling olmaz.

  // Tüm komutlar TEK registry'de (cekirdek.menuler): menü çubuğu, toolbar ve
  // komut paleti hep buradan beslenir — mükerrer tanım yok. Bir modül de aynı
  // MenuKaydi'ne MenuOge ekleyerek komut/araç katkılar (pluginable dikiş).
  // Ürün ağacı ECS hiyerarşisinden iç içe sürülür (grup + serbest geometri).
  cekirdek.agac().ekle(urunAgaciTopla);
  // CAM hiyerarşisi ana Ürün Ağacı'na sağlayıcı ile eklenir (AYRI ağaç yok).
  cekirdek.agac().ekle(
      [&camSis](Cekirdek& c, std::vector<AgacDugum>& kok) { camAgacTopla(c, camSis, kok); });
  // Kalıcı ölçümler (kumpas/cetvel) ana Ürün Ağacı'nda "Ölçümler" grubu olarak yaşar.
  cekirdek.agac().ekle(
      [&olcum](Cekirdek& c, std::vector<AgacDugum>& kok) { olcumAgacTopla(c, olcum, kok); });

  auto& menuK = cekirdek.menuler();
  // wb = "" → global (her workbench'te görünür); aksi halde yalnız o workbench aktifken.
  const auto komut = [&](const char* grup, const char* ad, const char* simge,
                         std::function<void(Cekirdek&)> fn, const char* wb = "",
                         std::function<bool(Cekirdek&)> etkinMi = nullptr,
                         const char* altGrup = "") {
    MenuOge o{grup, ad, simge, 0, std::move(fn), std::move(etkinMi), wb};
    o.altGrup = altGrup;  // dolu → alt şeritte flyout kümesi (kategorili/kompakt)
    menuK.ekle(std::move(o));
  };

  // ---- Durum-duyarlı etkinMi yardımcıları (KARARLI yakalama) ----
  // Menü/toolbar'da kur() sonrası çağrılır → YALNIZ heap-bağlı ref yakala:
  // sahne/aktifSketch member-ref'tir (kisitEtkin deseni). Yerel lambda YAKALAMA.
  const auto brepliGovdeVar = [&sahne]() -> bool {
    for (const Dugum& d : sahne.dugumler())
      if (d.brep && d.brep->gecerliMi()) return true;
    return false;
  };
  const auto etkinBrepli = [brepliGovdeVar](Cekirdek&) { return brepliGovdeVar(); };
  // Feature ön-koşulu: aktif (düzenlenen) VEYA seçili sketch (CATIA: sketch seç → Pad aktif).
  const auto etkinSketch = [&aktifSketch, &sahne](Cekirdek&) {
    return aktifSketch != VARLIK_YOK || sahne.seciliSketch() != VARLIK_YOK;
  };
  const auto etkinIkiGovde = [&sahne](Cekirdek&) { return sahne.seciliGovdeler().size() == 2; };
  const auto etkinSecimVar = [&sahne](Cekirdek&) { return !sahne.seciliGovdeler().empty(); };

  // ---- Workbench'ler (CATIA tarzı çalışma ortamları) ----
  // İlk eklenen otomatik aktif olur. Part Design = ana modelleme; Sketcher = 2D.
  cekirdek.workbenchler().ekle({"part", "Part Design", "⬡", nullptr, nullptr});
  cekirdek.workbenchler().ekle({"sketcher", "Sketcher", "✎", nullptr, nullptr});
  // CAM workbench: girişte panel açılır (CATIA Machining ortamı hissi).
  cekirdek.workbenchler().ekle(
      {"cam", "CAM", "⚙", [&camSis](Cekirdek&) { camSis.acik = true; }, nullptr});
  // GSD workbench: yüzey + tel-kafes modelleme (CATIA Generative Shape Design).
  cekirdek.workbenchler().ekle({"gsd", "Generative Shape Design", "▱", nullptr, nullptr});
  // Analiz workbench: draft/undercut/kalınlık/radius + kesit (kalıp/döküm analiz).
  cekirdek.workbenchler().ekle(
      {"analiz", "Analiz", "🔬", [&analizSis](Cekirdek&) { analizSis.acik = true; }, nullptr});
  // Döküm Simülasyonu workbench: entalpi-FDM katılaşma (Niyama/hot-spot).
  cekirdek.workbenchler().ekle(
      {"dokum", "Döküm Simülasyonu", "🔥", [&dokumSis](Cekirdek&) { dokumSis.acik = true; }, nullptr});

  // "Yeni Belge" = YENİ SÜREÇ (yeni fikocad.exe örneği). Süreç izolasyonu doğal scope:
  // kendi aygıtı/belgesi/penceresi — paylaşımlı-GPU çok-pencere karmaşası kökten gider.
  komut("Dosya", "Yeni Belge", "🗎", [](Cekirdek&) {
    wchar_t exeYol[MAX_PATH]{};
    GetModuleFileNameW(nullptr, exeYol, MAX_PATH);
    STARTUPINFOW si{};
    si.cb = sizeof(si);
    PROCESS_INFORMATION pi{};
    if (CreateProcessW(exeYol, nullptr, nullptr, nullptr, FALSE, 0, nullptr, nullptr,
                       &si, &pi)) {
      CloseHandle(pi.hThread);
      CloseHandle(pi.hProcess);
    }
  });
  komut("Dosya", "Aç (.fikocad)...", "📂", [&](Cekirdek&) { fiko::fikoAc(bglm); });
  komut("Dosya", "Kaydet (.fikocad)...", "💾", [&](Cekirdek&) { fiko::fikoKaydetKomut(bglm); });
  komut("Dosya", "STEP İçe Aktar...", "", [&](Cekirdek&) { fiko::stepAc(bglm); });
  komut("Dosya", "STEP Dışa Aktar...", "", [&](Cekirdek&) { fiko::stepKaydetKomut(bglm); }, "", etkinBrepli);
  komut("Dosya", "Dışa Aktar (STL/OBJ/IGES)...", "⭳",
        [&](Cekirdek&) { fiko::disaAktarKomut(bglm); }, "", etkinBrepli);
  komut("Dosya", "APT Aç...", "⭱", [&](Cekirdek&) { fiko::aptAc(bglm); });
  komut("Dosya", "APT Aktar...", "⭳", [&](Cekirdek&) { fiko::aptAktar(bglm); }, "", etkinBrepli);
  komut("Dosya", "Olay Günlüğü Yaz (.fikolog)", "", [&](Cekirdek& c) {
    c.olaylar().diskeYaz(olayLogYol);
    bilgiVer("Olay günlüğü yazıldı (.fikolog)");
  });
  // Geri Al / Yinele — etkinMi ile menüde durum-duyarlı; simge ile toolbar'da da.
  menuK.ekle({"Düzenle", "Geri Al", "↶", 0, [](Cekirdek& c) { c.geriAl(); },
              [](Cekirdek& c) { return c.komutlar().geriAlinabilir(); }});
  menuK.ekle({"Düzenle", "Yinele", "↷", 0, [](Cekirdek& c) { c.yinele(); },
              [](Cekirdek& c) { return c.komutlar().yinelenebilir(); }});
  const auto geoEkle = [&](DugumTip t) { cekirdek.uygula(std::make_unique<EkleIslem>(sahne, t)); };
  komut("Geometri", "Kutu", "◼", [&, geoEkle](Cekirdek&) { geoEkle(DugumTip::Kutu); }, "part");
  komut("Geometri", "Silindir", "▮", [&, geoEkle](Cekirdek&) { geoEkle(DugumTip::Silindir); }, "part");
  komut("Geometri", "Küre", "●", [&, geoEkle](Cekirdek&) { geoEkle(DugumTip::Kure); }, "part");
  komut("Geometri", "Koni", "▲", [&, geoEkle](Cekirdek&) { geoEkle(DugumTip::Koni); }, "part");
  komut("Boolean", "Birleştir", "∪", [&](Cekirdek&) { boolIslem(DugumTip::Birlesim); }, "part", etkinIkiGovde);
  komut("Boolean", "Çıkar (A−B)", "∖", [&](Cekirdek&) { boolIslem(DugumTip::Cikarma); }, "part", etkinIkiGovde);
  komut("Boolean", "Kesişim", "∩", [&](Cekirdek&) { boolIslem(DugumTip::Kesisim); }, "part", etkinIkiGovde);

  // WTEC Açılı Kafa mini-viewport paneli (üstte 3D kafa + altta B/C açı kontrolleri).
  komut("Araçlar", "🔧 WTEC Açılı Kafa", "🔧", [&](Cekirdek&) { wtecKafaAc(); });
  // GEÇİCİ TEST (VRML importer'ı ana sahnede doğrulama) — kaldırılacak.
  komut("Araçlar", "WTEC Kafa (ana sahne test)", "◆", [&](Cekirdek&) {
    sahne.vrmlIceAktar("D:/__sistem__/Masaustu/FikoYT.Yeni.Cag/fikatia-local/assets/3d/wtec-kafa/b_ust.wrl");
    sahne.vrmlIceAktar("D:/__sistem__/Masaustu/FikoYT.Yeni.Cag/fikatia-local/assets/3d/wtec-kafa/c_alt.wrl");
    tumunuSigdir();
  });

  // ---- Part Design feature'ları (HER FEATURE KENDİ PENCERESİNDE) ----
  // Hedef katı: önce seçili geçerli brep gövde, yoksa sahnedeki son geçerli katı (CATIA
  // InWork gibi). Çıkarma/kenar feature'ları hedef seçili olmasa da Pad gibi dialog açar.
  const auto hedefKatiBul = [&]() -> u32 {
    try {  // brep erişimi nadiren OSD_ThreadPool/mesh ile çakışıp deadlock atabilir → 0 dön
      for (u32 g : sahne.seciliGovdeler())
        if (Dugum* d = sahne.bul(g); d && d->brep && d->brep->gecerliMi()) return g;
      u32 son = 0;
      for (auto& d : sahne.dugumler())
        if (d.brep && d.brep->gecerliMi()) son = d.kimlik;
      return son;
    } catch (...) {
      return 0;  // hedef bulunamadı → feature yine de pencere açar (hata satırıyla)
    }
  };

  // Pad: aktif (dolu) sketch profilinden prizma katı. Pocket: aktif sketch +
  // seçili hedef katıdan boolean çıkarma. Taslak hemen sahnede önizlenir.
  komut("Feature", "Pad (Kabartma)", "⬒", [&](Cekirdek&) {
    const u32 k = partD.featureBaslat("pad", aktifSketch);  // aktif yoksa seçili sketch'e düşer
    if (k) featurePencereAc(k);
    else bilgiVer("Pad için bir sketch açın veya seçin (profil kapalı olabilir)");
  }, "part", etkinSketch, "olustur");
  komut("Feature", "Pocket (Cep)", "⬓", [&](Cekirdek&) {
    const u32 k = partD.featureBaslat("pocket", aktifSketch, hedefKatiBul());
    if (k) featurePencereAc(k);  // pencere her zaman açılır; eksik girdi pencere içinde tamamlanır
  }, "part", etkinSketch, "olustur");

  // Shaft (Döndür): aktif sketch profilini eksen etrafında döndürerek katı ekle.
  komut("Feature", "Shaft (Döndür)", "⟳", [&](Cekirdek&) {
    const u32 k = partD.featureBaslat("shaft", aktifSketch);  // aktif yoksa seçili sketch'e düşer
    if (k) featurePencereAc(k);
    else bilgiVer("Shaft için bir sketch açın veya seçin (profil kapalı olabilir)");
  }, "part", etkinSketch, "olustur");
  // Groove (Oyuk): aktif sketch + hedef katıdan döndürerek boolean çıkarma.
  komut("Feature", "Groove (Oyuk)", "◠", [&](Cekirdek&) {
    const u32 k = partD.featureBaslat("groove", aktifSketch, hedefKatiBul());
    if (k) featurePencereAc(k);
    else bilgiVer("Groove için kapalı bir sketch açın/seçin ve bir hedef katı bulunsun");
  }, "part", etkinSketch, "olustur");

  // Rib (Kaburga): aktif sketch profilini bir kılavuz eğri (center curve) boyunca süpürerek
  // katı EKLE. Profil = aktif/seçili sketch; kılavuz pencerede ayrıca seçilir (2. sketch).
  komut("Feature", "Rib (Kaburga)", "◤", [&](Cekirdek&) {
    const u32 k = partD.featureBaslat("rib", aktifSketch);  // aktif yoksa seçili sketch'e düşer
    if (k) featurePencereAc(k);
    else bilgiVer("Rib için kapalı bir profil sketch'i açın veya seçin");
  }, "part", etkinSketch, "olustur");
  // Slot (Kanal): Rib ile aynı süpürme ama hedef katıdan boolean ÇIKAR.
  komut("Feature", "Slot (Kanal)", "◥", [&](Cekirdek&) {
    const u32 k = partD.featureBaslat("slot", aktifSketch, hedefKatiBul());
    if (k) featurePencereAc(k);
    else bilgiVer("Slot için kapalı profil sketch'i ve bir hedef katı gerekli");
  }, "part", etkinSketch, "olustur");
  // Stiffener (Nervür): AÇIK profil kesitini kalınlaştırıp prizma → hedef katı ile Fuse.
  // Profil = aktif/seçili sketch (açık çizgi); hedef katı varsa birleşir, yoksa tek nervür.
  komut("Feature", "Stiffener (Nervür)", "◣", [&](Cekirdek&) {
    const u32 k = partD.featureBaslat("stiffener", aktifSketch, hedefKatiBul());
    if (k) featurePencereAc(k);
    else bilgiVer("Nervür için açık bir profil sketch'i açın veya seçin");
  }, "part", etkinSketch, "olustur");
  // Loft (Çok-Kesitli Katı): 1. kesit = aktif/seçili sketch; ek kesitler pencerede "➕ Kesit
  // Ekle" ile seçilir → kesitlerden geçen katı EKLE (ThruSections). En az 2 kesit gerekir.
  komut("Feature", "Loft (Çok-Kesitli)", "⧉", [&](Cekirdek&) {
    const u32 k = partD.featureBaslat("loft", aktifSketch);  // aktif yoksa seçili sketch'e düşer
    if (k) featurePencereAc(k);
    else bilgiVer("Loft için kapalı bir profil sketch'i açın veya seçin (ek kesitler pencerede)");
  }, "part", etkinSketch, "olustur");
  // Removed Loft (Çok-Kesitli Cep): Loft ile aynı ama hedef katıdan boolean ÇIKAR.
  komut("Feature", "Removed Loft (Çıkar)", "⧅", [&](Cekirdek&) {
    const u32 k = partD.featureBaslat("loft_cikar", aktifSketch, hedefKatiBul());
    if (k) featurePencereAc(k);
    else bilgiVer("Removed Loft için kapalı profil sketch'i ve bir hedef katı gerekli");
  }, "part", etkinSketch, "olustur");
  // Solid Combine (Kesişim Katı / CATIA Combine): 1. profil = aktif/seçili sketch; 2. profil
  // pencerede seçilir → her profil kendi normalinde prizmalanıp kesişir (öteleme kesişimi).
  komut("Feature", "Solid Combine (Kesişim)", "⧓", [&](Cekirdek&) {
    const u32 k = partD.featureBaslat("solid_combine", aktifSketch);  // aktif yoksa seçili sketch'e düşer
    if (k) featurePencereAc(k);
    else bilgiVer("Solid Combine için kapalı bir profil sketch'i açın/seçin (2. profil pencerede)");
  }, "part", etkinSketch, "olustur");
  // Hole (Delik): sketch GEREKMEZ; sayısal konum + seçili hedef katıdan çıkarma.
  komut("Feature", "Hole (Delik)", "◎", [&](Cekirdek&) {
    const u32 k = partD.featureBaslat("hole", VARLIK_YOK, hedefKatiBul());
    if (k) featurePencereAc(k);
    else bilgiVer("Hole için bir hedef katı gövde bulunsun");
  }, "part", etkinBrepli, "olustur");

  // Fillet/Chamfer (kenar) + Shell/Draft (yüz): sketch GEREKMEZ. Seçili katı gövde =
  // hedef; seçili alt-öğeler (kenar/yüz) ön-doldurulur, pencerede 🎯 ile eklenir/kaldırılır.
  // altPrefix="kenar"/"yuz", sayiKey="kenarSayi"/"yuzSayi" → parametrik feature.
  const auto cokluAltBaslat = [&](const char* tip, const char* etiketAd,
                                  const char* altPrefix, const char* sayiKey) {
    const u32 hedef = hedefKatiBul();
    const u32 k = partD.featureBaslat(tip, VARLIK_YOK, hedef);
    if (!k) { bilgiVer(std::string(etiketAd) + " için bir katı gövde bulunsun"); return; }
    // Hedefe ait seçili alt-öğeleri ön-doldur (kenar Kenar-kipinde, yüz Yüz-kipinde).
    int n = 0;
    for (const auto& o : sahne.secililer())
      if (o.alt > 0 && o.govde == hedef)
        partD.featureGuncelle(k, altPrefix + std::to_string(n++), (f64)o.alt);
    if (n) partD.featureGuncelle(k, sayiKey, (f64)n);
    featurePencereAc(k);
  };
  komut("Feature", "Fillet (Kenar Yuvarlat)", "◜", [&, cokluAltBaslat](Cekirdek&) {
    cokluAltBaslat("fillet", "Fillet", "kenar", "kenarSayi");
  }, "part", etkinBrepli, "cila");
  komut("Feature", "Chamfer (Pah)", "◺", [&, cokluAltBaslat](Cekirdek&) {
    cokluAltBaslat("chamfer", "Chamfer", "kenar", "kenarSayi");
  }, "part", etkinBrepli, "cila");
  // Fillet varyantları: Variable (tek kenar) + Face-Face / Tritangent (2-3 yüz).
  komut("Feature", "Variable Fillet (Değişken)", "◝", [&, cokluAltBaslat](Cekirdek&) {
    cokluAltBaslat("fillet_var", "Variable Fillet", "kenar", "kenarSayi");
  }, "part", etkinBrepli, "cila");
  komut("Feature", "Face-Face Fillet (Yüz-Yüz)", "◞", [&, cokluAltBaslat](Cekirdek&) {
    cokluAltBaslat("fillet_ff", "Face-Face Fillet", "yuz", "yuzSayi");
  }, "part", etkinBrepli, "cila");
  komut("Feature", "Tritangent Fillet (Üç-Teğet)", "◟", [&, cokluAltBaslat](Cekirdek&) {
    cokluAltBaslat("fillet_tri", "Tritangent Fillet", "yuz", "yuzSayi");
  }, "part", etkinBrepli, "cila");
  // Shell/Draft: yüz seçimli (Yüz kipinde ön-seçili yüzler ordinal olarak dolar; pencere
  // 🎯 Yüz Ekle beklenenTur=1 ile pick filtresini Yüze kilitler).
  komut("Feature", "Shell (İçi Boşalt)", "◳", [&, cokluAltBaslat](Cekirdek&) {
    cokluAltBaslat("shell", "Shell", "yuz", "yuzSayi");
  }, "part", etkinBrepli, "cila");
  komut("Feature", "Draft (Yüz Açısı)", "◿", [&, cokluAltBaslat](Cekirdek&) {
    cokluAltBaslat("draft", "Draft", "yuz", "yuzSayi");
  }, "part", etkinBrepli, "cila");
  // Draft Reflect Line (Yansıma Hattı): Draft ile aynı yüz seçimi; nötr eleman otomatik (yansıma hattı).
  komut("Feature", "Draft Reflect Line (Yansıma)", "◍", [&, cokluAltBaslat](Cekirdek&) {
    cokluAltBaslat("draft_reflect", "Draft Reflect Line", "yuz", "yuzSayi");
  }, "part", etkinBrepli, "cila");

  // ---- Sew Surface / Replace Face (Yüzey grubu): hedef katı + kaynak yüzey (2 gövde) ----
  // Hacme göre ayır: kapalı katı (hacim>0) = hedef; açık yüzey (hacim~0) = kaynak. Kaynak
  // yüzey kaynakDugum param'ına yazılır; Replace Face için hedefe ait seçili yüz yuz0'a ön-doldurulur.
  const auto yuzeyKaynakBaslat = [&](const char* tip, const char* etiketAd) {
    u32 kati = 0, yuzey = 0;
    for (u32 g : sahne.seciliGovdeler()) {
      Dugum* d = sahne.bul(g);
      if (!d || !d->brep || !d->brep->gecerliMi()) continue;
      if (d->brep->hacim() > 1e-6) { if (!kati) kati = g; }
      else if (!yuzey) yuzey = g;
    }
    if (!kati) kati = hedefKatiBul();
    if (!kati || !yuzey) {
      bilgiVer(std::string(etiketAd) + " için 1 katı + 1 açık yüzey seçin (Ctrl+tık)");
      return;
    }
    const u32 k = partD.featureBaslat(tip, VARLIK_YOK, kati);
    if (!k) { bilgiVer(std::string(etiketAd) + " başlatılamadı (hedef katı gerekli)"); return; }
    partD.featureGuncelle(k, "kaynakDugum", (f64)yuzey);
    // Replace Face: hedefe ait seçili yüzü (varsa) eski yüz olarak ön-doldur (side yönü için).
    int n = 0;
    for (const auto& o : sahne.secililer())
      if (o.alt > 0 && o.govde == kati) partD.featureGuncelle(k, "yuz" + std::to_string(n++), (f64)o.alt);
    if (n) partD.featureGuncelle(k, "yuzSayi", (f64)n);
    featurePencereAc(k);
  };
  komut("Yüzey", "Sew Surface (Yüzey Dik)", "◤", [&, yuzeyKaynakBaslat](Cekirdek&) {
    yuzeyKaynakBaslat("sew_surface", "Sew Surface");
  }, "part", etkinBrepli, "yuzey");
  komut("Yüzey", "Replace Face (Yüz Değiştir)", "▨", [&, yuzeyKaynakBaslat](Cekirdek&) {
    yuzeyKaynakBaslat("replace_face", "Replace Face");
  }, "part", etkinBrepli, "yuzey");

  // Pattern (Dik/Dairesel Dizi) + Mirror (Ayna): sketch/kenar/yüz seçimi GEREKMEZ.
  // Seçili katı gövde = hedef; sonuç kopyaların birleşimidir (Pad gibi düz param penceresi).
  const auto patternBaslat = [&](const char* tip, const char* etiketAd) {
    const u32 k = partD.featureBaslat(tip, VARLIK_YOK, hedefKatiBul());
    if (k) featurePencereAc(k);
    else bilgiVer(std::string(etiketAd) + " için bir katı gövde bulunsun");
  };
  komut("Feature", "Dik Dizi", "▦", [&, patternBaslat](Cekirdek&) {
    patternBaslat("pattern_dik", "Dik Dizi");
  }, "part", etkinBrepli, "cogalt");
  komut("Feature", "Dairesel Dizi", "◍", [&, patternBaslat](Cekirdek&) {
    patternBaslat("pattern_dairesel", "Dairesel Dizi");
  }, "part", etkinBrepli, "cogalt");
  // Kullanıcı Dizi (User Pattern): hedef katıyı bir konum sketch'inin NOKTA konumlarına kopyalar.
  // patternBaslat kalıbı (hedef katı + düz param penceresi); konum sketch'i pencerede seçilir.
  komut("Feature", "Kullanıcı Dizi", "✸", [&, patternBaslat](Cekirdek&) {
    patternBaslat("pattern_user", "Kullanıcı Dizi");
  }, "part", etkinBrepli, "cogalt");
  komut("Feature", "Ayna (Mirror)", "◪", [&, patternBaslat](Cekirdek&) {
    patternBaslat("mirror", "Ayna");
  }, "part", etkinBrepli, "cogalt");

  // Transformation (Öteleme/Döndürme/Simetri/Ölçek): sketch/seçim GEREKMEZ; seçili katı gövde =
  // hedef; sonuç dönüştürülmüş katıdır (orijinal gizlenir). patternBaslat kalıbı (hedef katı +
  // düz param penceresi). "donustur" flyout kümesi (CATIA Transformation Features grubu).
  komut("Feature", "Translation (Öteleme)", "➜", [&, patternBaslat](Cekirdek&) {
    patternBaslat("translation", "Öteleme");
  }, "part", etkinBrepli, "donustur");
  komut("Feature", "Rotation (Döndürme)", "⟲", [&, patternBaslat](Cekirdek&) {
    patternBaslat("rotation", "Döndürme");
  }, "part", etkinBrepli, "donustur");
  komut("Feature", "Symmetry (Simetri)", "◨", [&, patternBaslat](Cekirdek&) {
    patternBaslat("symmetry", "Simetri");
  }, "part", etkinBrepli, "donustur");
  komut("Feature", "Scaling (Ölçekleme)", "⤢", [&, patternBaslat](Cekirdek&) {
    patternBaslat("scaling", "Ölçekleme");
  }, "part", etkinBrepli, "donustur");

  // Thickness (Kalınlık): sketch/seçim GEREKMEZ; seçili katıyı uniform offset'ler (±).
  // patternBaslat kalıbı (hedef katı + düz param penceresi). "cila" flyout'ta (Shell yanı).
  komut("Feature", "Thickness (Kalınlık)", "◲", [&, patternBaslat](Cekirdek&) {
    patternBaslat("thickness", "Thickness");
  }, "part", etkinBrepli, "cila");

  // Boolean Bodies (Add/Remove/Intersect): 2 SEÇİLİ katı gövde arası parametrik boolean
  // (ürün ağacında feature; primitif "Boolean" grubundakiler anlık/undo, bunlar DAG). İlk
  // geçerli brep gövde = A (hedef), ikinci = B (ikinciGovde param). featureGuncelle B'yi
  // yazar → recompute; sonuç tek gövde, A/B gizlenir.
  const auto boolGovdeBaslat = [&](const char* tip, const char* etiketAd) {
    u32 a = 0, b = 0;
    for (u32 g : sahne.seciliGovdeler()) {
      if (Dugum* d = sahne.bul(g); d && d->brep && d->brep->gecerliMi()) {
        if (!a) a = g; else { b = g; break; }
      }
    }
    if (!a || !b) {
      bilgiVer(std::string(etiketAd) + " için 2 katı gövde seçin (Ctrl+tık)");
      return;
    }
    const u32 k = partD.featureBaslat(tip, VARLIK_YOK, a);
    if (!k) { bilgiVer(std::string(etiketAd) + " başlatılamadı"); return; }
    partD.featureGuncelle(k, "ikinciGovde", (f64)b);  // 2. gövde → yeniden hesapla
    featurePencereAc(k);
  };
  komut("Boolean", "Add (Gövde Birleştir)", "⊕", [&, boolGovdeBaslat](Cekirdek&) {
    boolGovdeBaslat("bool_add", "Add");
  }, "part", etkinIkiGovde, "govde");
  komut("Boolean", "Remove (Gövde Çıkar)", "⊖", [&, boolGovdeBaslat](Cekirdek&) {
    boolGovdeBaslat("bool_remove", "Remove");
  }, "part", etkinIkiGovde, "govde");
  komut("Boolean", "Intersect (Gövde Kesişim)", "⊗", [&, boolGovdeBaslat](Cekirdek&) {
    boolGovdeBaslat("bool_intersect", "Intersect");
  }, "part", etkinIkiGovde, "govde");
  // Union Trim (Birleşim-Kırp): 2 gövde Fuse + sonuç parçalarından seçili yüzü at (boolGovdeBaslat
  // kalıbı — A/B iki seçili gövde; kaldırılacak yüz pencerede fused sonuç üzerinde seçilir).
  komut("Boolean", "Union Trim (Birleşim-Kırp)", "⊍", [&, boolGovdeBaslat](Cekirdek&) {
    boolGovdeBaslat("union_trim", "Union Trim");
  }, "part", etkinIkiGovde, "govde");
  // Remove Lump (Parça Ayıkla): tek gövde birden çok ayrık katı parçaya bölünmüşse, seçili yüzün
  // ait olduğu parçayı tut/at (tek hedef katı; parça yüzü pencerede seçilir).
  komut("Boolean", "Remove Lump (Parça Ayıkla)", "⊟", [&, patternBaslat](Cekirdek&) {
    patternBaslat("remove_lump", "Remove Lump");
  }, "part", etkinBrepli, "govde");

  // ---- Referans Öğeler (CATIA Reference Elements): datum Nokta/Çizgi/Düzlem ----
  // Katı DEĞİL — feature'lara referans (Sketch düzlemi, Pad yönü, Mirror düzlemi).
  // Her komut ortak "Referans Öğe" penceresini ilgili sekmeyle açar.
  komut("Referans", "Nokta", "•", [](Cekirdek&) { referansPencereAc(0); }, "part", nullptr, "referans");
  komut("Referans", "Çizgi", "╱", [](Cekirdek&) { referansPencereAc(1); }, "part", nullptr, "referans");
  komut("Referans", "Düzlem", "◱", [](Cekirdek&) { referansPencereAc(2); }, "part", nullptr, "referans");

  // ---- Analiz WB komutları (her analiz KENDİ penceresinde) ----
  const auto analizHedefSec = [&]() -> u32 {
    for (u32 g : sahne.seciliGovdeler())
      if (Dugum* d = sahne.bul(g); d && d->brep && d->brep->gecerliMi()) return g;
    return 0;  // 0 = ilk görünür gövde (AnalizSistemi çözer)
  };
  // analizHedefSec kur()-yerel → DEĞERLE yakala (yalnız sahne heap-ref taşır → güvenli;
  // [&] dangling = 11 Analiz/Döküm komutu kareCiz'de 0xC0000005). analizEkleKomut de yerel
  // → tüketici lambda'lar [&, analizEkleKomut] ile onu DEĞERLE kopyalar (zincir kararlı).
  const auto analizEkleKomut = [&, analizHedefSec](analiz::AnalizTip tip) {
    const u32 k = analizSis.ekle(tip, analizHedefSec());
    if (k) { analizPencereAc(k); analizSis.acik = true; }
  };
  komut("Analiz", "Çekme Açısı", "◿",
        [&, analizEkleKomut](Cekirdek&) { analizEkleKomut(analiz::AnalizTip::Draft); }, "analiz", etkinBrepli);
  komut("Analiz", "Undercut", "⛏",
        [&, analizEkleKomut](Cekirdek&) { analizEkleKomut(analiz::AnalizTip::Undercut); }, "analiz", etkinBrepli);
  komut("Analiz", "Et Kalınlığı", "▤",
        [&, analizEkleKomut](Cekirdek&) { analizEkleKomut(analiz::AnalizTip::Kalinlik); }, "analiz", etkinBrepli);
  komut("Analiz", "Radius", "◜",
        [&, analizEkleKomut](Cekirdek&) { analizEkleKomut(analiz::AnalizTip::Radius); }, "analiz", etkinBrepli);
  komut("Analiz", "Kurvatür", "◠",
        [&, analizEkleKomut](Cekirdek&) { analizEkleKomut(analiz::AnalizTip::Kurvatur); }, "analiz", etkinBrepli);
  komut("Analiz", "Süreklilik", "≈",
        [&, analizEkleKomut](Cekirdek&) { analizEkleKomut(analiz::AnalizTip::Sureklilik); }, "analiz", etkinBrepli);
  komut("Analiz", "Zebra", "▦",
        [&, analizEkleKomut](Cekirdek&) { analizEkleKomut(analiz::AnalizTip::Zebra); }, "analiz", etkinBrepli);
  komut("Analiz", "Yansıma Hattı", "◍",
        [&, analizEkleKomut](Cekirdek&) { analizEkleKomut(analiz::AnalizTip::Reflect); }, "analiz", etkinBrepli);
  komut("Analiz", "Kesit Düzlemi", "✂", [&, analizHedefSec](Cekirdek&) {
    const u32 k = analizSis.kesitEkle(analizHedefSec());
    if (k) { kesitPencereAc(k); analizSis.acik = true; }
  }, "analiz", etkinBrepli);
  komut("Analiz", "Kesit Listesi", "≣", [&](Cekirdek&) {
    kesitListePencereAc();
    analizSis.acik = true;
  }, "analiz");

  // ---- Döküm Sim WB komutları ----
  komut("Döküm", "Döküm Sim Oluştur", "🔥", [&, analizHedefSec](Cekirdek&) {
    const u32 k = dokumSis.ekle(analizHedefSec());
    if (k) { dokumPencereAc(k); dokumSis.acik = true; }
  }, "dokum", etkinBrepli);
  komut("Döküm", "Döküm Sim Çalıştır", "▶", [&, analizHedefSec](Cekirdek&) {
    // Açık/son eklenen simülasyonu çöz; yoksa yeni oluştur + çöz.
    u32 k = dokumSis.hepsi().empty() ? dokumSis.ekle(analizHedefSec()) : dokumSis.hepsi().back();
    if (k) { dokumPencereAc(k); dokumSis.acik = true; dokumSis.cozumBaslat(k); }
  }, "dokum", etkinBrepli);

  // 3B Pah / Yuvarlatma (CATIA Chamfer/Fillet): kenar seç → değer gir → uygula.
  komut("İşlem", "Pah (3B)", "◺", [&](Cekirdek&) {
    if (sahne.kip() != SecimKip::Kenar) sahne.kipAyarla(SecimKip::Kenar);
    pah3DAcik = true; pah3DYuvarlat = false;
    bilgiVer("Kenar(lar) seçin → mesafe girip Uygula");
  }, "part");
  komut("İşlem", "Kenar Yuvarlat (3B)", "◜", [&](Cekirdek&) {
    if (sahne.kip() != SecimKip::Kenar) sahne.kipAyarla(SecimKip::Kenar);
    pah3DAcik = true; pah3DYuvarlat = true;
    bilgiVer("Kenar(lar) seçin → yarıçap girip Uygula");
  }, "part");

  // ---- GSD: HER FEATURE KENDİ PENCERESİNDE (gerçek OCCT B-Rep) ----
  // Wireframe (bağımsız): Nokta / Çizgi / Çember / Spline → TopoDS_Edge/Vertex.
  const auto gsdEkle = [&](const char* tip) {
    const u32 k = gsdSis.gsdBaslat(tip, VARLIK_YOK);
    if (k) gsdPencereAc(k);
    else bilgiVer("GSD öğesi başlatılamadı");
  };
  komut("Tel-Kafes", "Nokta", "•", [&, gsdEkle](Cekirdek&) { gsdEkle("nokta"); }, "gsd");
  komut("Tel-Kafes", "Çizgi", "╱", [&, gsdEkle](Cekirdek&) { gsdEkle("cizgi"); }, "gsd");
  komut("Tel-Kafes", "Çember", "◯", [&, gsdEkle](Cekirdek&) { gsdEkle("cember"); }, "gsd");
  komut("Tel-Kafes", "Spline", "∿", [&, gsdEkle](Cekirdek&) { gsdEkle("spline"); }, "gsd");
  // Yüzey (aktif sketch profili gerekli): Extrude / Revolve → açık shell.
  const auto gsdYuzeyEkle = [&](const char* tip) {
    const u32 k = gsdSis.gsdBaslat(tip, aktifSketch);  // aktif yoksa seçili sketch'e düşer
    if (k) gsdPencereAc(k);
    else bilgiVer("Yüzey için bir sketch açın veya seçin (profil geçerli mi?)");
  };
  komut("Yüzey", "Yüzey Extrude", "▱", [&, gsdYuzeyEkle](Cekirdek&) { gsdYuzeyEkle("yuzeyExtrude"); }, "gsd", etkinSketch);
  komut("Yüzey", "Yüzey Revolve", "◗", [&, gsdYuzeyEkle](Cekirdek&) { gsdYuzeyEkle("yuzeyRevolve"); }, "gsd", etkinSketch);
  komut("Görünüm", "💡 Işıklar", "💡", [&](Cekirdek&) { panelIsik = !panelIsik; });
  komut("Görünüm", "Seçimi Çerçevele", "", [&](Cekirdek&) { secimiSigdir(); });
  komut("Görünüm", "Tümünü Sığdır", "", [&](Cekirdek&) { tumunuSigdir(); });
  komut("Görünüm", "İzometrik", "", [&](Cekirdek&) { kamera.gorunumeGit(HizliGorunum::Izometrik); });
  komut("Görünüm", "Üst", "", [&](Cekirdek&) { kamera.gorunumeGit(HizliGorunum::Ust); });
  komut("Görünüm", "Ön", "", [&](Cekirdek&) { kamera.gorunumeGit(HizliGorunum::On); });
  komut("Görünüm", "Ortografik (O)", "",
        [&](Cekirdek&) { kamera.ortografikAyarla(!kamera.ortografik()); });
  komut("Düzenle", "Seçileni Sil", "", [&](Cekirdek&) { seciliSil(); }, "", etkinSecimVar);
  komut("Düzenle", "Seçimi Temizle", "", [&](Cekirdek&) { sahne.secimTemizle(); });
  komut("Ölçüm", "Ölçüm Penceresi (M)", "↔", [&](Cekirdek&) {
    olcum.acik = !olcum.acik;
    if (olcum.acik && olcum.kip == OlcumKip::Ayarlar) olcum.kip = OlcumKip::Cetvel;
  });
  komut("Ölçüm", "Kumpas (Tekli)", "", [&](Cekirdek&) { olcum.acik = true; olcum.kip = OlcumKip::Kumpas; olcum.secimSifirla(); });
  komut("Ölçüm", "Cetvel (İkili)", "", [&](Cekirdek&) { olcum.acik = true; olcum.kip = OlcumKip::Cetvel; olcum.secimSifirla(); });
  komut("Ölçüm", "Ölçümleri Temizle", "", [&](Cekirdek&) { olcum.temizle(); });
  komut("Görünüm", "✨ FikoUi Galerisi", "✨", [](Cekirdek&) { ui::galeriAcik() = !ui::galeriAcik(); });
  komut("Görünüm", "Hover Bilgi Kartı", "",
        [&](Cekirdek&) { hoverBilgiAcik = !hoverBilgiAcik; });
  komut("Görünüm", "Bağlam İpucu (HUD)", "",
        [&](Cekirdek&) { ort.hoverHudAcik = !ort.hoverHudAcik; });
  komut("Görünüm", "Perf HUD (FPS/Draw-call)", "",
        [](Cekirdek&) { perfGosterge().hudAcik = !perfGosterge().hudAcik; });
  komut("Makro", "Makro Konsolu", "◈", [&](Cekirdek&) { panelMakro = true; });
  komut("Makro", "Eklenti Yöneticisi", "◱", [&](Cekirdek&) { panelEklenti = true; });

  // ---- CAM (yalnız CAM workbench'te; komutlar undo/redo'lu Islem yolundan) ----
  const auto camEkle = [&](cam::CamIslemTip tip) {
    camSis.acik = true;
    cekirdek.uygula(std::make_unique<cam::CamIslemEkleIslem>(camSis, tip));
    if (cam::CamIslem* op = camSis.bul((u32)camSis.seciliKimlik)) {
      camSis.geometridenAta(*op);
      camSis.pencereAc(op->kimlik);  // yeni operasyon kendi penceresiyle acilir
    }
  };
  komut("CAM", "Profil", "▭", [&, camEkle](Cekirdek&) { camEkle(cam::CamIslemTip::Profil); }, "cam", etkinBrepli);
  komut("CAM", "Cep", "▣", [&, camEkle](Cekirdek&) { camEkle(cam::CamIslemTip::Cep); }, "cam", etkinBrepli);
  komut("CAM", "Delik", "◉", [&, camEkle](Cekirdek&) { camEkle(cam::CamIslemTip::Delik); }, "cam", etkinBrepli);
  komut("CAM", "3D Kaba", "≋", [&, camEkle](Cekirdek&) { camEkle(cam::CamIslemTip::Kaba3D); }, "cam", etkinBrepli);
  komut("CAM", "Kabadan Kalan", "⧉", [&, camEkle](Cekirdek&) { camEkle(cam::CamIslemTip::RestKaba); }, "cam", etkinBrepli);
  komut("CAM", "3D Finiş", "≡", [&, camEkle](Cekirdek&) { camEkle(cam::CamIslemTip::Finis3D); }, "cam", etkinBrepli);
  komut("CAM", "Sweep Finiş", "↝", [&, camEkle](Cekirdek&) { camEkle(cam::CamIslemTip::SweepFinis); }, "cam", etkinBrepli);
  komut("CAM", "Advanced Finiş", "◈", [&, camEkle](Cekirdek&) { camEkle(cam::CamIslemTip::AdvancedFinis); }, "cam", etkinBrepli);
  komut("CAM", "Kontur Güdümlü", "◠", [&, camEkle](Cekirdek&) { camEkle(cam::CamIslemTip::KonturGudumlu); }, "cam", etkinBrepli);
  komut("CAM", "Kalan Finiş (Pencil)", "✎", [&, camEkle](Cekirdek&) { camEkle(cam::CamIslemTip::ReworkFinis); }, "cam", etkinBrepli);
  komut("CAM", "Üretim Programı", "⚙", [&](Cekirdek&) { camSis.acik = !camSis.acik; }, "cam");
  komut("CAM", "Tümünü Hesapla", "⟳", [&](Cekirdek&) { camSis.kirlileriHesapla(); }, "cam");
  komut("CAM", "🎬 Simülasyon", "🎬", [&](Cekirdek&) { camSimPencereAc(0); }, "cam");
  komut("CAM", "⭳ APT Aktar", "⭳", [&](Cekirdek&) { fiko::aptAktar(bglm); }, "cam");
  komut("CAM", "🔧 Takım Tanımla", "🔧", [&](Cekirdek&) {
    // Yeni varsayilan takim ekle (numara = en buyuk + 1) → grafiksel pencerede ac.
    auto& lst = camSis.takimlar();
    int no = 1;
    for (const cam::Takim& t : lst) no = std::max(no, t.numara + 1);
    cam::Takim yeni;
    yeni.numara = no;
    yeni.ad = "Parmak Freze Ø10";
    yeni.boy = yeni.cap * 3.0;
    yeni.toplamBoy = yeni.cap * 6.0;
    lst.push_back(yeni);
    camTakimPencereAc(no);
  }, "cam");

  // ---- CATIA modeli (yeni CamOperasyon/PartOperasyon; her komut kendi penceresinde) ----
  const auto catOpEkle = [&](cam::CamIslemTipCatia tip) {
    panelAgac = true;  // CAM düğümleri ana Ürün Ağacı'nda görünür
    const u32 k = camSis.opEkle(tip);
    camOperasyonPencereAc(k);  // op kendi penceresini açar
  };
  komut("CAM", "◫ Part Operation Ekle", "▤", [&](Cekirdek&) {
    panelAgac = true;
    const u32 k = camSis.parcaOpEkle();
    camParcaOpPencereAc(k);  // makine ayarları penceresi açılır
  }, "cam");
  komut("CAM", "Üretim Ağacı", "🗂", [&](Cekirdek&) { panelAgac = true; }, "cam");
  komut("CAM", "Yüzey Tarama (Facing)", "▱", [&, catOpEkle](Cekirdek&) { catOpEkle(cam::CamIslemTipCatia::Facing); }, "cam");
  komut("CAM", "Cep (Pocketing)", "▣", [&, catOpEkle](Cekirdek&) { catOpEkle(cam::CamIslemTipCatia::Pocketing); }, "cam");
  komut("CAM", "Profil (Contouring)", "▭", [&, catOpEkle](Cekirdek&) { catOpEkle(cam::CamIslemTipCatia::Contouring); }, "cam");
  komut("CAM", "Eğri Takip", "◠", [&, catOpEkle](Cekirdek&) { catOpEkle(cam::CamIslemTipCatia::CurveFollow); }, "cam");
  komut("CAM", "Dairesel Freze", "◎", [&, catOpEkle](Cekirdek&) { catOpEkle(cam::CamIslemTipCatia::CircularMill); }, "cam");
  komut("CAM", "Delme (Drilling)", "◉", [&, catOpEkle](Cekirdek&) { catOpEkle(cam::CamIslemTipCatia::Drilling); }, "cam");
  komut("CAM", "Punta (Spot Drill)", "⊙", [&, catOpEkle](Cekirdek&) { catOpEkle(cam::CamIslemTipCatia::SpotDrill); }, "cam");
  komut("CAM", "Derin Delik (Peck)", "⇟", [&, catOpEkle](Cekirdek&) { catOpEkle(cam::CamIslemTipCatia::DeepHole); }, "cam");
  komut("CAM", "Kılavuz (Tapping)", "⎇", [&, catOpEkle](Cekirdek&) { catOpEkle(cam::CamIslemTipCatia::Tapping); }, "cam");
  komut("CAM", "Rayba (Reaming)", "⌾", [&, catOpEkle](Cekirdek&) { catOpEkle(cam::CamIslemTipCatia::Reaming); }, "cam");

  // ---- Sketcher ---- (Part'ta düzlem/düz yüzey seç → Sketch Oluştur; Sketcher'da Çık)
  // CATIA: seçili öğe bir REFERANS düzlem (DugumTip::Duzlem) ya da bir katının DÜZ
  // (planar) YÜZÜ olabilir. Düz yüzeyden geçici referans düzlem türetilir.
  // Dönüş: sketch düzlemi Varlik'i (yoksa VARLIK_YOK). XY'ye otomatik düşmez.
  const auto sketchDuzlemBul = [&]() -> Varlik {
    // 1) Doğrudan seçili referans düzlem.
    for (u32 g : sahne.seciliGovdeler())
      if (Dugum* d = sahne.bul(g); d && d->tip == DugumTip::Duzlem) return g;
    // 1b) CATIA Eksen Sistemi L-köşesi seçildiyse (alt 4/5/6 = XY/YZ/ZX düzlemi):
    //     gerçek referans DÜZLEM türet (sketchEkle bir DugumTip::Duzlem Varlik bekler).
    for (const SecimOge& o : sahne.secililer()) {
      Dugum* d = sahne.bul(o.govde);
      if (d && d->tip == DugumTip::EksenSistemi && eksensis::duzlemMi(o.alt))
        return sahne.eksenSistemiDuzlemi(o.alt);
    }
    // 2) Seçili düz yüzey (SecimOge: govde + alt=yüz ordinali, yuzBilgi.duzlemMi).
    for (const SecimOge& o : sahne.secililer()) {
      if (o.alt == 0) continue;  // yüz/kenar/nokta ögesi değil (gövde)
      Dugum* d = sahne.bul(o.govde);
      if (!d || !d->brep || !d->brep->gecerliMi()) continue;
      geometri::YuzBilgi yb;
      if (d->brep->yuzBilgi(o.alt, yb) && yb.duzlemMi)
        return sahne.yuzdenDuzlemEkle(o.govde, o.alt);  // düz yüzeyden düzlem türet
    }
    // 3) Sağ-tık "Eskiz Aç": öğe SEÇİLİ değil → bağlam menüsü hedefi (govde + alt ordinali).
    //    Referans düzlem / Origin eksen-sistemi düzlemi (alt 4/5/6) / düz yüz kabul edilir.
    if (sonPolitikaHedef) {
      if (Dugum* d = sahne.bul(sonPolitikaHedef)) {
        if (d->tip == DugumTip::Duzlem) return sonPolitikaHedef;
        if (d->tip == DugumTip::EksenSistemi && eksensis::duzlemMi(sonPolitikaHedefAlt))
          return sahne.eksenSistemiDuzlemi(sonPolitikaHedefAlt);
        if (d->brep && d->brep->gecerliMi() && sonPolitikaHedefAlt) {
          geometri::YuzBilgi yb;
          if (d->brep->yuzBilgi(sonPolitikaHedefAlt, yb) && yb.duzlemMi)
            return sahne.yuzdenDuzlemEkle(sonPolitikaHedef, sonPolitikaHedefAlt);
        }
      }
    }
    return VARLIK_YOK;
  };
  // sketchDuzlemBul kur()-yerel → BY-VALUE yakala (iç [&]'leri kararlı member; kur()
  // sonrası kareCiz'de çağrılınca dangling YOK; bglm alanı gerektirmez, [&, geoEkle] deseni).
  komut("Sketch", "Sketch Oluştur", "✎", [&, sketchDuzlemBul](Cekirdek& c) {
    const Varlik dz = sketchDuzlemBul();
    if (dz == VARLIK_YOK) { bilgiVer("Sketch için düzlemsel bir yüzey veya referans düzlem seçin."); return; }
    // Zaten açık bir sketch varsa önce onu kapat (araç/seçim durumu sızmasın).
    if (aktifSketch != VARLIK_YOK) {
      c.olaylar().yayinla(SketchCikildi{aktifSketch}, "Sketch");
      sketchArac.clear(); sketchNoktalar.clear(); sketchSecili.clear(); sketchSeciliKisit.clear(); sketchDuzenFaz = 0;
    }
    aktifSketch = sahne.sketchEkle(dz);
    c.workbenchler().aktifAyarla(c, "sketcher");
    c.olaylar().yayinla(SketchGirildi{aktifSketch}, "Sketch");
    panelSketch = true;
    // CATIA Normal View: kamerayı yeni sketch düzlemine dik + merkeze götür.
    if (Dugum* dzD = sahne.bul(dz)) {
      const auto& q = dzD->donusum.yonelim;
      kamera.yonelimeGit(Quatf{(f32)q.x, (f32)q.y, (f32)q.z, (f32)q.w});
      kamera.merkezle({(f32)dzD->donusum.konum.x, (f32)dzD->donusum.konum.y,
                       (f32)dzD->donusum.konum.z});
    }
    bilgiVer("Sketch oluşturuldu — Sketcher workbench aktif");
  }, "part");
  komut("Sketch", "Sketch'ten Çık", "✔", [&](Cekirdek& c) {
    if (aktifSketch != VARLIK_YOK) c.olaylar().yayinla(SketchCikildi{aktifSketch}, "Sketch");
    aktifSketch = VARLIK_YOK;
    sketchArac.clear();
    sketchNoktalar.clear();
    sketchSecili.clear();
    sketchSeciliKisit.clear();
    panelSketch = false;
    c.workbenchler().aktifAyarla(c, "part");
  }, "sketcher", etkinSketch);
  // CATIA Sketch Analysis: açık uç / açık profil / izole nokta + kısıt durumu tanısı.
  komut("Sketch", "Sketch Analizi", "🔬", [&](Cekirdek&) {
    sketchDurum.sketchAnalizAcik = !sketchDurum.sketchAnalizAcik;
  }, "sketcher", etkinSketch);

  // ---- Bağlam-farkındalık meta (Dalga 2): komut → uygun HedefKategori maskesi ----
  // Sağ-tık halkası + toolbar bağlam vurgusu bu meta'yı OKUR (TEK kaynak; mükerrer yok).
  // Yalnız yaygın komutlara verilir; kalanlar maske 0 (global) kalır → geriye uyumlu.
  {
    using baglam::HedefKategori;
    const u32 kenarM = baglam::maske(HedefKategori::KenarCizgi, HedefKategori::KenarYay,
                                     HedefKategori::KenarCember, HedefKategori::KenarSpline);
    const u32 yuzM = baglam::maske(HedefKategori::YuzDuz, HedefKategori::YuzSilindir,
                                   HedefKategori::YuzKonik, HedefKategori::YuzKure,
                                   HedefKategori::YuzEgri);
    const auto meta = [&](const char* grup, const char* ad, u32 m, u8 mn = 0, u8 mx = 255) {
      if (MenuOge* o = menuK.bul(grup, ad)) { o->hedefMaske = m; o->minSecim = mn; o->maxSecim = mx; }
    };
    meta("İşlem", "Kenar Yuvarlat (3B)", kenarM | yuzM);   // kenar/yüz seçimine uygulanır
    meta("İşlem", "Pah (3B)", kenarM | yuzM);
    meta("Feature", "Pad (Kabartma)", yuzM);               // yüz/sketch'ten kabartma
    meta("Feature", "Pocket (Cep)", yuzM);
    meta("Feature", "Fillet (Kenar Yuvarlat)", kenarM | yuzM);  // kenar seçimine uygulanır
    meta("Feature", "Chamfer (Pah)", kenarM | yuzM);
    meta("Feature", "Shell (İçi Boşalt)", yuzM);                // açık bırakılacak yüz seçimi
    meta("Feature", "Draft (Yüz Açısı)", yuzM);
    meta("Feature", "Dik Dizi", baglam::maske(HedefKategori::Govde));       // katı gövdeyi çoğaltır
    meta("Feature", "Dairesel Dizi", baglam::maske(HedefKategori::Govde));
    meta("Feature", "Ayna (Mirror)", baglam::maske(HedefKategori::Govde));
    meta("Sketch", "Sketch Oluştur", baglam::maske(HedefKategori::RefDuzlem, HedefKategori::YuzDuz));
    meta("Ölçüm", "Ölçüm Penceresi (M)", baglam::geometriHepsi());  // her geometride ölç
    meta("Boolean", "Birleştir", baglam::maske(HedefKategori::Govde), 2, 2);  // 2 gövde seçili
    meta("Boolean", "Çıkar (A−B)", baglam::maske(HedefKategori::Govde), 2, 2);
    meta("Boolean", "Kesişim", baglam::maske(HedefKategori::Govde), 2, 2);
  }

  // ---- Recompute tetikleyicileri (parametrik/link zinciri) ----
  // Geometri/parametre değişimi bağımlı nesneleri kirletir; kapanış kare sonunda
  // topolojik sırada çözülür (RecomputeMotor). kirlet(v) O(1); yayılım hesapla()'da.
  cekirdek.olaylar().abone<DonusumDegisti>(
      [&](const DonusumDegisti& e) { cekirdek.recompute().kirlet(e.varlik); });
  cekirdek.olaylar().abone<BrepDegisti>(
      [&](const BrepDegisti& e) { cekirdek.recompute().kirlet(e.varlik); });
  // Parametre değişince BAĞIMLI feature'lar da kirlenmeli → iki-argümanlı kirlet
  // (ters kenar BFS: param → onu formülde kullanan feature'lar). Tek-arg yalnız
  // param düğümünü kirletir, feature'a yayılmazdı (parametrik formül çalışmazdı).
  cekirdek.olaylar().abone<ParametreDegisti>(
      [&](const ParametreDegisti& e) { cekirdek.recompute().kirlet(cekirdek, e.varlik); });
  // CATIA associative update: Sketcher'dan ÇIKINCA, düzenlenen sketch'e bağlı feature
  // zinciri (Pad + sonrası) yeniden hesaplanır → Pad şekli güncellenir. Sketch düzenleme
  // (sürükle/geometri/kısıt) tek tek olay yayınlamaz; çıkış tek merkezi tetikleyicidir.
  cekirdek.olaylar().abone<SketchCikildi>(
      [&](const SketchCikildi& e) { partD.sketchGuncellendi(e.sketch); });
  // K2: Sahnede seçim değiştiğinde ürün ağacı seçili satıra bir kez kaydırsın.
  cekirdek.olaylar().abone<SecimDegisti>(
      [&](const SecimDegisti&) { agacKaydirGerek = true; });

  // Imlec altindaki dunya noktasi (odak duzlemi mesafesinde isin uzerinde)
  const auto imlecDunya = [&](f32 px, f32 py) -> Vec3f {
    const f32 ndcX = 2.0f * px / sonG - 1.0f;
    const f32 ndcY = 2.0f * py / sonY_ - 1.0f;
    const Mat4f invVP = (kamera.izdusum(sonEnBoy) * kamera.gorunum()).ters();
    const Vec4f yH = invVP * Vec4f{ndcX, ndcY, 0.0f, 1.0f};
    const Vec4f uH = invVP * Vec4f{ndcX, ndcY, 1.0f, 1.0f};
    const Vec3f yakin = yH.xyz() * (1.0f / yH.w);
    const Vec3f uzak = uH.xyz() * (1.0f / uH.w);
    const Vec3f dir = (uzak - yakin).birim();
    return yakin + dir * kamera.mesafe();  // orto'da kaynak = near nokta (paralel ışın)
  };

  // İmleç ışınını gerçek yüzeye atar; vuruş (gövde + dünya noktası + normal) döner.
  const auto imlecVurus = [&](f32 px, f32 py, Sahne::IsinVurus& vr) -> bool {
    const f32 ndcX = 2.0f * px / sonG - 1.0f;
    const f32 ndcY = 2.0f * py / sonY_ - 1.0f;
    const Mat4f invVP = (kamera.izdusum(sonEnBoy) * kamera.gorunum()).ters();
    const Vec4f yH = invVP * Vec4f{ndcX, ndcY, 0.0f, 1.0f};
    const Vec4f uH = invVP * Vec4f{ndcX, ndcY, 1.0f, 1.0f};
    const Vec3f yakinN = yH.xyz() * (1.0f / yH.w);
    const Vec3f uzakN = uH.xyz() * (1.0f / uH.w);
    return sahne.isinVur(yakinN, (uzakN - yakinN).birim(), vr);
  };
  // İmleç altındaki GERÇEK yüzeye ışın atar; vuruş varsa dünya noktasını verir.
  // Merkezleme (döndürme pivotu) ve tekerlek zoom hedefi için ortak (CATIA hissi).
  const auto imlecYuzey = [&, imlecVurus](f32 px, f32 py, Vec3f& cikti) -> bool {
    Sahne::IsinVurus vr;
    if (imlecVurus(px, py, vr)) { cikti = vr.nokta; return true; }
    return false;
  };

  // ---- Sketch düzlem eşleme (uv ↔ dünya) ----
  // Sketch düzlemin yerel XY'si = eskiz uv (z=0). Düzlem düğümü sahne.bul() ile
  // alınır; donusum bağlı düzlemin dünya konumu/yönelimidir.
  const auto sketchNesne = [&]() -> SketchNesnesi* {
    if (aktifSketch == VARLIK_YOK) return nullptr;
    return dynamic_cast<SketchNesnesi*>(cekirdek.nesne(aktifSketch));
  };
  // K5: sketch seçim havuzu değiştiğinde olay yayınla → makro/plugin gözlemleyebilir
  // (anayasa: her seçim yolu olay üretir). sketchSecili main-yerel olduğundan yayın
  // burada, mutasyon noktalarından çağrılır.
  const auto sketchSecimYay = [&] {
    cekirdek.olaylar().yayinla(
        SketchSecimDegisti{aktifSketch, static_cast<u32>(sketchSecili.size())}, "Sketch");
  };
  // CATIA "Normal View": kamerayı sketch düzlemine dik + merkez konumuna götür.
  // Kamera yönelimi = düzlem yönelimi → sketch XY'sine tam karşıdan bakılır.
  const auto sketchDikBak = [&](Varlik s) {
    auto* sk = dynamic_cast<SketchNesnesi*>(cekirdek.nesne(s));
    if (!sk) return;
    if (Dugum* dz = sahne.bul(sk->duzlem())) {
      const auto& q = dz->donusum.yonelim;
      kamera.yonelimeGit(Quatf{(f32)q.x, (f32)q.y, (f32)q.z, (f32)q.w});
      kamera.merkezle({(f32)dz->donusum.konum.x, (f32)dz->donusum.konum.y,
                       (f32)dz->donusum.konum.z});
    }
  };
  // Bir sketch'e gir (ürün ağacı/modüller çağırır): önceki açıksa kapat + araç
  // durumu temizle, sketcher workbench'e geç, Normal View. (Eski ürün-ağacı
  // çift-tık gövdesi buraya kapsüllendi — davranış birebir.)
  bglm.sketchGir = [&, sketchDikBak](Varlik v) {  // sketchDikBak yerel lambda → BY-VALUE
    if (aktifSketch != VARLIK_YOK && aktifSketch != v) {
      cekirdek.olaylar().yayinla(SketchCikildi{aktifSketch}, "Sketch");
      sketchArac.clear(); sketchNoktalar.clear(); sketchSecili.clear();
      sketchDuzenFaz = 0;
    }
    aktifSketch = v;
    cekirdek.workbenchler().aktifAyarla(cekirdek, "sketcher");
    cekirdek.olaylar().yayinla(SketchGirildi{v}, "Sketch");
    panelSketch = true;
    sketchDikBak(v);
  };
  // Düzlem-yerel (u,v) → dünya noktası (z=0 düzleminde).
  const auto uvDunya = [&](const Dugum* dz, f64 u, f64 v) -> Vec3f {
    const Vec3d w = dz->donusum.noktaUygula({u, v, 0.0});
    return {(f32)w.x, (f32)w.y, (f32)w.z};
  };
  // İmleç ışınını sketch düzlemiyle kes → düzlem-yerel (u,v). Işın imlecDunya
  // desenindeki gibi kurulur (ndc → invVP → yakın/uzak).
  const auto ekranUV = [&](const Dugum* dz, f32 px, f32 py, f64& u, f64& v) -> bool {
    const f32 ndcX = 2.0f * px / sonG - 1.0f;
    const f32 ndcY = 2.0f * py / sonY_ - 1.0f;
    const Mat4f invVP = (kamera.izdusum(sonEnBoy) * kamera.gorunum()).ters();
    const Vec4f yH = invVP * Vec4f{ndcX, ndcY, 0.0f, 1.0f};
    const Vec4f uH = invVP * Vec4f{ndcX, ndcY, 1.0f, 1.0f};
    const Vec3f yakin = yH.xyz() * (1.0f / yH.w);
    const Vec3f uzak = uH.xyz() * (1.0f / uH.w);
    const Vec3f dir = (uzak - yakin).birim();
    const Vec3d nd = dz->donusum.yonelim.dondur({0, 0, 1});
    const Vec3f normal{(f32)nd.x, (f32)nd.y, (f32)nd.z};
    const Vec3f duzP{(f32)dz->donusum.konum.x, (f32)dz->donusum.konum.y,
                     (f32)dz->donusum.konum.z};
    const f32 payda = normal.nokta(dir);
    if (std::abs(payda) < 1e-6f) return false;  // ışın düzleme paralel
    const f32 t = (duzP - yakin).nokta(normal) / payda;
    if (t < 0.0f) return false;  // düzlem arkada
    const Vec3f P = yakin + dir * t;
    const Vec3d L = dz->donusum.ters().noktaUygula({(f64)P.x, (f64)P.y, (f64)P.z});
    u = L.x; v = L.y;
    return true;
  };
  // Geometrinin uç noktası sayısı (coincidence taraması için: uc 0/1).
  const auto sketchUcSayi = [&](const eskiz::Geometri& g) -> int {
    if (g.tip == eskiz::GeoTip::Cizgi) return 2;
    if (g.tip == eskiz::GeoTip::Nokta || g.tip == eskiz::GeoTip::Daire) return 1;
    return 0;  // yay uçları şimdilik atlanır
  };
  // Coincidence kısıtı zaten var mı (aynı geo/uç çifti, sıra bağımsız).
  const auto sketchCakisikVar = [&](eskiz::EskizVeri& ev, u32 a, int au, u32 b, int bu) {
    for (const eskiz::Kisit& k : ev.kisitlar()) {
      if (k.tip != eskiz::KisitTip::Cakisik || k.geo.size() != 2) continue;
      const int k0 = k.nokta.size() > 0 ? k.nokta[0] : 0;
      const int k1 = k.nokta.size() > 1 ? k.nokta[1] : 0;
      if ((k.geo[0] == a && k0 == au && k.geo[1] == b && k1 == bu) ||
          (k.geo[0] == b && k0 == bu && k.geo[1] == a && k1 == au))
        return true;
    }
    return false;
  };
  // Yeni eklenen geometrinin uçları başka uçlara < tol yakınsa oto Çakışık kısıt.
  // Oto-kısıt STABİL ekleme: kısıt eklendikten sonra çözüm Çelişkili/Aşırı olursa
  // eklenen kısıt geri alınır → "kendi kendine bozan" oto-kısıt önlenir (profesyonel).
  const auto sketchGuvenliKisit = [](eskiz::EskizVeri& ev, u32 kimlik) {
    if (!kimlik) return;
    const auto d = ev.coz().durum;
    if (d == eskiz::Solver::Durum::Celiskili || d == eskiz::Solver::Durum::AsiriKisitli) {
      ev.kisitSil(kimlik);
      ev.coz();
    }
  };
  const auto sketchOtoCakisik = [&, sketchUcSayi, sketchCakisikVar, sketchGuvenliKisit](
                                    eskiz::EskizVeri& ev, u32 yeni) {
    if (!sketchOtoGeoKisit) return;
    const eskiz::Geometri* yg = ev.geoBul(yeni);
    if (!yg) return;
    const f64 tol = sketchDurum.sketchOtoCakisikTol;  // yalnız GERÇEKTEN çakışık uçlar (AyarSistemi: sketcher.oto_cakisik_tol)
    const int yn = sketchUcSayi(*yg);
    for (int i = 0; i < yn; ++i) {
      const f64 yx = ev.noktaX(yeni, i), yy = ev.noktaY(yeni, i);
      for (const eskiz::Geometri& o : ev.geometriler()) {
        if (o.kimlik == yeni) continue;
        const int on = sketchUcSayi(o);
        for (int j = 0; j < on; ++j) {
          const f64 ox = ev.noktaX(o.kimlik, j), oy = ev.noktaY(o.kimlik, j);
          if (std::hypot(yx - ox, yy - oy) < tol &&
              !sketchCakisikVar(ev, yeni, i, o.kimlik, j))
            sketchGuvenliKisit(ev, ev.kisitEkle(eskiz::KisitTip::Cakisik, {yeni, o.kimlik}, {i, j}, 0));
        }
      }
    }
  };
  // Yeni çizgi neredeyse yatay/dikey ise otomatik Yatay/Dikey kısıt (CATIA auto-constraint):
  // snap görsel hizalıyor ama kısıtsız çizgi sürüklenince bozulur → parametrik kalsın.
  const auto sketchOtoHV = [&, sketchGuvenliKisit](eskiz::EskizVeri& ev, u32 yeni) {
    if (!sketchOtoGeoKisit) return;
    const eskiz::Geometri* yg = ev.geoBul(yeni);
    if (!yg || yg->tip != eskiz::GeoTip::Cizgi || yg->yapi) return;
    const f64 dx = ev.noktaX(yeni, 1) - ev.noktaX(yeni, 0);
    const f64 dy = ev.noktaY(yeni, 1) - ev.noktaY(yeni, 0);
    const f64 uz = std::hypot(dx, dy);
    if (uz < sketchDurum.sketchOtoMinUzunluk) return;  // çok kısa segmentlere H/V ekleme (AyarSistemi: sketcher.oto_min_uzunluk)
    const f64 esik = sketchDurum.sketchOtoHVEsik;  // ~2° (sin) tolerans (AyarSistemi: sketcher.oto_hv_esik)
    if (std::abs(dy) / uz < esik)
      sketchGuvenliKisit(ev, ev.kisitEkle(eskiz::KisitTip::Yatay, {yeni}, {}, 0));
    else if (std::abs(dx) / uz < esik)
      sketchGuvenliKisit(ev, ev.kisitEkle(eskiz::KisitTip::Dikey, {yeni}, {}, 0));
  };
  // Yeni geometriye construction bayrağı + oto coincidence + oto H/V uygula.
  const auto sketchYeniEleman = [&, sketchOtoCakisik, sketchOtoHV](eskiz::EskizVeri& ev,
                                                                   u32 kimlik) {
    if (!kimlik) return;
    if (sketchYapiMod) ev.yapiAyarla(kimlik, true);
    sketchOtoCakisik(ev, kimlik);
    sketchOtoHV(ev, kimlik);
  };
  // sketchNoktalar biriktirilen tıklardan araç geometrisini eskiz'e ekler.
  const auto sketchAracTamamla = [&, sketchYeniEleman](eskiz::EskizVeri& ev) {
    const auto& np = sketchNoktalar;
    if (sketchArac == "cizgi" && np.size() == 2) {
      sketchYeniEleman(ev, ev.cizgiEkle(np[0][0], np[0][1], np[1][0], np[1][1]));
    } else if (sketchArac == "dikdortgen" && np.size() == 2) {
      const f64 x0 = np[0][0], y0 = np[0][1], x1 = np[1][0], y1 = np[1][1];
      sketchYeniEleman(ev, ev.cizgiEkle(x0, y0, x1, y0));
      sketchYeniEleman(ev, ev.cizgiEkle(x1, y0, x1, y1));
      sketchYeniEleman(ev, ev.cizgiEkle(x1, y1, x0, y1));
      sketchYeniEleman(ev, ev.cizgiEkle(x0, y1, x0, y0));
    } else if ((sketchArac == "daire" || sketchArac == "daireKoord") && np.size() == 2) {
      const f64 dx = np[1][0] - np[0][0], dy = np[1][1] - np[0][1];
      sketchYeniEleman(ev, ev.daireEkle(np[0][0], np[0][1], std::sqrt(dx * dx + dy * dy)));
    } else if ((sketchArac == "nokta" || sketchArac == "noktaKoord") && np.size() == 1) {
      sketchYeniEleman(ev, ev.noktaEkle(np[0][0], np[0][1]));
    } else if (sketchArac == "daire3" && np.size() == 3) {
      // 3 noktadan geçen çember: iki kenarın orta-dikmeleri kesişimi = merkez.
      const f64 ax = np[0][0], ay = np[0][1], bx = np[1][0], by = np[1][1];
      const f64 cx = np[2][0], cy = np[2][1];
      const f64 d = 2.0 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
      if (std::abs(d) < 1e-9) { sketchNoktalar.clear(); bilgiVer("Noktalar doğrusal — çember yok"); return; }
      const f64 a2 = ax * ax + ay * ay, b2 = bx * bx + by * by, c2 = cx * cx + cy * cy;
      const f64 mx = (a2 * (by - cy) + b2 * (cy - ay) + c2 * (ay - by)) / d;
      const f64 my = (a2 * (cx - bx) + b2 * (ax - cx) + c2 * (bx - ax)) / d;
      sketchYeniEleman(ev, ev.daireEkle(mx, my, std::hypot(ax - mx, ay - my)));
    } else if (sketchArac == "yay3" && np.size() == 3) {
      // 3 nokta: başlangıç / bitiş / üzerinden. Aynı çember matematiği + açılar.
      const f64 ax = np[0][0], ay = np[0][1], bx = np[1][0], by = np[1][1];
      const f64 cx = np[2][0], cy = np[2][1];
      const f64 d = 2.0 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
      if (std::abs(d) < 1e-9) { sketchNoktalar.clear(); bilgiVer("Noktalar doğrusal — yay yok"); return; }
      const f64 a2 = ax * ax + ay * ay, b2 = bx * bx + by * by, c2 = cx * cx + cy * cy;
      const f64 mx = (a2 * (by - cy) + b2 * (cy - ay) + c2 * (ay - by)) / d;
      const f64 my = (a2 * (cx - bx) + b2 * (ax - cx) + c2 * (bx - ax)) / d;
      const f64 r = std::hypot(ax - mx, ay - my);
      f64 a0 = std::atan2(ay - my, ax - mx);   // başlangıç açısı
      f64 a1 = std::atan2(by - my, bx - mx);   // bitiş açısı
      const f64 am = std::atan2(cy - my, cx - mx);  // üzerinden nokta açısı
      // a0→a1 arasında (CCW) am düşecek şekilde yönü seç.
      auto norm = [](f64 a) {
        constexpr f64 tau = 2.0 * std::numbers::pi;
        while (a < 0) a += tau;
        while (a >= tau) a -= tau;
        return a;
      };
      const f64 dA1 = norm(a1 - a0), dAm = norm(am - a0);
      if (dAm > dA1) std::swap(a0, a1);  // am aralık dışı → ters yön (a1→a0)
      sketchYeniEleman(ev, ev.yayEkle(mx, my, r, a0, a1));
    } else if (sketchArac == "merkezYay" && np.size() == 3) {
      // Merkez-tabanlı yay (CATIA Arc): 1=merkez, 2=başlangıç (yarıçap+açı),
      // 3=bitiş açısı. yay3'ten FARKLI (o 3-noktadan-geçen). Süpürme CCW.
      const f64 cx = np[0][0], cy = np[0][1];
      const f64 r = std::hypot(np[1][0] - cx, np[1][1] - cy);
      if (r < 1e-9) { sketchNoktalar.clear(); bilgiVer("Yay yarıçapı sıfır"); return; }
      const f64 a0 = std::atan2(np[1][1] - cy, np[1][0] - cx);
      f64 a1 = std::atan2(np[2][1] - cy, np[2][0] - cx);
      constexpr f64 tau = 2.0 * std::numbers::pi;
      while (a1 <= a0) a1 += tau;  // başlangıç→bitiş pozitif (CCW) süpürme
      sketchYeniEleman(ev, ev.yayEkle(cx, cy, r, a0, a1));
    } else if (sketchArac == "elips" && np.size() == 3) {
      // 1: merkez, 2: büyük eksen ucu (a + dönme), 3: küçük eksen (merkeze dik mesafe).
      const f64 cx = np[0][0], cy = np[0][1];
      const f64 ex = np[1][0] - cx, ey = np[1][1] - cy;
      const f64 a = std::hypot(ex, ey);
      const f64 aci = std::atan2(ey, ex);
      // 3. tık'ın büyük eksene DİK mesafesi = b (|perp bileşeni|).
      const f64 px = np[2][0] - cx, py = np[2][1] - cy;
      const f64 b = std::abs(-px * std::sin(aci) + py * std::cos(aci));
      if (a < 1e-6 || b < 1e-6) { sketchNoktalar.clear(); bilgiVer("Elips ekseni sıfır"); return; }
      sketchYeniEleman(ev, ev.elipsEkle(cx, cy, a, b, aci));
    } else if (sketchArac == "slot" && np.size() == 3) {
      // 1-2: eksen merkezleri, 3: genişlik (2. merkeze uzaklık = yarıçap).
      const f64 r = std::hypot(np[2][0] - np[1][0], np[2][1] - np[1][1]);
      if (r < 1e-6) { sketchNoktalar.clear(); bilgiVer("Slot yarıçapı sıfır"); return; }
      for (u32 k : ev.slotEkle(np[0][0], np[0][1], np[1][0], np[1][1], r))
        sketchYeniEleman(ev, k);
    } else if (sketchArac == "egriselSlot" && np.size() == 4) {
      // CATIA Cylindrical Elongated Hole: 1=merkez, 2=yay başlangıcı (r+a0),
      // 3=yay bitişi (a1, CCW), 4=genişlik noktası (eksene radyal uzaklık = g).
      const f64 cx = np[0][0], cy = np[0][1];
      const f64 r = std::hypot(np[1][0] - cx, np[1][1] - cy);
      if (r < 1e-6) { sketchNoktalar.clear(); bilgiVer("Eksen yayı yarıçapı sıfır"); return; }
      const f64 a0 = std::atan2(np[1][1] - cy, np[1][0] - cx);
      f64 a1 = std::atan2(np[2][1] - cy, np[2][0] - cx);
      constexpr f64 tau = 2.0 * std::numbers::pi;
      while (a1 <= a0) a1 += tau;  // başlangıç→bitiş pozitif (CCW) süpürme
      const f64 g = std::abs(std::hypot(np[3][0] - cx, np[3][1] - cy) - r);
      if (g < 1e-6 || g >= r) { sketchNoktalar.clear(); bilgiVer("Genişlik geçersiz (0 < g < eksen yarıçapı)"); return; }
      for (u32 k : ev.egriselSlotEkle(cx, cy, r, a0, a1, g)) sketchYeniEleman(ev, k);
    } else if (sketchArac == "anahtarDeligi" && np.size() == 3) {
      // CATIA Keyhole Profile: 1=büyük merkez, 2=küçük merkez, 3=küçük yarıçap
      // noktası (küçük merkeze uzaklık = r). Büyük R = 2×r (CATIA varsayılan).
      const f64 bxA = np[0][0], byA = np[0][1], kxA = np[1][0], kyA = np[1][1];
      const f64 rK = std::hypot(np[2][0] - kxA, np[2][1] - kyA);
      if (rK < 1e-6) { sketchNoktalar.clear(); bilgiVer("Küçük yarıçap sıfır"); return; }
      const f64 RB = 2.0 * rK;
      const f64 d = std::hypot(kxA - bxA, kyA - byA);
      if (d <= RB - rK + 1e-6) { sketchNoktalar.clear(); bilgiVer("Merkezler çok yakın (daireler iç içe)"); return; }
      for (u32 k : ev.anahtarDeligiEkle(bxA, byA, kxA, kyA, rK, RB))
        sketchYeniEleman(ev, k);
    } else if (sketchArac == "cokgen" && np.size() == 2) {
      // 1: merkez, 2: çevrel yarıçap. Kenar sayısı sketchPolyKenar (panel/canlı giriş).
      const f64 r = std::hypot(np[1][0] - np[0][0], np[1][1] - np[0][1]);
      if (r < 1e-6) { sketchNoktalar.clear(); bilgiVer("Çokgen yarıçapı sıfır"); return; }
      const int kn = std::clamp(sketchPolyKenar, 3, 20);
      for (u32 k : ev.cokgenEkle(np[0][0], np[0][1], r, kn))
        sketchYeniEleman(ev, k);
    } else if (sketchArac == "merkezliDikdortgen" && np.size() == 2) {
      // 1: merkez, 2: köşe → yarı-en/yarı-boy = |dx|/|dy|.
      const f64 yEn = std::abs(np[1][0] - np[0][0]), yBoy = std::abs(np[1][1] - np[0][1]);
      if (yEn < 1e-6 || yBoy < 1e-6) { sketchNoktalar.clear(); bilgiVer("Dikdörtgen boyutu sıfır"); return; }
      for (u32 k : ev.merkezliDikdortgenEkle(np[0][0], np[0][1], yEn, yBoy))
        sketchYeniEleman(ev, k);
    } else if (sketchArac == "yonluDikdortgen" && np.size() == 3) {
      // 1-2: taban doğrultusu, 3: yükseklik (tabana işaretli dik mesafe) — CATIA Oriented Rect.
      const f64 x0 = np[0][0], y0 = np[0][1], x1 = np[1][0], y1 = np[1][1];
      const f64 dx = x1 - x0, dy = y1 - y0, L = std::hypot(dx, dy);
      if (L < 1e-6) { sketchNoktalar.clear(); bilgiVer("Taban sıfır"); return; }
      const f64 h = (np[2][0] - x0) * (-dy / L) + (np[2][1] - y0) * (dx / L);  // sol normal işaretli
      if (std::abs(h) < 1e-6) { sketchNoktalar.clear(); bilgiVer("Yükseklik sıfır"); return; }
      for (u32 k : ev.yonluDikdortgenEkle(x0, y0, x1, y1, h)) sketchYeniEleman(ev, k);
    } else if (sketchArac == "paralelkenar" && np.size() == 3) {
      // 1: köşe, 2: 1. kenar ucu, 3: 2. kenar ucu — CATIA Parallelogram.
      const f64 x0 = np[0][0], y0 = np[0][1];
      const f64 v1x = np[1][0] - x0, v1y = np[1][1] - y0;
      const f64 v2x = np[2][0] - x0, v2y = np[2][1] - y0;
      if (std::hypot(v1x, v1y) < 1e-6 || std::hypot(v2x, v2y) < 1e-6) {
        sketchNoktalar.clear(); bilgiVer("Kenar sıfır"); return;
      }
      for (u32 k : ev.paralelkenarEkle(x0, y0, v1x, v1y, v2x, v2y)) sketchYeniEleman(ev, k);
    } else if (sketchArac == "sonsuzCizgi" && !np.empty()) {
      // CATIA Infinite Line (pragmatik): görünür alanı fazlasıyla aşan (±1e4)
      // YAPI çizgisi (referans için). Mod: 0=Yatay 1=Dikey (1 tık + H/V kısıt),
      // 2=2 Nokta (nokta + yön).
      constexpr f64 SL = 1e4;
      u32 k = 0;
      if (sketchDurum.sonsuzMod == 2) {
        if (np.size() < 2) return;
        const f64 dx = np[1][0] - np[0][0], dy = np[1][1] - np[0][1];
        const f64 L = std::hypot(dx, dy);
        if (L < 1e-9) { sketchNoktalar.clear(); bilgiVer("Yön sıfır"); return; }
        k = ev.cizgiEkle(np[0][0] - dx / L * SL, np[0][1] - dy / L * SL,
                         np[0][0] + dx / L * SL, np[0][1] + dy / L * SL);
      } else {
        const f64 x = np.back()[0], y = np.back()[1];
        const bool yatay = sketchDurum.sonsuzMod == 0;
        k = yatay ? ev.cizgiEkle(x - SL, y, x + SL, y)
                  : ev.cizgiEkle(x, y - SL, x, y + SL);
        ev.kisitEkle(yatay ? eskiz::KisitTip::Yatay : eskiz::KisitTip::Dikey, {k}, {}, 0);
      }
      ev.yapiAyarla(k, true);  // sonsuz çizgi = yapı (construction) elemanı
    } else {
      return;
    }
    ev.coz();
    sketchNoktalar.clear();
  };
  // Araç başına gereken tık sayısı (tamamlanınca geometri eklenir).
  const auto sketchAracTikSayi = [&]() -> size_t {
    if (sketchArac == "nokta" || sketchArac == "noktaKoord") return 1;
    if (sketchArac == "sonsuzCizgi") return sketchDurum.sonsuzMod == 2 ? 2 : 1;
    if (sketchArac == "egriselSlot") return 4;
    if (sketchArac == "daire3" || sketchArac == "yay3" || sketchArac == "elips" ||
        sketchArac == "slot" || sketchArac == "yonluDikdortgen" ||
        sketchArac == "paralelkenar" || sketchArac == "merkezYay" ||
        sketchArac == "anahtarDeligi") return 3;
    if (sketchArac.empty()) return 0;
    return 2;  // cizgi / dikdortgen / daire / daireKoord / cokgen / merkezliDikdortgen
  };

  // ---- Sketcher çizim araçları + kısıt komutları ----
  // Araç seç → sketchArac ayarla, bekleyen tıkları temizle (Sketcher toolbar'ında
  // görünür). Kısıt uygula → sketchSecili geometrilere kısıt ekle + çöz.
  const auto sketchAracSec = [&](const char* arac) {
    sketchArac = arac;
    sketchNoktalar.clear();
  };
  // ---- 2B düzenleme araçlarının hangi kümede olduğunu tanı ----
  const auto sketchDuzenMi = [&](const std::string& a) {
    return a == "trim" || a == "uzat" || a == "bol" || a == "kapat" ||
           a == "hizlikirp" || a == "tumleyen" ||
           a == "fillet" || a == "pah" || a == "ayna" || a == "simetri" ||
           a == "otele" || a == "dondur" || a == "olcekle" || a == "ofset" ||
           a == "aciortay" || a == "tegetcizgi" || a == "kesisimnokta" ||
           a == "izdusumnokta";
  };
  // Düzenleme fazını 0→1 ilerlet (Enter / ekran "Devam" düğmesi / ön-seçim ile aynı yol).
  // sketchSeciliMerkez'den sonra atanır; burada yalnız bildirildi (sketchDuzenSec çağırır).
  std::function<void()> sketchDuzenIlerlet;
  // sketchDuzenSec, sketchDuzenIlerlet'e bağlı (aşağıda atanır) → oradan sonra
  // tanımlanır ki by-value capture geçerli (kur() sonrası dangling olmasın).
  std::function<void(const char*)> sketchDuzenSec;
  // İmleç uv'sine en yakın eskiz geometrisi (eşik^2 uv birimi). 0 = yok.
  const auto sketchEnYakin = [&](eskiz::EskizVeri& ev, f64 u, f64 v) -> u32 {
    u32 enYakin = 0; f64 enMesafe = 0.35 * 0.35;
    for (const eskiz::Geometri& g : ev.geometriler()) {
      f64 md;
      if (g.tip == eskiz::GeoTip::Cizgi) {
        const f64 ax = ev.noktaX(g.kimlik, 0), ay = ev.noktaY(g.kimlik, 0);
        const f64 bx = ev.noktaX(g.kimlik, 1), by = ev.noktaY(g.kimlik, 1);
        const f64 dx = bx - ax, dy = by - ay, L2 = dx * dx + dy * dy;
        f64 tpar = L2 > 1e-12 ? ((u - ax) * dx + (v - ay) * dy) / L2 : 0.0;
        tpar = std::clamp(tpar, 0.0, 1.0);
        const f64 px2 = ax + tpar * dx - u, py2 = ay + tpar * dy - v;
        md = px2 * px2 + py2 * py2;
      } else if (g.tip == eskiz::GeoTip::Daire || g.tip == eskiz::GeoTip::Yay) {
        const f64 cx = ev.noktaX(g.kimlik, 2), cy = ev.noktaY(g.kimlik, 2);
        const f64 dr = std::sqrt((u - cx) * (u - cx) + (v - cy) * (v - cy)) - ev.px(g.ir);
        md = dr * dr;
      } else {
        const f64 dx = ev.noktaX(g.kimlik, 0) - u, dy = ev.noktaY(g.kimlik, 0) - v;
        md = dx * dx + dy * dy;
      }
      if (md < enMesafe) { enMesafe = md; enYakin = g.kimlik; }
    }
    return enYakin;
  };
  // İmleç/koordinat yardımcılarını bglm'e bağla (MDI ayrı-pencere: frame bunlara
  // ort.bglm üzerinden erişir → per-window belge'ye doğru bağlanır). Ana pencerede
  // bglm.X == yerel X olduğundan davranış birebir.
  bglm.imlecDunya = imlecDunya;
  bglm.imlecYuzey = imlecYuzey;
  bglm.imlecVurus = imlecVurus;
  bglm.uvDunya = uvDunya;
  bglm.ekranUV = ekranUV;
  bglm.sketchEnYakin = sketchEnYakin;
  bglm.sketchAracTikSayi = sketchAracTikSayi;
  bglm.sketchAracTamamla = sketchAracTamamla;
  bglm.sketchSecimYay = sketchSecimYay;
  // Seçili geometrilerin ağırlık merkezi (otele/döndür/ölçek varsayılan referansı).
  const auto sketchSeciliMerkez = [&](eskiz::EskizVeri& ev, f64& mx, f64& my) {
    mx = my = 0; int n = 0;
    for (u32 k : sketchSecili) {
      const eskiz::Geometri* g = ev.geoBul(k);
      if (!g) continue;
      const int uc = (g->tip == eskiz::GeoTip::Daire || g->tip == eskiz::GeoTip::Yay) ? 3 : 2;
      for (int i = 0; i < uc; ++i) { mx += ev.noktaX(k, i); my += ev.noktaY(k, i); ++n; }
    }
    if (n) { mx /= n; my /= n; }
  };
  // Düzenleme fazını ilerlet: seçim → eksen/referans/değer aşaması (Enter'a gerek yok).
  sketchDuzenIlerlet = [&, sketchDuzenMi, sketchNesne, sketchSeciliMerkez]() {
    if (!sketchDuzenMi(sketchArac) || sketchSecili.empty() || sketchDuzenFaz != 0) return;
    if (sketchArac == "ayna" || sketchArac == "simetri" || sketchArac == "otele" ||
        sketchArac == "dondur" || sketchArac == "olcekle" || sketchArac == "ofset") {
      sketchDuzenFaz = 1;
      if (sketchArac == "ayna" || sketchArac == "simetri") bilgiVer("Eksen çizgisini seçin");
      else if (sketchArac == "otele") bilgiVer("Referans noktasını tıklayın (veya değer girin)");
      else if (sketchArac == "ofset") bilgiVer("Ofset mesafesini girin / Uygula");
      else {
        if (SketchNesnesi* sn = sketchNesne())
          sketchSeciliMerkez(sn->eskiz(), sketchDuzenRef[0], sketchDuzenRef[1]);
        bilgiVer("Değer girin veya Uygula (referans = seçim merkezi)");
      }
    }
  };
  // Düzenleme moduna geç: seçimi koru (ayna/otele önce seçim ister), fazı sıfırla.
  // sketchDuzenIlerlet by-value yakalanır (artık atanmış + kendine-yeter) → kopyalanınca dangling yok.
  sketchDuzenSec = [&, sketchDuzenIlerlet](const char* arac) {
    sketchArac = arac;
    sketchNoktalar.clear();
    sketchDuzenFaz = 0;
    for (auto& s : sketchDuzGiris) s[0] = 0;
    if (sketchArac == "ayna") sketchDurum.sketchAynaKopya = true;    // Ayna = kopyala (öntanım)
    else if (sketchArac == "simetri") sketchDurum.sketchAynaKopya = false;  // Simetri = taşı
    // Zaten geometri seçiliyse klavye beklemeden doğrudan aktif (faz 1'e geç).
    if (!sketchSecili.empty() && sketchDuzenIlerlet) sketchDuzenIlerlet();
  };
  const auto sketchDurumMetin = [&](eskiz::EskizVeri& ev) {
    switch (ev.durum()) {
      case eskiz::Solver::Durum::IyiKisitli:   bilgiVer("Sketch tam belirli"); break;
      case eskiz::Solver::Durum::AzKisitli:    bilgiVer("Az belirli — " + std::to_string(ev.dof()) + " DOF"); break;
      case eskiz::Solver::Durum::AsiriKisitli: bilgiVer("Aşırı belirli (fazla kısıt)"); break;
      case eskiz::Solver::Durum::Celiskili:    bilgiVer("Çelişkili kısıt"); break;
      case eskiz::Solver::Durum::Yakinsamadi:  bilgiVer("Çözücü yakınsamadı"); break;
    }
  };
  // Seçili geometrilere kısıt uygula. deger<0 → boyutsuz kısıt; boyutsal
  // kısıtlar (Mesafe/Yarıçap) mevcut ölçüyü hedef alır (deger).
  const auto sketchKisitUygula = [&, sketchNesne, sketchDurumMetin](eskiz::KisitTip tip) {
    SketchNesnesi* sk = sketchNesne();
    if (!sk) return;
    eskiz::EskizVeri& ev = sk->eskiz();
    if (sketchSecili.empty()) { bilgiVer("Önce eskiz geometrisi seçin"); return; }
    eskiz::Skalar deger = 0;
    if (tip == eskiz::KisitTip::Mesafe && !sketchSecili.empty()) {
      const eskiz::Geometri* g = ev.geoBul(sketchSecili[0]);
      if (g && g->tip == eskiz::GeoTip::Cizgi) {
        const eskiz::Skalar dx = ev.noktaX(g->kimlik, 1) - ev.noktaX(g->kimlik, 0);
        const eskiz::Skalar dy = ev.noktaY(g->kimlik, 1) - ev.noktaY(g->kimlik, 0);
        deger = std::sqrt(dx * dx + dy * dy);
      }
    } else if (tip == eskiz::KisitTip::Yaricap && !sketchSecili.empty()) {
      const eskiz::Geometri* g = ev.geoBul(sketchSecili[0]);
      if (g && (g->tip == eskiz::GeoTip::Daire || g->tip == eskiz::GeoTip::Yay))
        deger = ev.px(g->ir);
    }
    ev.kisitEkle(tip, sketchSecili, deger);
    ev.coz();
    sketchSecili.clear();
    sketchDurumMetin(ev);
  };
  const auto sketchAktifMi = [&]() { return aktifSketch != VARLIK_YOK; };

  // Tek geometriye uygun boyutsal kısıtı mevcut ölçüyle ekle: çizgi → Mesafe
  // (uzunluk), daire/yay → Yarıçap. Dönüş: kısıt kimliği (uygulanamazsa 0).
  // sketchOtoOlcu + sketchOtoKisit ortak kullanır (mükerrer değil).
  const auto sketchGeoOlcu = [](eskiz::EskizVeri& ev, const eskiz::Geometri* g) -> u32 {
    if (g->tip == eskiz::GeoTip::Cizgi)
      return ev.kisitEkle(eskiz::KisitTip::Mesafe, {g->kimlik},
                          std::hypot(ev.noktaX(g->kimlik, 1) - ev.noktaX(g->kimlik, 0),
                                     ev.noktaY(g->kimlik, 1) - ev.noktaY(g->kimlik, 0)));
    if (g->tip == eskiz::GeoTip::Daire || g->tip == eskiz::GeoTip::Yay)
      return ev.kisitEkle(eskiz::KisitTip::Yaricap, {g->kimlik}, ev.px(g->ir));
    return 0;
  };
  // Otomatik ölçülendirme: seçime göre uygun boyutsal kısıtı mevcut ölçüyle ekle.
  //   1 çizgi → Mesafe(uzunluk), 1 daire/yay → Yarıçap, 2 nokta → Mesafe,
  //   2 çizgi → Açı. Ölçü hedefi geometrinin güncel değeridir (referans alır).
  const auto sketchOtoOlcu = [&, sketchNesne, sketchDurumMetin, sketchGeoOlcu]() {
    SketchNesnesi* sk = sketchNesne();
    if (!sk) return;
    eskiz::EskizVeri& ev = sk->eskiz();
    if (sketchSecili.empty()) { bilgiVer("Önce eskiz geometrisi seçin"); return; }
    if (sketchSecili.size() == 1) {
      const eskiz::Geometri* g = ev.geoBul(sketchSecili[0]);
      if (!g) return;
      if (!sketchGeoOlcu(ev, g)) { bilgiVer("Bu geometri için otomatik ölçü yok"); return; }
    } else if (sketchSecili.size() == 2) {
      const eskiz::Geometri* g0 = ev.geoBul(sketchSecili[0]);
      const eskiz::Geometri* g1 = ev.geoBul(sketchSecili[1]);
      if (!g0 || !g1) return;
      if (g0->tip == eskiz::GeoTip::Nokta && g1->tip == eskiz::GeoTip::Nokta) {
        const eskiz::Skalar dm = std::hypot(ev.noktaX(g1->kimlik, 0) - ev.noktaX(g0->kimlik, 0),
                                            ev.noktaY(g1->kimlik, 0) - ev.noktaY(g0->kimlik, 0));
        ev.kisitEkle(eskiz::KisitTip::Mesafe, {g0->kimlik, g1->kimlik}, dm);
      } else if (g0->tip == eskiz::GeoTip::Cizgi && g1->tip == eskiz::GeoTip::Cizgi) {
        const eskiz::Skalar a0 = std::atan2(ev.noktaY(g0->kimlik, 1) - ev.noktaY(g0->kimlik, 0),
                                            ev.noktaX(g0->kimlik, 1) - ev.noktaX(g0->kimlik, 0));
        const eskiz::Skalar a1 = std::atan2(ev.noktaY(g1->kimlik, 1) - ev.noktaY(g1->kimlik, 0),
                                            ev.noktaX(g1->kimlik, 1) - ev.noktaX(g1->kimlik, 0));
        ev.kisitEkle(eskiz::KisitTip::Aci, {g0->kimlik, g1->kimlik}, std::abs(a1 - a0));
      } else { bilgiVer("Bu seçim için otomatik ölçü yok"); return; }
    } else { bilgiVer("Otomatik ölçü: 1 veya 2 öğe seçin"); return; }
    ev.coz();
    sketchSecili.clear();
    sketchDurumMetin(ev);
  };

  // Temas kısıtı (CATIA Contact Constraint): 2 seçili geometrinin tip çiftine
  // göre EN UYGUN kısıtı otomatik uygula — daire+daire → eş merkez (Cakisik
  // merkez-merkez), daire+çizgi → Teğet, nokta+nokta → Çakışık, nokta+geo →
  // NoktaUzerinde, çizgi+çizgi → Çakışık (en yakın uç çifti).
  const auto sketchTemasKisit = [&, sketchNesne, sketchDurumMetin]() {
    SketchNesnesi* sk = sketchNesne();
    if (!sk) return;
    eskiz::EskizVeri& ev = sk->eskiz();
    if (sketchSecili.size() != 2) { bilgiVer("Temas kısıtı: 2 öğe seçin"); return; }
    const eskiz::Geometri* g0 = ev.geoBul(sketchSecili[0]);
    const eskiz::Geometri* g1 = ev.geoBul(sketchSecili[1]);
    if (!g0 || !g1) return;
    using GT = eskiz::GeoTip;
    const auto daireMi = [](const eskiz::Geometri* g) {
      return g->tip == GT::Daire || g->tip == GT::Yay;
    };
    const char* ad = nullptr;
    if (daireMi(g0) && daireMi(g1)) {
      ev.kisitEkle(eskiz::KisitTip::Cakisik, {g0->kimlik, g1->kimlik}, {2, 2}, 0);
      ad = "Eş merkez";
    } else if ((g0->tip == GT::Cizgi && daireMi(g1)) || (daireMi(g0) && g1->tip == GT::Cizgi)) {
      // Solver Tegel sırası: g0=çizgi, g1=daire/yay.
      const u32 cz = g0->tip == GT::Cizgi ? g0->kimlik : g1->kimlik;
      const u32 dr = g0->tip == GT::Cizgi ? g1->kimlik : g0->kimlik;
      ev.kisitEkle(eskiz::KisitTip::Tegel, {cz, dr});
      ad = "Teğet";
    } else if (g0->tip == GT::Nokta || g1->tip == GT::Nokta) {
      const eskiz::Geometri* nk = g0->tip == GT::Nokta ? g0 : g1;
      const eskiz::Geometri* hd = g0->tip == GT::Nokta ? g1 : g0;
      if (hd->tip == GT::Nokta) {
        ev.kisitEkle(eskiz::KisitTip::Cakisik, {nk->kimlik, hd->kimlik}, {0, 0}, 0);
        ad = "Çakışık";
      } else if (hd->tip == GT::Cizgi || daireMi(hd)) {
        ev.kisitEkle(eskiz::KisitTip::NoktaUzerinde, {nk->kimlik, hd->kimlik}, {0, 0}, 0);
        ad = "Nokta üzerinde";
      }
    } else if (g0->tip == GT::Cizgi && g1->tip == GT::Cizgi) {
      // En yakın uç çiftini bul → o uçlar çakışık.
      int e0 = 0, e1 = 0; f64 enM = 1e300;
      for (int i = 0; i < 2; ++i)
        for (int j = 0; j < 2; ++j) {
          const f64 dx = ev.noktaX(g1->kimlik, j) - ev.noktaX(g0->kimlik, i);
          const f64 dy = ev.noktaY(g1->kimlik, j) - ev.noktaY(g0->kimlik, i);
          if (dx * dx + dy * dy < enM) { enM = dx * dx + dy * dy; e0 = i; e1 = j; }
        }
      ev.kisitEkle(eskiz::KisitTip::Cakisik, {g0->kimlik, g1->kimlik}, {e0, e1}, 0);
      ad = "Çakışık (uç-uç)";
    }
    if (!ad) { bilgiVer("Bu geometri çifti için temas kısıtı yok"); return; }
    ev.coz();
    sketchSecili.clear();
    sketchDurumMetin(ev);
  };

  // Oto Kısıt (CATIA Auto Constraint): seçili (boşsa tüm) geometrilere otomatik
  // kısıt seti — eğimi <5° çizgiye Yatay, >85° Dikey; her çizgiye Mesafe, daire/
  // yaya Yarıçap (sketchGeoOlcu). Zaten o tipte kısıtı olan geometri atlanır;
  // ekleme sonrası çözüm Çelişkili/Aşırı olursa son eklenen geri alınır.
  const auto sketchOtoKisit = [&, sketchNesne, sketchDurumMetin, sketchGeoOlcu]() {
    SketchNesnesi* sk = sketchNesne();
    if (!sk) return;
    eskiz::EskizVeri& ev = sk->eskiz();
    std::vector<u32> hedef = sketchSecili;
    if (hedef.empty())
      for (const eskiz::Geometri& g : ev.geometriler()) hedef.push_back(g.kimlik);
    if (hedef.empty()) { bilgiVer("Eskizde geometri yok"); return; }
    const auto kisitVar = [&ev](eskiz::KisitTip tip, u32 geoId) {
      for (const eskiz::Kisit& k : ev.kisitlar())
        if (k.tip == tip)
          for (u32 g : k.geo)
            if (g == geoId) return true;
      return false;
    };
    // Kısıtı geçici uygula; çözüm bozulursa (Çelişkili/Aşırı) geri al.
    const auto guvenliEkle = [&ev](u32 kimlik) -> bool {
      if (!kimlik) return false;
      const auto d = ev.coz().durum;
      if (d == eskiz::Solver::Durum::Celiskili || d == eskiz::Solver::Durum::AsiriKisitli) {
        ev.kisitSil(kimlik);
        ev.coz();
        return false;
      }
      return true;
    };
    int eklendi = 0;
    for (u32 id : hedef) {
      const eskiz::Geometri* g = ev.geoBul(id);
      if (!g) continue;
      if (g->tip == eskiz::GeoTip::Cizgi) {
        const f64 dx = std::abs(ev.noktaX(id, 1) - ev.noktaX(id, 0));
        const f64 dy = std::abs(ev.noktaY(id, 1) - ev.noktaY(id, 0));
        const f64 t5 = 0.0874886;  // tan(5°): eğim eşiği
        if (dy < dx * t5 && !kisitVar(eskiz::KisitTip::Yatay, id) &&
            !kisitVar(eskiz::KisitTip::Dikey, id))
          eklendi += guvenliEkle(ev.kisitEkle(eskiz::KisitTip::Yatay, {id}));
        else if (dx < dy * t5 && !kisitVar(eskiz::KisitTip::Dikey, id) &&
                 !kisitVar(eskiz::KisitTip::Yatay, id))
          eklendi += guvenliEkle(ev.kisitEkle(eskiz::KisitTip::Dikey, {id}));
        if (!kisitVar(eskiz::KisitTip::Mesafe, id))
          eklendi += guvenliEkle(sketchGeoOlcu(ev, g));
      } else if (g->tip == eskiz::GeoTip::Daire || g->tip == eskiz::GeoTip::Yay) {
        if (!kisitVar(eskiz::KisitTip::Yaricap, id) && !kisitVar(eskiz::KisitTip::Cap, id))
          eklendi += guvenliEkle(sketchGeoOlcu(ev, g));
      }
    }
    sketchSecili.clear();
    if (!eklendi) { bilgiVer("Oto kısıt: eklenecek yeni kısıt yok"); return; }
    ev.coz();
    sketchDurumMetin(ev);
  };

  // ---- Sketch mantık delegeleri bağla (sketch_ui.cpp modülü main'in yerel
  //      lambda'larına dokunmadan bunları çağırır — davranış birebir). ----
  bglm.sketchNesne = sketchNesne;  // yerel lambda'yı KOPYALA (sarma DEĞİL) — kur() dönünce
                                   // yerel lambda ölür; kopya kalıcı referentleri (aktifSketch/cekirdek) taşır
  // Wrapper DEĞİL, KOPYA: yerel lambda'lar kendine-yeter (nested yerel çağrılarını
  // by-value yakalar) → kur() dönünce bglm kopyası dangling içermez. (1384 satırındaki
  // sketchAracTamamla ataması ile tekilleştirildi — mükerrer/ezici wrapper kaldırıldı.)
  bglm.sketchDuzenMi = sketchDuzenMi;
  bglm.sketchDikBak = sketchDikBak;
  bglm.sketchAracSec = sketchAracSec;
  bglm.sketchDuzenSec = sketchDuzenSec;
  bglm.sketchDuzenIlerlet = sketchDuzenIlerlet;
  bglm.sketchKisitUygula = sketchKisitUygula;
  bglm.sketchOtoOlcu = sketchOtoOlcu;
  bglm.sketchTemasKisit = sketchTemasKisit;
  bglm.sketchOtoKisit = sketchOtoKisit;

  // NOT: kur()-yerel sketchAracSec/sketchDuzenSec dangling → KALICI bglm kopyası yakala
  // (satır 1519-1520). Menü lambda'ları kur() sonrası kareCiz'de çağrılır (Kısıt deseni).
  // Alt şerit KATEGORİLİ/FLYOUT: aynı altGrup → tek küme düğmesi (▾), tıkla açıl.
  // 32 düğme yerine ~10 kategori (CATIA Profile/Operation/Constraint toolbar mantığı).
  // Çizim: Çizgi | [Dikdörtgen▾] | [Daire▾] | [Eğri▾] | Nokta.
  komut("Çizim", "Çizgi", "╱", [&bglm](Cekirdek&) { bglm.sketchAracSec("cizgi"); }, "sketcher", etkinSketch, "cizgi");
  komut("Çizim", "Sonsuz Çizgi", "∞", [&bglm](Cekirdek&) { bglm.sketchAracSec("sonsuzCizgi"); }, "sketcher", etkinSketch, "cizgi");
  komut("Çizim", "Açıortay", "∠", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("aciortay"); }, "sketcher", etkinSketch, "cizgi");
  komut("Çizim", "İki-Teğet Çizgi", "⌣", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("tegetcizgi"); }, "sketcher", etkinSketch, "cizgi");
  komut("Çizim", "Dikdörtgen", "▭", [&bglm](Cekirdek&) { bglm.sketchAracSec("dikdortgen"); }, "sketcher", etkinSketch, "dikdortgen");
  komut("Çizim", "Merkezli Dikdörtgen", "▢", [&bglm](Cekirdek&) { bglm.sketchAracSec("merkezliDikdortgen"); }, "sketcher", etkinSketch, "dikdortgen");
  komut("Çizim", "Yönlü Dikdörtgen", "◇", [&bglm](Cekirdek&) { bglm.sketchAracSec("yonluDikdortgen"); }, "sketcher", etkinSketch, "dikdortgen");
  komut("Çizim", "Paralelkenar", "▱", [&bglm](Cekirdek&) { bglm.sketchAracSec("paralelkenar"); }, "sketcher", etkinSketch, "dikdortgen");
  komut("Çizim", "Daire", "◯", [&bglm](Cekirdek&) { bglm.sketchAracSec("daire"); }, "sketcher", etkinSketch, "daire");
  komut("Çizim", "Daire (3N)", "◎", [&bglm](Cekirdek&) { bglm.sketchAracSec("daire3"); }, "sketcher", etkinSketch, "daire");
  komut("Çizim", "Daire (Koord)", "⌖", [&bglm](Cekirdek&) { bglm.sketchAracSec("daireKoord"); }, "sketcher", etkinSketch, "daire");
  komut("Çizim", "Yay (3N)", "⌒", [&bglm](Cekirdek&) { bglm.sketchAracSec("yay3"); }, "sketcher", etkinSketch, "egri");
  komut("Çizim", "Merkez Yay", "◜", [&bglm](Cekirdek&) { bglm.sketchAracSec("merkezYay"); }, "sketcher", etkinSketch, "egri");
  komut("Çizim", "Elips", "⬭", [&bglm](Cekirdek&) { bglm.sketchAracSec("elips"); }, "sketcher", etkinSketch, "egri");
  komut("Çizim", "Slot", "▬", [&bglm](Cekirdek&) { bglm.sketchAracSec("slot"); }, "sketcher", etkinSketch, "egri");
  komut("Çizim", "Eğrisel Slot", "◠", [&bglm](Cekirdek&) { bglm.sketchAracSec("egriselSlot"); }, "sketcher", etkinSketch, "egri");
  komut("Çizim", "Anahtar Deliği", "⚿", [&bglm](Cekirdek&) { bglm.sketchAracSec("anahtarDeligi"); }, "sketcher", etkinSketch, "egri");
  komut("Çizim", "Çokgen", "⬡", [&bglm](Cekirdek&) { bglm.sketchAracSec("cokgen"); }, "sketcher", etkinSketch, "egri");
  komut("Çizim", "Nokta", "·", [&bglm](Cekirdek&) { bglm.sketchAracSec("nokta"); }, "sketcher", etkinSketch, "nokta");
  komut("Çizim", "Nokta (Koord)", "⊹", [&bglm](Cekirdek&) { bglm.sketchAracSec("noktaKoord"); }, "sketcher", etkinSketch, "nokta");
  komut("Çizim", "Kesişim Noktası", "✕", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("kesisimnokta"); }, "sketcher", etkinSketch, "nokta");
  komut("Çizim", "İzdüşüm Noktası", "⊥", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("izdusumnokta"); }, "sketcher", etkinSketch, "nokta");
  // ---- Düzenle (2B Operations): [Kırp▾] | [Köşe▾] | [Dönüşüm▾] ----
  komut("Düzenle", "Hızlı Kırp (Silgi)", "✄", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("hizlikirp"); }, "sketcher", etkinSketch, "kirp");
  komut("Düzenle", "Kırp", "✂", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("trim"); }, "sketcher", etkinSketch, "kirp");
  komut("Düzenle", "Uzat", "⇥", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("uzat"); }, "sketcher", etkinSketch, "kirp");
  komut("Düzenle", "Böl", "⑂", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("bol"); }, "sketcher", etkinSketch, "kirp");
  komut("Düzenle", "Kapat", "◯", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("kapat"); }, "sketcher", etkinSketch, "kirp");
  komut("Düzenle", "Tümleyen", "◟", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("tumleyen"); }, "sketcher", etkinSketch, "kirp");
  komut("Düzenle", "Köşe", "⌒", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("fillet"); }, "sketcher", etkinSketch, "kose");
  komut("Düzenle", "Pah", "◺", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("pah"); }, "sketcher", etkinSketch, "kose");
  komut("Düzenle", "Ayna", "⇋", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("ayna"); }, "sketcher", etkinSketch, "donusum");
  komut("Düzenle", "Simetri", "↔", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("simetri"); }, "sketcher", etkinSketch, "donusum");
  komut("Düzenle", "Ötele", "⤢", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("otele"); }, "sketcher", etkinSketch, "donusum");
  komut("Düzenle", "Döndür", "↻", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("dondur"); }, "sketcher", etkinSketch, "donusum");
  komut("Düzenle", "Ölçekle", "⤡", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("olcekle"); }, "sketcher", etkinSketch, "donusum");
  komut("Düzenle", "Ofset", "∥", [&bglm](Cekirdek&) { bglm.sketchDuzenSec("ofset"); }, "sketcher", etkinSketch, "donusum");
  // ---- Kısıt: [Geometrik▾] | [Ölçüsel▾] ----
  // KRİTİK: menü lambda'ları kur() döndükten sonra kareCiz'de çağrılır → yerel
  // (sketchKisitUygula/sketchAktifMi) [&] yakalamak dangling'dir (0xC0000005 hover
  // çökmesi). KALICI bglm kopyası + kararlı member aktifSketch yakala.
  const auto kisitEtkin = [&aktifSketch](Cekirdek&) { return aktifSketch != VARLIK_YOK; };
  komut("Kısıt", "Yatay", "H", [&bglm](Cekirdek&) { bglm.sketchKisitUygula(eskiz::KisitTip::Yatay); }, "sketcher", kisitEtkin, "geokisit");
  komut("Kısıt", "Dikey", "V", [&bglm](Cekirdek&) { bglm.sketchKisitUygula(eskiz::KisitTip::Dikey); }, "sketcher", kisitEtkin, "geokisit");
  komut("Kısıt", "Dik", "⊥", [&bglm](Cekirdek&) { bglm.sketchKisitUygula(eskiz::KisitTip::Dik); }, "sketcher", kisitEtkin, "geokisit");
  komut("Kısıt", "Paralel", "∥", [&bglm](Cekirdek&) { bglm.sketchKisitUygula(eskiz::KisitTip::Paralel); }, "sketcher", kisitEtkin, "geokisit");
  komut("Kısıt", "Eşit", "=", [&bglm](Cekirdek&) { bglm.sketchKisitUygula(eskiz::KisitTip::Esit); }, "sketcher", kisitEtkin, "geokisit");
  komut("Kısıt", "Çakışık", "∘", [&bglm](Cekirdek&) { bglm.sketchKisitUygula(eskiz::KisitTip::Cakisik); }, "sketcher", kisitEtkin, "geokisit");
  komut("Kısıt", "Temas", "◉", [&bglm](Cekirdek&) { bglm.sketchTemasKisit(); }, "sketcher", kisitEtkin, "geokisit");
  komut("Kısıt", "Mesafe", "↦", [&bglm](Cekirdek&) { bglm.sketchKisitUygula(eskiz::KisitTip::Mesafe); }, "sketcher", kisitEtkin, "olcukisit");
  komut("Kısıt", "Yarıçap", "⌀", [&bglm](Cekirdek&) { bglm.sketchKisitUygula(eskiz::KisitTip::Yaricap); }, "sketcher", kisitEtkin, "olcukisit");
  komut("Kısıt", "Oto Ölçü", "⊢", [&bglm](Cekirdek&) { bglm.sketchOtoOlcu(); }, "sketcher", kisitEtkin, "olcukisit");
  komut("Kısıt", "Oto Kısıt", "⚙", [&bglm](Cekirdek&) { bglm.sketchOtoKisit(); }, "sketcher", kisitEtkin, "olcukisit");
  komut("Kısıt", "Animasyon", "▶", [&bglm](Cekirdek&) { sketchAnimasyonAc(bglm); }, "sketcher", kisitEtkin, "olcukisit");

  // Fare girdisi ImGui IO uzerinden 3D pencere icinde islenir (capture ImGui'de);
  // boylece 3D pencere bir ImGui penceresi olsa bile kamera/secim calisir.
  // Boyut değişimi BU pencerenin KENDİ yüzeyini işaretler (aktif yüzey değil — resize
  // callback'i mesaj pompasında, aktif yüzey son çizilen pencereninki olabilir).
  pencere.boyutDegisti = [this](u32, u32) {
    if (anaMi_) vulkan_.anaYuzeyNesne().boyutlandirmaGerekli();
    else if (yuzeySahip_) yuzeySahip_->boyutlandirmaGerekli();
  };
  // Explorer'dan surukle-birak: birakilan her dosyayi bu belgeye ac/ice aktar.
  // yolAc uzantiya gore dogru iceaktarici secer (.step/.stp→STEP, .apt/.FikoApt→APT);
  // desteklenmeyen/erisilemez yolda kendi guard'i var (cokme yok).
  pencere.dosyaBirakildi = [&bglm](const std::string& yol) { fiko::yolAc(bglm, yol); };
  // ---- BASİT/GLOBAL kısayolları merkezi kayda ekle (mod-bağımsız, kendine-yeten
  // eylem + tuş tüketir). Bağlamsal kısayollar (1-4/F/T/O/G/M/Space/Delete/ESC...)
  // tusBasildi if-zincirinde KALIR — mod-bağımlı, taşımak riskli (davranış birebir).
  // Yakalama [&]: eski tusBasildi lambda'sıyla aynı semantik (belge/üye referansı,
  // pencere ömrü boyunca geçerli). kisayollar_ üye → kur() sonrası da yaşar.
  kisayollar_.ekle({'Z', true, false, false, "Geri Al", [&] { cekirdek.geriAl(); }});
  kisayollar_.ekle({'Y', true, false, false, "Yinele", [&] { cekirdek.yinele(); }});
  // Ctrl+S = .fikocad kaydet, Ctrl+O = .fikocad aç. (Modsuz 'O' ortografik toggle
  // olarak kalır — kayıt ctrl=true, çakışmaz.)
  kisayollar_.ekle({'S', true, false, false, "Kaydet (.fikocad)", [&] { fiko::fikoKaydetKomut(bglm); }});
  kisayollar_.ekle({'O', true, false, false, "Aç (.fikocad)", [&] { fiko::fikoAc(bglm); }});
  // Merkezi takip: kısayol çalışınca KomutCalisti yayınla → kısayollar da tek audit/
  // telemetri akışına girer (menü/palet/toolbar ile aynı; lisans için tek-merkez).
  kisayollar_.kisayolCalisti = [&](const std::string& ad) {
    cekirdek.olaylar().yayinla(KomutCalisti{ad.c_str(), "Kısayol", "kisayol", true}, ad.c_str());
  };

  pencere.tusBasildi = [&](u32 vk) {
    const ImGuiIO& io = ImGui::GetIO();
    // Metin kutusuna yazılıyorsa (ImGui InputText aktif) kısayolları TETİKLEME — tuş
    // ImGui'ye gitsin. Yoksa Ctrl+Z metin-geri-al yerine global "Geri Al"ı, 'F'/'O' vb.
    // yazı yerine komut tetikler (CATIA/standart editör davranışı).
    if (io.WantTextInput) return;
    // Merkezi global kısayollar ÖNCE: eşleşirse eylem çalışır + tuş tüketilir.
    // shift/alt=false geçilir → eski gevşek "io.KeyCtrl" (shift/alt önemsiz)
    // semantiği BİREBİR korunur (Ctrl+Z/Ctrl+Y kayıtları ctrl=true,shift/alt=false).
    if (kisayollar_.bas(vk, io.KeyCtrl, false, false)) return;
    if (vk == '1') secimSis.fYuz = !secimSis.fYuz;      // seçim filtresi aç/kapa
    if (vk == '2') secimSis.fKenar = !secimSis.fKenar;
    if (vk == '3') secimSis.fNokta = !secimSis.fNokta;
    if (vk == '4') secimSis.fGovde = !secimSis.fGovde;
    if (vk == 'F') { if (io.KeyShift) tumunuSigdir(); else secimiSigdir(); }
    if (vk == 'T') {  // alan seçimi trap tipini döngüsel değiştir (CATIA dışlayıcı)
      alan.tip = sonrakiTrap(alan.tip);
      bilgiVer(std::string("Alan seçimi: ") + trapAdi(alan.tip));
    }
    if (vk == 'O') kamera.ortografikAyarla(!kamera.ortografik());  // ortografik/perspektif
    if (vk == 'G') {  // compass'ı seçili gövdeye yapıştır / bırak
      if (pusula.bagli) { pusula.bagli = false; pusula.tutamac = -1; }
      else {
        const auto sg = sahne.seciliGovdeler();
        if (!sg.empty()) { pusula.bagli = true; pusula.govde = sg.back(); pusula.tutamac = -1; }
      }
    }
    if (vk == 'M') {
      olcum.acik = !olcum.acik;
      if (olcum.acik && olcum.kip == OlcumKip::Ayarlar) olcum.kip = OlcumKip::Cetvel;
    }
    if (vk == VK_SPACE) {
      // Ctrl+Space kontrol merkezini açar — ama bu, ImGui kare-döngüsündeki tek
      // yetkili tetikleyicide (komutPalet) ele alınır; burada YALNIZ düz Space.
      // Böylece eski/yeni iki paletin aynı anda açılması (çift kutu) engellenir.
      if (!io.KeyCtrl) hizliGorunumAcik = true;
    }
    if (vk == VK_DELETE) {
      // K7: Sketcher aktifken Delete gövde SİLMEZ — seçili sketch geometrisini
      // siler (yoksa no-op + bilgi). Aksi halde sahne gövdesi silme akışı.
      // kur()-yerel sketchNesne dangling → KALICI bglm kopyası (satır 1512).
      if (SketchNesnesi* sk = bglm.sketchNesne();
          sk && cekirdek.workbenchler().aktif() == "sketcher") {
        if (!sketchSecili.empty() || !sketchSeciliKisit.empty()) {
          for (u32 k : sketchSeciliKisit) sk->eskiz().kisitSil(k);  // seçili kısıt sembolleri
          for (u32 g : sketchSecili) sk->eskiz().geoSil(g);          // seçili geometri (+ bağlı kısıt)
          sketchSeciliKisit.clear();
          sketchSecili.clear();
          sk->eskiz().coz();  // kısıt kalkınca DOF/çözüm tazele
          cekirdek.olaylar().yayinla(ParametreDegisti{aktifSketch}, "Sketch");
        } else {
          bilgiVer("Silmek için önce sketch geometrisi/kısıt seçin");
        }
      } else {
        seciliSil();
      }
    }
    if (vk == VK_ESCAPE) {
      if (secimYigini.acikMi()) {  // Selection Stack açık → seçime dokunmadan iptal
        secimYigini.kapat();
        secimSis.hoverBastir(false);
      }
      else if (alan.aktifMi()) alan.iptal();  // trap sürükleme/poligon iptal
      else if (hizliYonSecim > 0) hizliYonSecim = 0;  // "Yön Seç" modunu iptal (tık önceliğiyle aynı sıra)
      else if (refSecim.aktif()) refSecim.bitir();
      else if (analizSis.secimAktif()) analizSis.secimBitir();
      else if (camSis.secim().aktif()) camSis.secim().bitir();
      // K3: Ölçüm modu ESC zincirine dahil — aktif nokta-koyma akışını iptal et,
      // hâlâ açıksa ölçüm penceresini kapat (diğer tek-tık modlarıyla tutarlı).
      else if (olcum.acik && olcum.kip != OlcumKip::Ayarlar) {
        if (olcum.canliVar || olcum.ref1.var) olcum.secimSifirla();  // aktif akışı iptal
        else olcum.acik = false;                                     // boşsa modu kapat
      }
      else if (!sketchArac.empty()) {
        sketchArac.clear(); sketchNoktalar.clear();
        sketchDuzenFaz = 0; sketchSecili.clear();  // düzenleme akışını da iptal et
      }
      else if (!sketchSecili.empty()) sketchSecili.clear();
      else if (pusula.bagli) { pusula.bagli = false; pusula.tutamac = -1; }
      else sahne.secimTemizle();
    }
    // Düzenleme modu faz ilerlet: Enter → aynı callback (ekran "Devam" ile ortak yol).
    // kur()-yerel sketchDuzenIlerlet dangling → KALICI bglm kopyası (satır 1521).
    if (vk == VK_RETURN && bglm.sketchDuzenIlerlet) bglm.sketchDuzenIlerlet();
  };

  // ---- g_araclar kurulum (komut registry dolduktan SONRA) ----
  // Aday havuzu (özelleştirme "buton ekle") + "Geometri" grubundan başlangıç çubuğu.
  {
    g_araclar.menudenAdaylar(cekirdek);
    ui::AracCubugu gc;
    gc.kimlik = "geometri_cubugu";
    gc.baslik = "Geometri";
    gc.kenar = ui::DockKenar::Ust;
    gc.gorunur = false;  // varsayılan kapalı (kullanıcı Görünüm'den açar)
    g_araclar.ekle(std::move(gc));
    g_araclar.menudenDoldur(cekirdek, "geometri_cubugu", "Geometri");
    g_araclar.workbenchDegisti(cekirdek.workbenchler().aktif());
  }

  // ---- Alt Şerit (SeritCubugu) ayarı: registry'den HER KARE çizilir (kopyasız).
  // Grupları aktif workbench'e göre filtreler; komutları etkinMi ile pasifleştirir.
  {
    ui::SeritAyar sa;
    sa.grupSira = {"Düzenle", "Sketch",   "Çizim", "Geometri", "Boolean", "Feature", "İşlem",
                   "Tel-Kafes", "Yüzey",  "Analiz", "Döküm",   "CAM",     "Kısıt",   "Dosya"};
    sa.gizliGruplar = {"Görünüm", "Ölçüm", "Makro"};  // menü çubuğunda kalır
    sa.grupEtiket = {{"Düzenle", "DÜZENLE"},   {"Geometri", "PRİMİTİF KATI"}, {"Boolean", "BOOLEAN"},
                     {"Feature", "FEATURE"},   {"İşlem", "PAH / YUVARLAT"},   {"Tel-Kafes", "TEL-KAFES"},
                     {"Yüzey", "YÜZEY"},       {"Sketch", "ESKİZ"},           {"Çizim", "ÇİZİM"},
                     {"Analiz", "ANALİZ"},     {"Döküm", "DÖKÜM"},            {"CAM", "CAM"},
                     {"Kısıt", "KISIT"},       {"Dosya", "DOSYA"}};
    sa.sagCiz = [](Cekirdek&) {
      ImGui::AlignTextToFramePadding();
      ImGui::TextDisabled("%.0f FPS", ImGui::GetIO().Framerate);
    };
    // Bağlam vurgusu: imleç-altı kategori bit'i → butonCiz hedefMaske eşleşenleri parlatır.
    // &ort (üye; stabil) yakalanır → lambda her kare canlı belge motorundan okur.
    sa.baglamBiti = [ortP = &ort](Cekirdek&) -> unsigned {
      if (!ortP->belgePtr) return 0u;
      const auto kat = ortP->belgePtr->baglamMotoru.sonKarar().hedef.kategori;
      if (kat == baglam::HedefKategori::Bos) return 0u;
      return baglam::kategoriBit(kat);
    };
    g_serit.ayar() = std::move(sa);
  }

  // Merkezi ayarlar: kayıt + kalıcı yükleme + değişim köprüsü (ayar_kalici +
  // AyarDegisti olayı) + "Araçlar → ⚙ Ayarlar…" menü komutu (ayar_kayitlari içinde).
  // ayarlarDurum SINIF ÜYESİ (persistence: settings command onu [&]=this yakalar).
  ayarlarKaydet(bglm, ayarlarDurum_);

  // Son kullanılan dosyalar (MRU) — app-global, ayar'dan bir kez yükle (tüm pencereler
  // paylaşır; kur() MDI'de pencere-başına çalışır → tek yükleme guard'ı).
  if (!g_sonDosyalarYuklendi) {
    g_sonDosyalar.yukle(ayarOku("son_dosyalar", ""));
    g_sonDosyalarYuklendi = true;
  }
  // Kümülatif komut kullanımı (oturumlar arası telemetri) — app-global, bir kez yükle.
  if (!g_lisansYuklendi) {
    g_lisans.kullanimYukle(ayarOku("lisans_kullanim", ""));
    g_lisansYuklendi = true;
  }

  // ---- Polimorfik panel kaydı (kayıt sırası = eski kareCiz çizim sırası, BİREBİR) ----
  // Erken grup: Ürün Ağacı → Işık → Özellikler → Olay → Makro → Eklenti (~2966-2975).
  panelYon_.ekle(std::make_unique<FikoUrunAgaciPanel>());
  panelYon_.ekle(std::make_unique<FikoIsikPanel>());
  panelYon_.ekle(std::make_unique<FikoOzelliklerPanel>());
  panelYon_.ekle(std::make_unique<FikoOlayPanel>());
  panelYon_.ekle(std::make_unique<FikoMakroPanel>());
  panelYon_.ekle(std::make_unique<FikoEklentiPanel>());
  // App-global lisans/takip'i bu pencerenin Cekirdek'ine bağla (gate + KomutCalisti dinle).
  // Tüm pencereler tek g_lisans paylaşır → tutarlı lisans + toplu kullanım.
  g_lisans.bagla(cekirdek);
  // Güncelleme servisini bu pencerenin Cekirdek'ine bağla (olay erişimi + GorevSistemi async).
  g_guncelleme.bagla(cekirdek);
  // Dalga 1: açılışta sessiz oto-kontrol (ayar açıksa). Worker WinHTTP GET → main-thread
  // sürüm karşılaştır → yeni sürümde bildirim penceresi. Ağ yoksa sessizce Hata'da kalır.
  if (g_guncelleme.otoKontrol()) guncellemeKontrolBaslat(g_guncelleme, /*sessiz*/ true);
  // Komut Denetim / Lisans paneli (app-global g_lisans'ın görünür yönetimi) + toggle menü
  // komutu. Ham işaretçi panelYon_ (pencere ömrü) içinde yaşar → menü lambda güvenli.
  {
    auto lisansPanel = std::make_unique<FikoLisansPanel>(g_lisans, cekirdek);
    FikoLisansPanel* lp = lisansPanel.get();
    panelYon_.ekle(std::move(lisansPanel));
    cekirdek.menuler().ekle({"Araçlar", "🔒 Komut Denetim", "🔒", 0,
                             [lp](Cekirdek&) { lp->degistir(); }});
  }
  // Merkezi süreç yöneticisi (görev yöneticisi dashboard) — IPC + olay akışı + sonlandırma.
  {
    auto surecPanel = std::make_unique<FikoSurecYoneticiPanel>(g_surecKopru, cekirdek);
    surecYonPanel_ = surecPanel.get();  // forward drain'i beslemek için ham işaretçi (pencere ömrü)
    FikoSurecYoneticiPanel* sp = surecYonPanel_;
    panelYon_.ekle(std::move(surecPanel));
    cekirdek.menuler().ekle({"Araçlar", "🖧 Süreç Yöneticisi", "🖧", 0,
                             [sp](Cekirdek&) { sp->degistir(); }});
  }
  // Otomatik güncelleme: bildirim/denetim paneli + menü komutu (aç + hemen denetle).
  {
    auto gp = std::make_unique<FikoGuncellemePanel>(g_guncelleme);
    FikoGuncellemePanel* gpp = gp.get();
    panelYon_.ekle(std::move(gp));
    cekirdek.menuler().ekle({"Araçlar", "🔄 Güncellemeleri Denetle", "🔄", 0,
                             [gpp](Cekirdek&) {
                               gpp->ac();
                               guncellemeKontrolBaslat(g_guncelleme, /*sessiz*/ false);
                             }});
  }

  // Geç, bitişik batch: eski kareCiz ~3330-3344 çağrı sırası birebir korunur.
  // camUstte = ort.camUstte, dokumSis = belge.dokumSis, vulkan = vulkan_ (kur alias'ları).
  panelYonGec_.ekle(std::make_unique<FikoCamProgramPanel>(camUstte));
  panelYonGec_.ekle(std::make_unique<FikoCamOperasyonPanel>());
  panelYonGec_.ekle(std::make_unique<FikoCamParcaOpPanel>());
  panelYonGec_.ekle(std::make_unique<FikoCamTakimPanel>());
  panelYonGec_.ekle(std::make_unique<FikoCamTakimEkseniPanel>());
  panelYonGec_.ekle(std::make_unique<FikoFeaturePanel>());
  panelYonGec_.ekle(std::make_unique<FikoDinamikKomutPanel>());
  panelYonGec_.ekle(std::make_unique<FikoReferansPanel>());
  panelYonGec_.ekle(std::make_unique<FikoGsdPanel>());
  panelYonGec_.ekle(std::make_unique<FikoAnalizPanel>());
  panelYonGec_.ekle(std::make_unique<FikoKesitPanel>());
  panelYonGec_.ekle(std::make_unique<FikoDokumPanel>(dokumSis));
  panelYonGec_.ekle(std::make_unique<FikoCamSimPanel>(vulkan));

  // Dağınık paneller (İşlemler/pah3D blokları arasında) — yerinde tekil ciz() ile.
  hizli3dPanel_ = std::make_unique<FikoHizli3dPanel>();
  ayarlarPanel_ = std::make_unique<FikoAyarlarPanel>(ayarlarDurum_);
  sketcherPanel_ = std::make_unique<FikoSketcherPanel>();
  sketchAraclariPanel_ = std::make_unique<FikoSketchAraclariPanel>();
}  // ---- FikoBelgePenceresi::kur() sonu ---------------------------------------

// ---- FikoBelgePenceresi::kareCiz() — eski kareGovdesi lambda gövdesi (BİREBİR) ---
// return false = kare atlandı (swapchain hazır değil) → senaryo/tail atlanır.
bool FikoBelgePenceresi::kareCiz() {
    g_sonAdim = "kareCiz: UI+girdi";  // faz izleyici (çökme handler cokme.log'a yazar)
    // ── Merkezi süreç yöneticisi (IPC) — ana pencere main-thread pompası (tek/proc) ──
    if (anaMi_ && ort_.belgePtr) {
      Cekirdek& cek = ort_.belgePtr->cekirdek;
      // Heartbeat: durum (meşgul = aktif görev var mı) + belge/uptime/bellek kayda yaz (throttle'lı).
      bool mesgul = false;
      for (const auto& g : cek.gorevler().anlik())
        if (g.durum == GorevDurum::Calisiyor) { mesgul = true; break; }
      g_surecKopru.durumAyarla(mesgul ? SurecDurum::Mesgul : SurecDurum::Aktif);
      g_surecKopru.kalpAt();
      // Bu sürecin olaylarını (abone merkez varsa) delta+batch+throttle ile forward et.
      g_surecKopru.olaylariForwardEt(cek.olaylar());
      // Gelen komut/parametre/kapat çerçeveleri (ad→MenuOge→komutCalistir; merkezi gate+audit).
      for (const IpcKomut& k : g_surecKopru.beklemedekiCerceveler()) {
        cek.olaylar().yayinla(SurecKomutuAlindi{k.kaynakPid}, k.ad.empty() ? "IPC" : k.ad.c_str());
        if (k.tip == SurecMesajTip::Kapat) {  // nazik kapanış → WM_CLOSE (kaydet-sor yolu)
          PostMessageW((HWND)pencere_.dogalTutamac(), WM_CLOSE, 0, 0);
          continue;
        }
        for (const MenuOge& o : cek.menuler().hepsi())
          if (o.ad == k.ad) { cek.komutCalistir(o, "ipc"); break; }
      }
      // Merkeze forward edilmiş olaylar → SurecOlayAlindi re-publish (LisansSistemi/makro tek-merkez)
      // + dashboard olay akışını besle.
      for (const ForwardOlay& fo : g_surecKopru.beklemedekiOlaylar()) {
        cek.olaylar().yayinla(SurecOlayAlindi{fo.kaynakPid, fo.tur, fo.varlik}, fo.ozet);
        if (surecYonPanel_) surecYonPanel_->olayEkle(fo);
      }
    }
    const f32 dt = dt_;      // döngüden (calis) hesaplanır
    const f32 zaman = zaman_;
    const auto simdi = simdi_;  // basış-süresi timer'ları için
    (void)zaman;
    Pencere& pencere = pencere_;
    PencereOrtami& ort = ort_;
    VulkanBaglam& vulkan = vulkan_;
    // ---- Sınıf-üye altyapı alias'ları (frame gövdesi bare isimlerini korur) ----
    BoruHatlari& hatlar = hatlar_;
    CizimOnbellek& anaCizim = anaCizim_;
    BelgeYonetici& belgeYon = belgeYon_;
    girdi::FareJestSistemi& jestSis = jestSis_;
    girdi::SahneHoverProbu& hoverProbu = hoverProbu_;
    girdi::BaglamPolitika& baglamPol = baglamPol_;
    girdi::PolitikaKarar& sonPolitika = sonPolitika_;
    ImVec2& sonPolitikaKonum = sonPolitikaKonum_;
    u32& sonPolitikaHedef = sonPolitikaHedef_;
    u32& sonPolitikaHedefAlt = sonPolitikaHedefAlt_;
    bool& baglamMenuAcildi = baglamMenuAcildi_;
    ui::AracCubuguSistemi& g_araclar = g_araclar_;
    ui::SeritCubugu& g_serit = g_serit_;
    AyarlarPencereDurum& ayarlarDurum = ayarlarDurum_;
    std::vector<Vec3f>& hizliDunyaNokta = hizliDunyaNokta_;
    const f32 kareSuresiMs = kareSuresiMs_;
    const int kare = kare_;
    // ---- Per-window alias bloğu: gövde bare isimleri PARAM ort'un belge/state/bglm/
    // imgui'sinden alır (eski kareGovdesi captured alias'larının aynısı). Tek pencerede
    // BİREBİR; çok-pencerede her pencere kendi verisini kullanır. ----
    ImGuiKatman& arayuz = *ort.imguiPtr;
    Belge& belge = *ort.belgePtr;
    UygulamaBaglam& bglm = *ort.bglmPtr;
    // İmleç/koordinat yardımcıları bglm'den (per-window doğru belge'ye bağlı;
    // yakalanan calistir-yerel'lerini gölgeler). Ana pencerede bglm.X == yerel X.
    auto& imlecDunya = bglm.imlecDunya;
    auto& imlecYuzey = bglm.imlecYuzey;
    auto& imlecVurus = bglm.imlecVurus;
    auto& uvDunya = bglm.uvDunya;
    auto& ekranUV = bglm.ekranUV;
    auto& sketchEnYakin = bglm.sketchEnYakin;
    auto& sketchAracTikSayi = bglm.sketchAracTikSayi;
    auto& sketchAracTamamla = bglm.sketchAracTamamla;
    auto& sketchNesne = bglm.sketchNesne;
    auto& sketchDuzenMi = bglm.sketchDuzenMi;
    auto& sketchSecimYay = bglm.sketchSecimYay;
    auto& sketchDuzenIlerlet = bglm.sketchDuzenIlerlet;  // düzenleme fazı ilerlet (Enter/Devam)
    SecimGpu& secim = belge.secim;
    Cekirdek& cekirdek = belge.cekirdek;
    Sahne& sahne = belge.sahne;
    SecimSistemi& secimSis = belge.secimSis;
    OlcumSistemi& olcum = belge.olcum;
    AlanSecim& alan = belge.alan;
    betik::MakroMotor& makro = belge.makro;
    eklenti::EklentiYonetici& eklentiler = belge.eklentiler;
    cam::CamSistemi& camSis = belge.camSis;
    parca::PartDesignSistemi& partD = belge.partD;
    gsd::GsdSistemi& gsdSis = belge.gsdSis;
    analiz::AnalizSistemi& analizSis = belge.analizSis;
    DokumSistemi& dokumSis = belge.dokumSis;
    SahneRefSecim& refSecim = belge.refSecim;
    OrbitKamera& kamera = belge.kamera;
    FareDurum& fare = ort.fare;
    u32& menuKimlik = ort.menuKimlik;
    bool& menuAc = ort.menuAc;
    HizliUIDurum& hizliDurum = ort.hizliDurum;
    bool& hizli3dAcik = hizliDurum.hizli3dAcik;
    u32& hizli3dKimlik = hizliDurum.hizli3dKimlik;
    int& hizliKutukMod = hizliDurum.hizliKutukMod;
    bool& hizliKutukSahne = hizliDurum.hizliKutukSahne;
    f32& hizliKutukSaydam = hizliDurum.hizliKutukSaydam;
    int& hizliYonSecim = hizliDurum.hizliYonSecim;
    bool& pickFiltreYedekVar = ort.pickFiltreYedekVar;
    bool& pickYedYuz = ort.pickYedYuz;
    bool& pickYedKenar = ort.pickYedKenar;
    bool& pickYedNokta = ort.pickYedNokta;
    bool& pickYedGovde = ort.pickYedGovde;
    Vec3f& hizliYon1 = hizliDurum.hizliYon1;
    Vec3f& hizliYon2 = hizliDurum.hizliYon2;
    bool& hizliYon1Var = hizliDurum.hizliYon1Var;
    bool& hizliYon2Var = hizliDurum.hizliYon2Var;
    Vec3f (&hizliKose)[8] = hizliDurum.hizliKose;
    bool& hizliKutuVar = hizliDurum.hizliKutuVar;
    Vec3f& hizliBoyut = hizliDurum.hizliBoyut;
    PusulaDurum& pusula = ort.pusula;
    KesitGizmoDurum& kesitGizmo = ort.kesitGizmo;
    SecimYigini& secimYigini = ort.secimYigini;
    bool& paletAcik = ort.paletAcik;
    bool& paletOdakla = ort.paletOdakla;
    bool& hizliGorunumAcik = ort.hizliGorunumAcik;
    IsikDurum& isikDurum = ort.isikDurum;
    bool& panelIsik = ort.panelIsik;
    bool& isikUstte = ort.isikUstte;
    bool& panelAgac = ort.panelAgac;
    bool& panelOzellik = ort.panelOzellik;
    bool& panelOlay = ort.panelOlay;
    bool& agacIcinde = ort.agacIcinde;
    bool& ozellikAcikOnceki = ort.ozellikAcikOnceki;
    bool& agacKaydirGerek = ort.agacKaydirGerek;
    bool& agacUstte = ort.agacUstte;
    bool& ozellikUstte = ort.ozellikUstte;
    bool& olayUstte = ort.olayUstte;
    bool& islerUstte = ort.islerUstte;
    bool& olcumUstte = ort.olcumUstte;
    bool& camUstte = ort.camUstte;
    bool& panelIsler = ort.panelIsler;
    bool& panelMakro = ort.panelMakro;
    bool& panelEklenti = ort.panelEklenti;
    SketchUIDurum& sketchDurum = ort.sketchDurum;
    Varlik& aktifSketch = sketchDurum.aktifSketch;
    bool& panelSketch = sketchDurum.panelSketch;
    std::string& sketchArac = sketchDurum.sketchArac;
    std::vector<std::array<f64, 2>>& sketchNoktalar = sketchDurum.sketchNoktalar;
    std::vector<u32>& sketchSecili = sketchDurum.sketchSecili;
    std::vector<u32>& sketchSeciliKisit = sketchDurum.sketchSeciliKisit;
    eskiz::SnapSonuc& sonSnap = sketchDurum.sonSnap;
    bool& sketchYapiMod = sketchDurum.sketchYapiMod;
    bool& sketchOtoGeoKisit = sketchDurum.sketchOtoGeoKisit;
    int& sketchPolyKenar = sketchDurum.sketchPolyKenar;
    int& sketchDuzenFaz = sketchDurum.sketchDuzenFaz;
    std::array<f64, 2>& sketchDuzenRef = sketchDurum.sketchDuzenRef;
    char (&sketchDuzGiris)[4][32] = sketchDurum.sketchDuzGiris;
    bool& pah3DAcik = ort.pah3DAcik;
    bool& pah3DYuvarlat = ort.pah3DYuvarlat;
    char (&pah3DGiris)[32] = ort.pah3DGiris;
    size_t& oncekiIsSayi = ort.oncekiIsSayi;
    u32& olcumSurukle = ort.olcumSurukle;
    int& olcumSurukleMod = ort.olcumSurukleMod;
    DugumPusula& ozellikEski = ort.ozellikEski;
    char (&paletSorgu)[128] = ort.paletSorgu;
    std::string& durumMesaj = ort.durumMesaj;
    f32& durumMesajBitis = ort.durumMesajBitis;
    f32& simdikiZaman = ort.simdikiZaman;
    f32& sonEnBoy = ort.sonEnBoy;
    f32& sonG = ort.sonG;
    f32& sonY_ = ort.sonY_;
    uint64_t& vpDoku = ort.vpDoku;
    VkImageView& vpKayitliGorunum = ort.vpKayitliGorunum;
    u32& istenenVpG = ort.istenenVpG;
    u32& istenenVpY = ort.istenenVpY;
    f32& vpRectMinX = ort.vpRectMinX;
    f32& vpRectMinY = ort.vpRectMinY;
    bool& vpHover = ort.vpHover;
    bool& dockKuruldu = ort.dockKuruldu;
    bool& hoverBilgiAcik = ort.hoverBilgiAcik;
    auto& bilgiVer = bglm.bilgiVer;
    auto& tumunuSigdir = bglm.tumunuSigdir;
    auto& secimiSigdir = bglm.secimiSigdir;
    auto& seciliSil = bglm.seciliSil;
    auto& gorunurDegistir = bglm.gorunurDegistir;
    auto& boolIslem = bglm.boolIslem;
    // ---- Çok-pencere: BU pencerenin ImGui context'ini aktif yap + render hedefini
    // kendi yüzeyi/viewport'una yönlendir. Ana pencere → varsayılan (ana yüzey);
    // ek pencere → kendi PencereYuzeyi + ViewportHedef (aktifYuzeyAyarla). ----
    arayuz.ctxAktif();
    if (anaMi_) vulkan.aktifSifirla();
    else vulkan.aktifYuzeyAyarla(yuzeySahip_.get(), vpSahip_.get());
    kamera.guncelle(dt);
    // Tema değişimini izle (galeri/Lua hangi yoldan değiştirirse değiştirsin)
    // → kalıcı ayara yaz (ptr karşılaştırma: değişmedikçe bedava).
    {
      static const char* sonTemaAd = ui::tema().ad;
      if (ui::tema().ad != sonTemaAd) {
        sonTemaAd = ui::tema().ad;
        ayarYaz("tema", sonTemaAd);
      }
    }
    // Ölçü çizgisi varsayılan yönü ekrana baksın diye kamera bazisini ölçüme ver.
    olcum.kameraSag = kamera.yonelim().dondur({1, 0, 0});
    olcum.kameraYukari = kamera.yonelim().dondur({0, 1, 0});
    sahne.bekleyenleriYenidenInsa();  // ozellik degisimiyle isaretlenen dugumler (kok/reaktif)
    // Tamamlanan arka plan islerinin finalize'i (GPU yukleme/sahne) burada,
    // kare kaydi baslamadan once main-thread'de kosar; GorevBasladi/Bitti yayinlar.
    cekirdek.gorevler().pompala(cekirdek.olaylar());
    // Makro per-frame callback'leri (fiko.guncelle) + eklenti kareGuncelle.
    makro.kareGuncelle(dt);
    eklentiler.kareGuncelle(dt);
    // Parametrik/link zinciri: kirli nesneleri topolojik sırada yeniden hesapla.
    if (cekirdek.recompute().kirliVar())
      cekirdek.recompute().hesapla(cekirdek, cekirdek.olaylar());

    // ---- Hızlı 3D kütük kutusu: kare başına BİR kez (sahne overlay + panel aynı veri) ----
    // İmza-önbellek: girdiler (gövde/mod/yön/matris/mesh) değişmedikçe O(n) PCA/
    // dönüşüm YENİDEN KOŞMAZ (büyük mesh'te panel açıkken kare bütçesi korunur).
    hizliKutuVar = false;
    if (hizli3dAcik || hizliKutukSahne) {
      if (Dugum* hd = sahne.bul(hizli3dKimlik)) {
        const Mat4f M = hd->matris();
        static u64 hizliImza = 0;
        static bool hizliSonVar = false;
        u64 im = 1469598103934665603ull;
        const auto kat = [&](u64 v) { im = (im ^ v) * 1099511628211ull; };
        kat(hizli3dKimlik); kat((u64)hizliKutukMod);
        kat((u64)hizliYon1Var * 2 + (u64)hizliYon2Var);
        const auto katF = [&](f32 f) { u32 b; std::memcpy(&b, &f, 4); kat(b); };
        katF(hizliYon1.x); katF(hizliYon1.y); katF(hizliYon1.z);
        katF(hizliYon2.x); katF(hizliYon2.y); katF(hizliYon2.z);
        for (int i = 0; i < 16; ++i) katF((&M.e[0][0])[i]);
        kat((u64)hd->cpuKonum.size());
        kat((u64)reinterpret_cast<uintptr_t>(hd->cpuKonum.data()));
        katF(hd->yerelSinir.asgari.x); katF(hd->yerelSinir.azami.x);
        katF(hd->yerelSinir.asgari.y); katF(hd->yerelSinir.azami.y);
        katF(hd->yerelSinir.asgari.z); katF(hd->yerelSinir.azami.z);
        if (im == hizliImza) {
          hizliKutuVar = hizliSonVar;  // önbellek geçerli (hizliKose/hizliBoyut korunur)
        } else {
        hizliImza = im;
        if (hizliKutukMod == 2 && hizliYon1Var) {
          hizliDunyaNokta.clear();
          hizliDunyaNokta.reserve(hd->cpuKonum.size());
          for (const Vec3f& p : hd->cpuKonum) {
            const Vec4f w = M * Vec4f{p.x, p.y, p.z, 1.0f};
            hizliDunyaNokta.push_back({w.x, w.y, w.z});
          }
          const KutukKutu k = kutukKutuYon(hizliDunyaNokta, hizliYon1, hizliYon2Var, hizliYon2);
          if (k.gecerli) {
            for (int i = 0; i < 8; ++i) hizliKose[i] = k.kose[i];
            hizliKutuVar = true;
          }
        } else {
          const KutukKutu k = hizliKutukMod == 1 ? kutukKutuObb(hd->cpuKonum)
                                                 : kutukKutuAabb(hd->yerelSinir);
          if (k.gecerli) {
            for (int i = 0; i < 8; ++i) {
              const Vec4f w = M * Vec4f{k.kose[i].x, k.kose[i].y, k.kose[i].z, 1.0f};
              hizliKose[i] = {w.x, w.y, w.z};
            }
            hizliKutuVar = true;
          }
        }
        if (hizliKutuVar) hizliBoyut = kutukBoyutKoseler(hizliKose);
        hizliSonVar = hizliKutuVar;
        }  // imza-önbellek
      }
    }

    // Viewport hedefini 3D pencerenin son isteğine göre ayarla (1 kare gecikme)
    vulkan.viewportBoyut(istenenVpG, istenenVpY);
    secim.boyutAyarla(vulkan.viewportBoyutu().width, vulkan.viewportBoyutu().height);
    if (vpKayitliGorunum != vulkan.viewportRenkGorunum()) {
      if (vpDoku) arayuz.dokuKaldir(vpDoku);
      vpDoku = arayuz.dokuEkle(vulkan.viewportOrnekleyici(), vulkan.viewportRenkGorunum());
      vpKayitliGorunum = vulkan.viewportRenkGorunum();
    }
    // Ek belgeler: hedef boyut + kamera yumuşatma + belge-scope kare bakımı + doku
    belgeHedefleriniGuncelle(belgeYon, arayuz, vulkan, dt);
    wtecKafaGuncelle(arayuz, vulkan, dt, shaderDizin_);  // WTEC açılı kafa mini-viewport

    arayuz.kareBasla();
    ImGuiIO& io = ImGui::GetIO();
    const f32 ekranG = io.DisplaySize.x, ekranY = io.DisplaySize.y;
    const SecimKip kip = sahne.kip();
    const char* kipAd[] = {"Gövde", "Yüz", "Kenar", "Nokta"};

    // ---- Komut paleti: Ctrl+Space toggle (açılışta registry'den beslenir) ----
    if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Space)) {
      if (ort.komutPalet.acikMi()) {
        ort.komutPalet.kapat();
      } else {
        // Universal palet: komut + sahne nesnesi tek fuzzy aramada (kaynaklar canlı).
        ui::universalKaynaklariKur(ort.universalArama, ort.paletKayit, cekirdek, sahne,
                                   [&bglm](u32) { bglm.secimiSigdir(); },
                                   g_sonDosyalar,
                                   [&bglm](const std::string& yol) { fiko::yolAc(bglm, yol); });
        ort.komutPalet.baglaUniversal(&ort.universalArama);
        ort.komutPalet.ac();
      }
    }

    // ---- Dockspace ana çerçeve (CATIA/Unreal editör düzeni) ----
    const ImGuiViewport* anaVp = ImGui::GetMainViewport();
    ImGui::SetNextWindowPos(anaVp->WorkPos);
    ImGui::SetNextWindowSize(anaVp->WorkSize);
    ImGui::SetNextWindowViewport(anaVp->ID);
    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
    ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
    ImGui::Begin("##dockhost", nullptr,
                 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse |
                     ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
                     ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus);
    ImGui::PopStyleVar(3);

    const ImGuiID dockKimlik = ImGui::GetID("FikoDock");
    ImGui::DockSpace(dockKimlik, ImVec2(0, 0), ImGuiDockNodeFlags_PassthruCentralNode);

    // İlk çalıştırmada varsayılan yerleşim
    if (!dockKuruldu) {
      dockKuruldu = true;
      ImGui::DockBuilderRemoveNode(dockKimlik);
      ImGui::DockBuilderAddNode(dockKimlik, ImGuiDockNodeFlags_DockSpace);
      ImGui::DockBuilderSetNodeSize(dockKimlik, anaVp->WorkSize);
      ImGuiID merkez = dockKimlik;
      const ImGuiID sol = ImGui::DockBuilderSplitNode(merkez, ImGuiDir_Left, 0.19f, nullptr, &merkez);
      const ImGuiID alt = ImGui::DockBuilderSplitNode(merkez, ImGuiDir_Down, 0.24f, nullptr, &merkez);
      ImGui::DockBuilderDockWindow("Ürün Ağacı", sol);
      ImGui::DockBuilderDockWindow("Olay Denetçi", alt);
      ImGui::DockBuilderDockWindow("3D Görünüm", merkez);
      // 3D görünüm merkez düğümü sabit: tab çubuğu gizli + undock/split engelli.
      if (ImGuiDockNode* merkezNode = ImGui::DockBuilderGetNode(merkez))
        merkezNode->LocalFlags |= ImGuiDockNodeFlags_NoTabBar | ImGuiDockNodeFlags_NoUndocking |
                                  ImGuiDockNodeFlags_NoDockingSplit;
      ImGui::DockBuilderFinish(dockKimlik);
    }

    // ---- Menü + araç çubuğu (ana pencerede) ----
    if (ImGui::BeginMenuBar()) {
      // Menü çubuğu registry'den (cekirdek.menuler) — gruplar sırayla, her grup
      // kendi MenuOge'lerini çizer. Yeni komut = registry'ye MenuOge eklemek.
      for (const std::string& grup : cekirdek.menuler().gruplar()) {
        if (ImGui::BeginMenu(grup.c_str())) {
          for (const MenuOge& o : cekirdek.menuler().hepsi()) {
            if (o.grup != grup) continue;
            const bool etkin = !o.etkinMi || o.etkinMi(cekirdek);
            std::string etk = o.simge.empty() ? o.ad : (o.simge + "  " + o.ad);
            if (ImGui::MenuItem(etk.c_str(), nullptr, false, etkin)) cekirdek.komutCalistir(o, "menu");
          }
          ImGui::EndMenu();
        }
      }
      if (ImGui::BeginMenu("Seçim")) {
        ImGui::TextDisabled("Seçim filtresi (çoklu)");
        ImGui::MenuItem("◧ Yüz", "1", &secimSis.fYuz);
        ImGui::MenuItem("▤ Kenar", "2", &secimSis.fKenar);
        ImGui::MenuItem("• Nokta", "3", &secimSis.fNokta);
        ImGui::MenuItem("◼ Gövde", "4", &secimSis.fGovde);
        ImGui::EndMenu();
      }
      if (ImGui::BeginMenu("Pencereler")) {
        ImGui::MenuItem("Ürün Ağacı", nullptr, &panelAgac);
        ImGui::MenuItem("  ↳ 3B Görünüm İçinde", nullptr, &agacIcinde, panelAgac);
        ImGui::MenuItem("Işıklar", nullptr, &panelIsik);
        ImGui::MenuItem("Özellikler", nullptr, &panelOzellik);
        ImGui::MenuItem("Olay Denetçi", nullptr, &panelOlay);
        ImGui::MenuItem("İşlemler", nullptr, &panelIsler);
        ImGui::MenuItem("Sketcher", nullptr, &panelSketch);
        ImGui::MenuItem("Ölçüm Penceresi", "M", &olcum.acik);
        ImGui::MenuItem("Üretim Programı", nullptr, &camSis.acik);
        // CAM Üretim Ağacı artık ana Ürün Ağacı'nda (ayrı pencere kaldırıldı).
        ImGui::MenuItem("Makro Konsolu", nullptr, &panelMakro);
        ImGui::MenuItem("Eklenti Yöneticisi", nullptr, &panelEklenti);
        ImGui::MenuItem("✨ FikoUi Galerisi", nullptr, &ui::galeriAcik());
        {
          bool serittGor = g_serit.gorunurMu();
          if (ImGui::MenuItem("▭ Alt Şerit", nullptr, &serittGor)) {
            if (serittGor) g_serit.goster(); else g_serit.gizle();
          }
        }
        if (ImGui::MenuItem("⌘ Komut Paleti", "Ctrl+Space")) {
          ui::universalKaynaklariKur(ort.universalArama, ort.paletKayit, cekirdek, sahne,
                                     [&bglm](u32) { bglm.secimiSigdir(); },
                                     g_sonDosyalar,
                                     [&bglm](const std::string& yol) { fiko::yolAc(bglm, yol); });
          ort.komutPalet.baglaUniversal(&ort.universalArama);
          ort.komutPalet.ac();
        }
        // Ek belge pencereleri (MDI): kapatılan belge buradan geri açılır.
        if (!belgeYon.hepsi().empty()) {
          ImGui::Separator();
          ImGui::TextDisabled("Belgeler");
          for (auto& bk : belgeYon.hepsi())
            ImGui::MenuItem(bk->ad.c_str(), nullptr, &bk->acik);
        }
        ImGui::EndMenu();
      }
      if (ImGui::BeginMenu("Compass")) {
        const auto sg = sahne.seciliGovdeler();
        if (ImGui::MenuItem("Seçilene Yapıştır", nullptr, false, !sg.empty())) {
          pusula.bagli = true; pusula.govde = sg.back(); pusula.tutamac = -1;
        }
        if (ImGui::MenuItem("Bırak (köşeye)", "Esc", false, pusula.bagli)) {
          pusula.bagli = false; pusula.tutamac = -1;
        }
        ImGui::Separator();
        ImGui::TextDisabled("Kırmızı kareyi parçaya sürükle");
        ImGui::EndMenu();
      }
      ImGui::TextDisabled("│");
      // Workbench seçici (aktif = mavi). Araç çubukları aktif workbench'e göre
      // değişir; taşınabilir toolbar pencerelerinde gösterilir (aşağıda).
      {
        auto& wbk = cekirdek.workbenchler();
        for (const WorkbenchOge& wb : wbk.hepsi()) {
          const bool aktifWb = wb.kimlik == wbk.aktif();
          if (aktifWb) ImGui::PushStyleColor(ImGuiCol_Button, {0.13f, 0.50f, 0.85f, 1.0f});
          const std::string lbl = wb.simge.empty() ? wb.ad : (wb.simge + " " + wb.ad);
          ImGui::PushID(wb.kimlik.c_str());
          if (ImGui::SmallButton(lbl.c_str())) wbk.aktifAyarla(cekirdek, wb.kimlik);
          ImGui::PopID();
          if (aktifWb) ImGui::PopStyleColor();
          ImGui::SameLine();
        }
      }
      ImGui::TextDisabled("│");
      if (ImGui::SmallButton("✕ Sil")) seciliSil();
      ImGui::SameLine(); if (ImGui::SmallButton("⛶ Sığdır")) tumunuSigdir();
      ImGui::SameLine(); if (ImGui::SmallButton("≡ Komut")) { paletAcik = true; paletOdakla = true; paletSorgu[0] = 0; }
      ImGui::TextDisabled("│");
      // Seçim filtresi toggle'ları (etkin = mavi). Çoklu — CATIA tarzı.
      struct { const char* et; bool* bayrak; } filtreler[] = {
          {"◧ Yüz", &secimSis.fYuz}, {"▤ Kenar", &secimSis.fKenar},
          {"• Nokta", &secimSis.fNokta}, {"◼ Gövde", &secimSis.fGovde}};
      for (int i = 0; i < 4; ++i) {
        ImGui::SameLine(0.0f, i ? 3.0f : 8.0f);
        const bool aktif = *filtreler[i].bayrak;
        if (aktif) ImGui::PushStyleColor(ImGuiCol_Button, {0.13f, 0.50f, 0.85f, 1.0f});
        if (ImGui::SmallButton(filtreler[i].et)) *filtreler[i].bayrak = !*filtreler[i].bayrak;
        if (aktif) ImGui::PopStyleColor();
      }
      // Alan seçimi (trap) tip düğme grubu — aktif olan vurgulu (CATIA dışlayıcı).
      ImGui::SameLine(); ImGui::TextDisabled("│");
      struct { const char* et; TrapTip t; } trapDug[] = {
          {"▭", TrapTip::Dikdortgen}, {"▢", TrapTip::KesenDikdortgen},
          {"⬠", TrapTip::Poligon},    {"✎", TrapTip::FircaIzi},
          {"▣", TrapTip::DisRadikdortgen}, {"◪", TrapTip::DisKesen}};
      for (int i = 0; i < 6; ++i) {
        ImGui::SameLine(0.0f, i ? 2.0f : 8.0f);
        const bool aktif = alan.tip == trapDug[i].t;
        if (aktif) ImGui::PushStyleColor(ImGuiCol_Button, {0.20f, 0.60f, 0.35f, 1.0f});
        ImGui::PushID(2000 + i);
        if (ImGui::SmallButton(trapDug[i].et)) alan.tip = trapDug[i].t;
        ImGui::PopID();
        if (aktif) ImGui::PopStyleColor();
        if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s (T döngü)", trapAdi(trapDug[i].t));
      }
      char fps[48];
      std::snprintf(fps, sizeof(fps), "%.1f ms", kareSuresiMs);
      ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize(fps).x - 4);
      ImGui::TextColored({0.35f, 0.65f, 1.0f, 1.0f}, "%s", fps);
      ImGui::EndMenuBar();
    }
    ImGui::End();  // dockhost

    // ---- 3D Görünüm penceresi (MDI child, offscreen doku) ----
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
    // NoBringToFrontOnFocus: 3D görünüme tıklamak onu yüzen panellerin üstüne çıkarmaz
    // → ayrık paneller (Ürün Ağacı vb.) varsayılan olarak hep önde kalır.
    // NoMove: 3D görünüm sürüklenip taşınamaz / ayrı OS penceresine çıkarılamaz (sabit merkez).
    if (ImGui::Begin("3D Görünüm", nullptr,
                     ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoMove)) {
      // Dock düğümünü her kare kilitle (ini geri-yüklemesinden bağımsız): tab çubuğu
      // gizli + tab'dan tutup ayrı pencere/OS-viewport'a sürükleme + node bölme engelli.
      if (ImGuiDockNode* mNode = ImGui::GetWindowDockNode())
        mNode->LocalFlags |= ImGuiDockNodeFlags_NoTabBar | ImGuiDockNodeFlags_NoUndocking |
                             ImGuiDockNodeFlags_NoDockingSplit;
      vpHover = ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows);
      const ImVec2 avail = ImGui::GetContentRegionAvail();
      istenenVpG = (u32)std::max(avail.x, 16.0f);
      istenenVpY = (u32)std::max(avail.y, 16.0f);
      const ImVec2 imlecKonum = ImGui::GetCursorScreenPos();
      vpRectMinX = imlecKonum.x;
      vpRectMinY = imlecKonum.y;
      // Arka plan (bulut+logo) sahne dokusundan ÖNCE, aynı drawlist'e → sahne
      // saydam-temiz viewport ile bunun önüne kompoze olur (derinlik korunur).
      arkaPlanCiz(ImGui::GetWindowDrawList(), imlecKonum, avail);
      if (vpDoku) ImGui::Image((ImTextureID)vpDoku, avail);

      // ---- CATIA spec tree: ürün ağacı 3B görünümün İÇİNDE (sol overlay) ----
      // Ayrık pencere yerine viewport'a gömülü yarı-saydam panel → z-order
      // sorunu yapısal olarak yok (aynı pencerenin child'ı, Image'ın hep üstünde).
      if (panelAgac && agacIcinde) {
        const float agacGen = 330.0f;
        const float agacYuk = std::max(160.0f, avail.y - 24.0f);
        const ImVec2 agacMin{vpRectMinX + 10.0f, vpRectMinY + 12.0f};
        ImGui::SetCursorScreenPos(agacMin);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 6));
        // CATIA spec tree: panelin OPAK zemini yok — ağaç 3B sahnenin üstünde
        // yüzer (saydam). Satırların kendi hover/seçim vurgusu + metin gölgesi
        // (agac_gorunum) okunurluğu sağlar. NoBackground + ChildBg alpha 0.
        ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4{0, 0, 0, 0});
        ImGui::SetNextWindowBgAlpha(0.0f);
        ImGui::BeginChild("##agacOverlay", {agacGen, agacYuk}, false,
                          ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBackground);
        const bool agacSatirUstunde = urunAgaciIcerikCiz(bglm);
        ImGui::EndChild();
        ImGui::PopStyleColor(1);
        ImGui::PopStyleVar(1);
        // Tık-geçiş (CATIA): kamera/pick YALNIZ gerçek bir ağaç SATIRI üstünde
        // bastırılır. Ağacın BOŞ alanında fare arkadaki 3B sahneye geçer (orbit/
        // pan + seçim çalışır). Eski davranış tüm panel rect'ini tüketiyordu.
        if (agacSatirUstunde) vpHover = false;
      }

      // ---- CATIA 3B Compass (navigasyon + manipülatör) ----
      bool pusulaUstunde = false;
      pusulaCizVeIsle(pusula, sahne, kamera, ImGui::GetWindowDrawList(), io, vpHover,
                      vpRectMinX, vpRectMinY, avail.x, avail.y, sonEnBoy, pusulaUstunde);

      // ---- Kesit düzlemi 3D gizmosu (sürükle = normali boyunca kaydır) ----
      // pusulaUstunde OR'lanır → tutamaç üstünde kamera/seçim jestleri bastırılır.
      kesitGizmoCizVeIsle(analizSis, sahne, kamera, ImGui::GetWindowDrawList(), io,
                          vpHover && !pusulaUstunde, vpRectMinX, vpRectMinY, avail.x,
                          avail.y, sonEnBoy, kesitGizmo, pusulaUstunde,
                          [](u32 k) { return kesitPencereAcikMi(k); });
      // Sürükleme bitti → tek DonusumIslem olarak undo geçmişine (ara kareler girmez)
      if (pusula.surukleTamam) {
        pusula.surukleTamam = false;
        if (Dugum* pd = sahne.bul(pusula.surukleGovdeKimlik)) {
          const Vec3d fk = pd->donusum.konum - pusula.surukleEski.konum;
          const bool konumDegisti = fk.x * fk.x + fk.y * fk.y + fk.z * fk.z > 1e-14;
          const bool yonelimDegisti =
              std::abs(pd->donusum.yonelim.x - pusula.surukleEski.yonelim.x) +
                  std::abs(pd->donusum.yonelim.y - pusula.surukleEski.yonelim.y) +
                  std::abs(pd->donusum.yonelim.z - pusula.surukleEski.yonelim.z) +
                  std::abs(pd->donusum.yonelim.w - pusula.surukleEski.yonelim.w) >
              1e-12;
          if (konumDegisti || yonelimDegisti)
            cekirdek.uygula(std::make_unique<DonusumIslem>(
                sahne, pd->kimlik, pusula.surukleEski, pd->donusum));
        }
      }

      // ---- Ortak seçim-modu HUD (viewport üst-orta) — K9: tek görsel dil ----
      // CAM / RefSecim / gelecek seçim modları hepsi buradan çizer: aynı
      // konum/padding/yuvarlatma; yalnız bağlam vurgusu (kenar rengi) değişir.
      // sira = üst üste binmeyi önleyen istif indeksi (0=üst, 1=altına...).
      const auto secimHudCiz = [&](const char* metin, ImU32 kenarRenk, int sira) {
        if (!metin || !metin[0]) return;
        ImDrawList* hudCiz = ImGui::GetWindowDrawList();
        const ImVec2 ts = ImGui::CalcTextSize(metin);
        const f32 padX = 12.0f, padY = 6.0f;
        const f32 cx = vpRectMinX + avail.x * 0.5f;
        const f32 ustY = vpRectMinY + 14.0f + sira * (ts.y + padY * 2 + 8.0f);
        const ImVec2 min{cx - ts.x * 0.5f - padX, ustY};
        const ImVec2 max{cx + ts.x * 0.5f + padX, min.y + ts.y + padY * 2};
        hudCiz->AddRectFilled(min, max, IM_COL32(18, 24, 28, 228), 6.0f);
        hudCiz->AddRect(min, max, kenarRenk, 6.0f, 0, 1.5f);
        hudCiz->AddText({min.x + padX, min.y + padY}, IM_COL32(232, 244, 250, 255), metin);
      };
      // Aktif modlar istifle: CAM (cyan) üstte, RefSecim (yeşil) altına.
      int hudSira = 0;
      if (camSis.secim().aktif())
        secimHudCiz(camSis.secim().hudMetin(), IM_COL32(26, 217, 255, 220), hudSira++);
      if (analizSis.secimAktif())
        secimHudCiz(analizSis.hudMetin(), IM_COL32(120, 200, 120, 230), hudSira++);
      // RefSecim rozeti: 🎯 + hud + beklenen tür etiketi (mod-unut panzehiri, vurgulu yeşil).
      if (refSecim.aktif()) {
        const int bt = refSecim.beklenenTur();
        const char* turEt = bt == 2 ? "KENAR" : bt == 1 ? "YÜZ" : bt == 3 ? "NOKTA" : "SERBEST";
        char refHud[192];
        std::snprintf(refHud, sizeof(refHud), "🎯  %s   ·   [%s]", refSecim.hudMetin(), turEt);
        secimHudCiz(refHud, IM_COL32(80, 240, 130, 240), hudSira++);
        if (vpHover) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);  // mod aktif ipucu
      }

      // ---- Ölçüm overlay: dünya→ekran projeksiyon + ImGui drawlist ----
      // Ayrı GPU pipeline yok: kare başına yalnız N nokta projeksiyonu (önemsiz).
      // Kalıcı ölçümler + canlı önizleme; etiket sürükleme (ofset) + sağ sürükleme (döndür).
      {
        ImDrawList* ciz = ImGui::GetWindowDrawList();
        const Mat4f olcVp = kamera.izdusum(sonEnBoy) * kamera.gorunum();
        const auto ekrana = [&](const Vec3f& p, ImVec2& s) -> bool {
          const Vec4f h = olcVp * Vec4f{p.x, p.y, p.z, 1.0f};
          if (h.w <= 1e-4f) return false;
          s.x = vpRectMinX + (h.x / h.w * 0.5f + 0.5f) * avail.x;
          s.y = vpRectMinY + (h.y / h.w * 0.5f + 0.5f) * avail.y;
          return true;
        };

        // ---- Selection Stack (CATIA "Other Selection"): liste + 3B daire + canlı önizleme ----
        if (secimYigini.acikMi()) {
          // Tekerlek + ok ile gez — vurguAyarla/peek ile AYNI karede (tutarlı, kaymasız).
          if (ImGui::GetIO().MouseWheel != 0.0f)
            secimYigini.gez(ImGui::GetIO().MouseWheel > 0.0f ? -1 : 1);
          if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) secimYigini.gez(1);
          if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) secimYigini.gez(-1);
          bool sec = secimYigini.ciz(ciz, ekrana);
          if (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))
            sec = true;
          if (const SecimAday* aday = secimYigini.aktifAday()) {
            secimSis.vurguAyarla(aday->govde, aday->alt, aday->tur);  // gezilen aday canlı highlight
            kareIste(2);
            if (sec) {  // tık/Enter → aday gerçek seçime çevrilir
              secimSis.tikla(ImGui::GetIO().KeyCtrl);
              secimYigini.kapat();
              secimSis.hoverBastir(false);
            }
          }
        }

        // ---- Referans eksen etiketleri (x/y/z) + orijin noktası (CATIA tarzı) ----
        // Etiketler + merkez, eksen ÇİZGİLERİYLE aynı düğüm dönüşümünden türetilir
        // (pusula ile taşıma tek grup olarak taşır). Çizgiler render'da dugumMvp =
        // referansGorselMvp(vp, d.matris(), pivot, ...) ile çizilir; etiket/merkez
        // AYNI kompozit matrisle projekte edilir → çizgi/yazı/merkez birlikte hareket.
        {
          const ImU32 refRenk = IM_COL32(205, 208, 216, 255);
          const f32 kamMesafe = kamera.mesafe();
          // Kompozit MVP ile yerel noktayı ekrana projekte et (ekrana()'nın matris
          // versiyonu; h.w kontrolü + viewport-yerel piksel).
          const auto ekranaMvp = [&](const Mat4f& mvp, const Vec3f& p, ImVec2& s) -> bool {
            const Vec4f h = mvp * Vec4f{p.x, p.y, p.z, 1.0f};
            if (h.w <= 1e-4f) return false;
            s.x = vpRectMinX + (h.x / h.w * 0.5f + 0.5f) * avail.x;
            s.y = vpRectMinY + (h.y / h.w * 0.5f + 0.5f) * avail.y;
            return true;
          };
          // TEK Eksen Sistemi düğümünden x/y/z etiketleri + orijin noktası. Eksen
          // renkleriyle eşleşen etiket rengi (X kırmızı / Y yeşil / Z mavi).
          for (auto& d : sahne.dugumler()) {
            if (d.tip != DugumTip::EksenSistemi || !sahne.agacGorunur(d.kimlik)) continue;
            // Çizgilerle birebir aynı ekran-sabit kompozit (pivot = düğüm dünya konumu).
            const Vec3f pivot{(f32)d.donusum.konum.x, (f32)d.donusum.konum.y,
                              (f32)d.donusum.konum.z};
            const Mat4f mvp = referansGorselMvp(olcVp, d.matris(), pivot, kamMesafe,
                                                REFERANS_ORAN_EKSEN, d.gorselOran);
            struct ET { Vec3f yon; const char* et; ImU32 renk; };
            const ET etler[3] = {
                {{1, 0, 0}, "x", IM_COL32(235, 70, 72, 255)},
                {{0, 1, 0}, "y", IM_COL32(78, 210, 92, 255)},
                {{0, 0, 1}, "z", IM_COL32(84, 135, 250, 255)}};
            for (const ET& e : etler) {
              ImVec2 s;  // yerel uç: yön · (EKS_L 5.0 + pay); ölçek MVP'ye gömülü.
              if (ekranaMvp(mvp, e.yon * 5.55f, s))
                ciz->AddText({s.x + 3, s.y - 7}, e.renk, e.et);
            }
            ImVec2 orij;  // yerel origin (0,0,0) → düğüm konumuna gider.
            if (ekranaMvp(mvp, Vec3f{0, 0, 0}, orij)) ciz->AddCircleFilled(orij, 2.8f, refRenk);
          }
        }

        // ---- Alan seçimi (trap) overlay: CATIA görsel dili (tipe göre stil) ----
        // Nokta listeleri viewport-yerel (lx/ly) → mutlak ekran için vpRectMin ekle.
        if (alan.aktifMi()) {
          const bool kesen = alan.kesenMi();
          const bool dis = alan.tip == TrapTip::DisRadikdortgen ||
                           alan.tip == TrapTip::DisKesen;
          // Renk: içinde=mavi, kesen=yeşil, dış=kırmızı, fırça=turuncu.
          ImU32 kenarRenk = dis ? IM_COL32(235, 90, 90, 235)
                            : kesen ? IM_COL32(90, 220, 130, 235)
                                    : IM_COL32(120, 190, 255, 235);
          if (alan.tip == TrapTip::FircaIzi) kenarRenk = IM_COL32(255, 165, 60, 240);
          const ImU32 dolguRenk = dis ? IM_COL32(235, 90, 90, 26)
                                  : kesen ? IM_COL32(90, 220, 130, 26)
                                          : IM_COL32(120, 190, 255, 26);
          const auto mut = [&](const Vec2f& p) {
            return ImVec2(vpRectMinX + p.x, vpRectMinY + p.y);
          };
          // Kesikli kenar (kesen/dış modları): elle segment; içinde = düz.
          const bool kesikli = kesen;
          auto kesikliDikdortgen = [&](ImVec2 a, ImVec2 b) {
            const ImVec2 kose[4] = {{a.x, a.y}, {b.x, a.y}, {b.x, b.y}, {a.x, b.y}};
            for (int i = 0; i < 4; ++i) {
              const ImVec2 p0 = kose[i], p1 = kose[(i + 1) % 4];
              const f32 uz = std::sqrt((p1.x - p0.x) * (p1.x - p0.x) +
                                       (p1.y - p0.y) * (p1.y - p0.y));
              const int seg = std::max(1, (int)(uz / 8.0f));
              for (int s = 0; s < seg; s += 2) {
                const f32 t0 = (f32)s / seg, t1 = (f32)std::min(s + 1, seg) / seg;
                ciz->AddLine({p0.x + (p1.x - p0.x) * t0, p0.y + (p1.y - p0.y) * t0},
                             {p0.x + (p1.x - p0.x) * t1, p0.y + (p1.y - p0.y) * t1},
                             kenarRenk, 1.4f);
              }
            }
          };
          if (alan.tip == TrapTip::FircaIzi) {
            const auto& iz = alan.iz();
            for (std::size_t i = 1; i < iz.size(); ++i)
              ciz->AddLine(mut(iz[i - 1]), mut(iz[i]), kenarRenk, 4.0f);
          } else if (alan.poligonKipMi()) {
            const auto& iz = alan.iz();
            std::vector<ImVec2> pts;
            pts.reserve(iz.size() + 1);
            for (const Vec2f& p : iz) pts.push_back(mut(p));
            if (pts.size() >= 2) {
              // Kapanış ön-izlemesi: son köşe → imleç.
              pts.push_back(ImVec2(io.MousePos.x, io.MousePos.y));
              ciz->AddPolyline(pts.data(), (int)pts.size(), kenarRenk, 0, 1.6f);
              ciz->AddLine(pts.back(), pts.front(), kenarRenk, 1.0f);
            }
            for (const Vec2f& p : iz) ciz->AddCircleFilled(mut(p), 3.0f, kenarRenk);
          } else {
            const ImVec2 a = mut(alan.bas()), b = mut(alan.son());
            const ImVec2 asg{std::min(a.x, b.x), std::min(a.y, b.y)};
            const ImVec2 azm{std::max(a.x, b.x), std::max(a.y, b.y)};
            ciz->AddRectFilled(asg, azm, dolguRenk);
            if (kesikli) kesikliDikdortgen(asg, azm);
            else ciz->AddRect(asg, azm, kenarRenk, 0.0f, 0, 1.4f);
          }
          // Canlı aday sayısı HUD (imleç yakını).
          const Mat4f trapVp = kamera.izdusum(sonEnBoy) * kamera.gorunum();
          const u32 aday = alan.adaySayisi(trapVp, {0.0f, 0.0f}, {avail.x, avail.y});
          char hud[64];
          const char* trapKipAd = sahne.kip() == SecimKip::Yuz    ? "yüz"
                                  : sahne.kip() == SecimKip::Kenar ? "kenar"
                                  : sahne.kip() == SecimKip::Nokta ? "nokta"
                                                                  : "gövde";
          std::snprintf(hud, sizeof(hud), "%u %s", aday, trapKipAd);
          ciz->AddText({io.MousePos.x + 14, io.MousePos.y + 4}, kenarRenk, hud);
        }

        // ---- Sketch overlay → sketch_ui.cpp (sketchOverlayCiz), davranış birebir ----
        // Kare-yerel çizim durumu (ciz/ekrana/io/vpHover/vpRect + sketch helper'ları)
        // SketchCizimBaglam ile geçilir; blok gövdesi modüle taşındı.
        {
          SketchCizimBaglam scb;
          scb.ciz = ciz;
          scb.io = &io;
          scb.vpHover = vpHover;
          scb.vpRectMinX = vpRectMinX;
          scb.vpRectMinY = vpRectMinY;
          scb.vpBoyutX = avail.x;
          scb.vpBoyutY = avail.y;
          scb.ekrana = ekrana;
          scb.uvDunya = uvDunya;
          scb.ekranUV = ekranUV;
          scb.sketchEnYakin = sketchEnYakin;
          sketchOverlayCiz(bglm, scb);
        }

        // Sketch Analizi penceresi → sketch_ui.cpp::sketchAnalizPenceresiCiz (gelişmiş,
        // tablo+düzeltici aksiyonlu). Eski satır-içi mükerrer pencere kaldırıldı.

        // ---- Kütük kutusu overlay (sahnede tel-kafes + ayarlı saydam dolgu) ----
        // Köşeler kare başında dünya-uzayında hesaplandı (hizliKose). Burada yalnız projeksiyon.
        if (hizliKutukSahne && hizliKutuVar) {
          ImVec2 ek[8]; bool gecerli = true;
          for (int i = 0; i < 8; ++i)
            if (!ekrana(hizliKose[i], ek[i])) { gecerli = false; break; }
          if (gecerli) {
            static const int kenar[12][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6},
                                             {6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7}};
            const ImU32 renk = IM_COL32(64, 240, 224, 235);
            const int da = (int)(std::clamp(hizliKutukSaydam, 0.0f, 0.6f) * 255.0f);
            const ImU32 dolgu = IM_COL32(64, 240, 224, da);
            if (da > 1) {
              ciz->AddQuadFilled(ek[0], ek[1], ek[2], ek[3], dolgu);
              ciz->AddQuadFilled(ek[4], ek[5], ek[6], ek[7], dolgu);
              ciz->AddQuadFilled(ek[0], ek[1], ek[5], ek[4], dolgu);
              ciz->AddQuadFilled(ek[3], ek[2], ek[6], ek[7], dolgu);
            }
            for (auto& e : kenar) ciz->AddLine(ek[e[0]], ek[e[1]], renk, 1.6f);
            for (int i = 0; i < 8; ++i)
              ciz->AddCircleFilled(ek[i], 2.6f, IM_COL32(120, 255, 240, 255));
          }
        }
        // ofsetYon'u a→b ekseni etrafında oc.aci kadar döndür (Rodrigues) — döndürme özelliği
        const auto ofsetEt = [](const Olcum& oc) -> Vec3f {
          if (std::abs(oc.aci) < 1e-5f) return oc.ofsetYon;
          Vec3f ax = (oc.b - oc.a);
          if (ax.uzunlukKare() < 1e-10f) return oc.ofsetYon;
          ax = ax.birim();
          const f32 c = std::cos(oc.aci), s = std::sin(oc.aci);
          return oc.ofsetYon * c + ax.capraz(oc.ofsetYon) * s +
                 ax * (ax.nokta(oc.ofsetYon)) * (1.0f - c);
        };

        const auto cizOlcum = [&](Olcum& oc, bool onizleme) {
          if (!oc.gecerli) return;
          const ImU32 ana = onizleme ? IM_COL32(120, 220, 255, 235) : IM_COL32(255, 196, 64, 255);
          const ImU32 soluk = onizleme ? IM_COL32(120, 220, 255, 130) : IM_COL32(255, 196, 64, 150);
          const auto okCiz = [&](ImVec2 uc, ImVec2 hedef) {
            f32 dx = hedef.x - uc.x, dy = hedef.y - uc.y;
            const f32 L = std::sqrt(dx * dx + dy * dy);
            if (L < 1.0f) return;
            dx /= L; dy /= L;
            ciz->AddTriangleFilled(uc, ImVec2(uc.x + dx * 11 - dy * 4, uc.y + dy * 11 + dx * 4),
                                   ImVec2(uc.x + dx * 11 + dy * 4, uc.y + dy * 11 - dx * 4), ana);
          };
          bool ustunde = false;
          const auto etiketCiz = [&](ImVec2 poz, const char* metin) {
            const ImVec2 ts = ImGui::CalcTextSize(metin);
            const ImVec2 rmin(poz.x - ts.x * 0.5f - 5, poz.y - ts.y * 0.5f - 3);
            const ImVec2 rmax(poz.x + ts.x * 0.5f + 5, poz.y + ts.y * 0.5f + 3);
            ustunde = vpHover && ImGui::IsMouseHoveringRect(rmin, rmax);
            ciz->AddRectFilled(rmin, rmax, IM_COL32(22, 26, 36, 225), 4.0f);
            ciz->AddRect(rmin, rmax, ustunde ? IM_COL32(120, 220, 255, 255) : soluk, 4.0f);
            ciz->AddText(ImVec2(rmin.x + 5, rmin.y + 3), IM_COL32(240, 240, 245, 255), metin);
          };
          const auto artiCiz = [&](ImVec2 p) {
            ciz->AddLine(ImVec2(p.x - 5, p.y), ImVec2(p.x + 5, p.y), ana, 1.5f);
            ciz->AddLine(ImVec2(p.x, p.y - 5), ImVec2(p.x, p.y + 5), ana, 1.5f);
          };

          if (oc.tur == OlcumTur::Mesafe || oc.tur == OlcumTur::Uzunluk ||
              oc.tur == OlcumTur::Cap) {
            const Vec3f oy = ofsetEt(oc);
            // Her şahit çizgisinin İKİ ucu da bağımsız: geometri tarafı (taban) + ölçü tarafı (uzatma).
            const Vec3f ga3 = oc.a + oy * oc.taban1;                  // geometri ucu A
            const Vec3f gb3 = oc.b + oy * oc.taban2;                  // geometri ucu B
            const Vec3f oa3 = oc.a + oy * (oc.ofset + oc.uzatma1);    // ölçü ucu A
            const Vec3f ob3 = oc.b + oy * (oc.ofset + oc.uzatma2);    // ölçü ucu B
            ImVec2 ga, gb, oa, ob;
            if (!ekrana(ga3, ga) || !ekrana(gb3, gb) || !ekrana(oa3, oa) || !ekrana(ob3, ob))
              return;
            ciz->AddLine(ga, oa, soluk, 1.0f);
            ciz->AddLine(gb, ob, soluk, 1.0f);
            ciz->AddLine(oa, ob, ana, 1.8f);
            okCiz(oa, ob);
            okCiz(ob, oa);
            const u32 sid = onizleme ? SURUKLE_CANLI : oc.kimlik;
            // oy'nin ekran-uzayı yönü → sürükleme deltasını ofset eksenine projekte et.
            const auto eksenSurukle = [&](const Vec3f& taban3, f32& hedef) {
              ImVec2 s0, s1;
              if (ekrana(taban3, s0) && ekrana(taban3 + oy, s1)) {
                const f32 vx = s1.x - s0.x, vy = s1.y - s0.y, L2 = vx * vx + vy * vy;
                if (L2 > 1e-6f) hedef += (io.MouseDelta.x * vx + io.MouseDelta.y * vy) / L2;
              }
            };
            // Şahit uç tutamağı: sürükleyerek o ucu bağımsız uzat/kısalt.
            // mod 2=ölçü ucu A, 3=ölçü ucu B, 4=geometri ucu A, 5=geometri ucu B.
            const auto ucTutamak = [&](ImVec2 uc, const Vec3f& taban3, int mod, f32& uzatma) {
              const bool uzer = vpHover && ImGui::IsMouseHoveringRect(
                                              ImVec2(uc.x - 6, uc.y - 6), ImVec2(uc.x + 6, uc.y + 6));
              ciz->AddCircleFilled(uc, 3.5f, uzer ? IM_COL32(120, 220, 255, 255) : soluk);
              if (uzer && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
                olcumSurukle = sid; olcumSurukleMod = mod;
              }
              if (olcumSurukle == sid && olcumSurukleMod == mod) {
                fare.kameraKullandi = true;
                if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) eksenSurukle(taban3, uzatma);
                else olcumSurukle = 0;
              }
            };
            ucTutamak(oa, oc.a, 2, oc.uzatma1);
            ucTutamak(ob, oc.b, 3, oc.uzatma2);
            ucTutamak(ga, oc.a, 4, oc.taban1);
            ucTutamak(gb, oc.b, 5, oc.taban2);
            const Vec3f orta3 = (oa3 + ob3) * 0.5f;
            ImVec2 om;
            if (ekrana(orta3, om)) {
              om.y -= 12;
              etiketCiz(om, oc.metin.c_str());
              // Etiket: taşı (sol=ofset) / döndür (sağ). Canlı önizleme de dahil (kimlik=CANLI).
              if (ustunde) {
                if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
                  olcumSurukle = sid; olcumSurukleMod = 0;
                } else if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
                  olcumSurukle = sid; olcumSurukleMod = 1;
                }
              }
              if (olcumSurukle == sid && (olcumSurukleMod == 0 || olcumSurukleMod == 1)) {
                fare.kameraKullandi = true;
                const bool aktif = olcumSurukleMod == 0 ? ImGui::IsMouseDown(ImGuiMouseButton_Left)
                                                        : ImGui::IsMouseDown(ImGuiMouseButton_Right);
                if (aktif && olcumSurukleMod == 0) eksenSurukle(orta3, oc.ofset);  // ofset kaydır
                else if (aktif) oc.aci += io.MouseDelta.x * 0.01f;                 // döndür
                else olcumSurukle = 0;
              }
            }
          } else if (oc.tur == OlcumTur::Aci) {
            ImVec2 pc, pa, pb;
            if (!ekrana(oc.c, pc) || !ekrana(oc.a, pa) || !ekrana(oc.b, pb)) return;
            ciz->AddLine(pc, pa, soluk, 1.0f);
            ciz->AddLine(pc, pb, soluk, 1.0f);
            ImVec2 onceki; bool oncekiVar = false;
            for (const Vec3f& y : oc.yay) {
              ImVec2 s;
              if (!ekrana(y, s)) { oncekiVar = false; continue; }
              if (oncekiVar) ciz->AddLine(onceki, s, ana, 1.8f);
              onceki = s; oncekiVar = true;
            }
            const Vec3f eKon = oc.yay.empty() ? (oc.a + oc.b) * 0.5f : oc.yay[oc.yay.size() / 2];
            ImVec2 em;
            if (ekrana(eKon, em)) { em.y -= 12; etiketCiz(em, oc.metin.c_str()); }
          } else if (oc.tur == OlcumTur::Yaricap) {
            ImVec2 pa, pb, pc;
            if (!ekrana(oc.a, pa) || !ekrana(oc.b, pb) || !ekrana(oc.c, pc)) return;
            ciz->AddLine(pa, pb, ana, 1.8f);
            artiCiz(pc);
            okCiz(pb, pa);
            ImVec2 em((pa.x + pb.x) * 0.5f, (pa.y + pb.y) * 0.5f - 12);
            etiketCiz(em, oc.metin.c_str());
          } else {  // Alan, Koordinat
            ImVec2 pa;
            if (!ekrana(oc.a, pa)) return;
            artiCiz(pa);
            ImVec2 em(pa.x + 16, pa.y - 16);
            ciz->AddLine(pa, ImVec2(em.x - 8, em.y + 8), soluk, 1.0f);
            etiketCiz(em, oc.metin.c_str());
          }
        };

        for (Olcum& oc : olcum.hepsi()) cizOlcum(oc, false);
        if (olcum.acik && olcum.canliVar) cizOlcum(olcum.canli, true);
        // Seçili referansları vurgula (canlı ölçüm henüz tamamlanmadıysa 1. seçim görünsün)
        if (olcum.acik) {
          const auto refIsaret = [&](const OlcumRef& r) {
            if (!r.var) return;
            ImVec2 s;
            if (ekrana(r.merkez, s))
              ciz->AddCircleFilled(s, 5.0f, IM_COL32(120, 220, 255, 235));
          };
          refIsaret(olcum.ref1);
          refIsaret(olcum.ref2);

          // "Nokta Koy" modu: fare altında geometriye izdüşen canlı nokta — tıklanınca
          // ölçülür. Fare gezdikçe imleci yüzeye projekte eder (canlı geri bildirim).
          if (vpHover && olcum.kip != OlcumKip::Ayarlar &&
              olcum.aktifFiltre() == SecimFiltre::SerbestNokta) {
            const f32 mlx = io.MousePos.x - vpRectMinX, mly = io.MousePos.y - vpRectMinY;
            Sahne::IsinVurus vr;
            ImVec2 s;
            if (imlecVurus(mlx, mly, vr) && vr.govde && ekrana(vr.nokta, s)) {
              ciz->AddCircleFilled(s, 3.5f, IM_COL32(120, 220, 255, 210));
              ciz->AddCircle(s, 7.0f, IM_COL32(120, 220, 255, 255), 0, 1.5f);
              ciz->AddLine(ImVec2(s.x - 9, s.y), ImVec2(s.x + 9, s.y), IM_COL32(120, 220, 255, 160), 1.0f);
              ciz->AddLine(ImVec2(s.x, s.y - 9), ImVec2(s.x, s.y + 9), IM_COL32(120, 220, 255, 160), 1.0f);
            }
          }
        }
      }

      // ---- Kamera + seçim girdisi: CATIA/web durum makinesi (fikokontrolCad.ts) ----
      // Tuş bitmask'ı web ile aynı: 1=Sol, 2=Sağ, 4=Orta.
      //   Orta(4)       → PAN (harekete kadar; hareketsiz bırakış = merkezle+pivot)
      //   Orta+Sağ(6)   → DÖNDÜR
      //   Sol+Sağ(3)    → ZOOM
      //   DÖNDÜR iken bir tuş bırakılıp Orta basılı kalırsa(4) → ZOOM'a geç (CATIA jesti)
      //   A/S/D basılıyken sürükleme → Döndür/Zoom/Pan (web keyState eşi)
      const f32 lx = io.MousePos.x - vpRectMinX;
      const f32 ly = io.MousePos.y - vpRectMinY;
      fare.sonX = lx; fare.sonY = ly;  // pick imleci
      const f32 mdx = io.MouseDelta.x, mdy = io.MouseDelta.y;
      const bool hareket = (mdx != 0.0f || mdy != 0.0f);
      const int btn = (ImGui::IsMouseDown(ImGuiMouseButton_Left) ? 1 : 0) |
                      (ImGui::IsMouseDown(ImGuiMouseButton_Right) ? 2 : 0) |
                      (ImGui::IsMouseDown(ImGuiMouseButton_Middle) ? 4 : 0);
      const bool basildi = ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
                           ImGui::IsMouseClicked(ImGuiMouseButton_Right) ||
                           ImGui::IsMouseClicked(ImGuiMouseButton_Middle);
      const bool birakildi = ImGui::IsMouseReleased(ImGuiMouseButton_Left) ||
                             ImGui::IsMouseReleased(ImGuiMouseButton_Right) ||
                             ImGui::IsMouseReleased(ImGuiMouseButton_Middle);
      // btn kombinasyonundan kip eşle (web _buttonsToState)
      const auto kipEsle = [](int b) {
        return b == 6 ? 0 : b == 3 ? 1 : b == 4 ? 2 : -1;  // 6=döndür,3=zoom,4=pan
      };

      // Alan seçimi (trap) yalnız SERBEST seçim bağlamında: özel modlar aktifken
      // devre dışı (sketcher/yönSeç/refSecim/CAM-seçim/ölçüm tek-tık akışlarıdır).
      g_sonAdim = "kareCiz: kamera/secim girdisi";  // faz izleyici
      // CATIA: Sketcher'da da çerçeve seçimi VAR — ama yalnız SEÇİM modunda (çizim aracı
      // yokken) ve segment-bazlı sadece eskiz öğelerini seçer (aşağıda sketchKisitAyarla).
      const bool sketcherAktif =
          sketchNesne() && cekirdek.workbenchler().aktif() == "sketcher";
      const bool sketcherSecimModu = sketcherAktif && sketchArac.empty();
      const bool trapUygun =
          (!sketcherAktif || sketcherSecimModu) &&
          hizliYonSecim == 0 && !refSecim.aktif() && !camSis.secim().aktif() &&
          !(olcum.acik && olcum.kip != OlcumKip::Ayarlar);

      // Datum (ekran-sabit ölçekli) düğümlerin trap testi görsel boyutla eşleşsin
      // diye kamera mesafesi her kare alan seçime iletilir (render/pick ile aynı kaynak).
      alan.kameraMesafeAyarla(kamera.mesafe());
      // Sketcher seçim modu → çerçeve YALNIZ aktif sketch'e (segment-bazlı); değilse 3D.
      alan.sketchKisitAyarla(sketcherSecimModu ? static_cast<u32>(aktifSketch) : 0u);

      // CATIA çerçeve seçimi köprüsü: sketcher seçim modunda alan seçimi eskiz
      // geometrilerini sketchSecili'ye uygular (3D sahne seçimi kirlenmez); değilse 3D.
      const auto alanBitirUygula = [&](const Mat4f& vp, const Vec2f& vpBoyut,
                                       TrapModifier m) -> u32 {
        if (alan.sketchKisit() != 0) {
          const std::vector<u32> geolar = alan.bitirSketch(vp, {0.0f, 0.0f}, vpBoyut);
          if (m == TrapModifier::Degistir) sketchSecili.clear();
          for (u32 g : geolar) {
            const auto it = std::find(sketchSecili.begin(), sketchSecili.end(), g);
            if (m == TrapModifier::Cikar) {
              if (it != sketchSecili.end()) sketchSecili.erase(it);
            } else if (it == sketchSecili.end()) {
              sketchSecili.push_back(g);  // Ekle/Degistir: biriktir (tekil)
            }
          }
          cekirdek.olaylar().yayinla(
              SketchSecimDegisti{aktifSketch, static_cast<u32>(sketchSecili.size())}, "Sketch");
          return (u32)geolar.size();
        }
        return alan.bitir(vp, {0.0f, 0.0f}, vpBoyut, m);
      };

      // ---- Girdi jest sistemi (A): ham girdiyi besle (GÖZLEMCİ — tüketmez) ----
      // guiUstunde: menü/panel/pusula/trap üstünde sahne-jestleri bastırılır. Böylece
      // mevcut CATIA kamera/seçim davranışı korunur; jest sistemi yalnız ek menü tetikler.
      {
        girdi::FareKare fk;
        fk.sol = ImGui::IsMouseDown(ImGuiMouseButton_Left);
        fk.orta = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
        fk.sag = ImGui::IsMouseDown(ImGuiMouseButton_Right);
        fk.konum = {lx, ly};
        fk.delta = {mdx, mdy};
        fk.tekerlek = io.MouseWheel;
        fk.dt = io.DeltaTime;
        fk.guiUstunde =
            io.WantCaptureMouse || !vpHover || pusulaUstunde || alan.aktifMi();
        fk.hover = hoverProbu.simdiki();  // bağlam enjeksiyonu (bu karenin hover'ı)
        // On-demand: 3B sahnede bir şey hover'lıyken (nokta/kenar/yüz vurgusu) sürekli
        // kare iste → seyrek karede highlight titremez (heartbeat re-pick flicker'ı biter).
        if (hoverProbu.simdiki().govdeId || sahne.vurgu.govde) kareIste(2);
        jestSis.guncelle(fk);
      }

      if (vpHover && !pusulaUstunde) {
        if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
          fare.sol = true; fare.solBasX = lx; fare.solBasY = ly;
          fare.solBasZaman = simdi; fare.uzunBasIslendi = false;
          // Trap başlat: poligon → her tık bir köşe (ilk tık modu açar). Dikdörtgen/
          // fırça basışta BAŞLAMAZ — 6px sürüklenince tembel başlar (aşağıda) ki
          // uzun-bas (Hızlı 3D) ve normal tık seçimiyle çakışmasın.
          if (trapUygun && alan.poligonKipMi()) {
            if (!alan.aktifMi()) alan.basla(lx, ly); else alan.poligonNokta(lx, ly);
          }
        }
        if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
          fare.sag = true; fare.sagBasX = lx; fare.sagBasY = ly; fare.sagBasZaman = simdi;
          if (alan.aktifMi()) alan.iptal();  // trap sürükleme/poligon sağ-tık iptal
        }
        if (ImGui::IsMouseClicked(ImGuiMouseButton_Middle)) {
          fare.orta = true; fare.ortaBasX = lx; fare.ortaBasY = ly; fare.ortaBasZaman = simdi;
        }
        if (basildi) { fare.kip = kipEsle(btn); fare.kameraKullandi = false; }
        if (io.MouseWheel != 0.0f && !secimYigini.acikMi()) {
          // Yığın açıkken tekerlek adaylar arası gezer (overlay bloğunda) → zoom yok.
          Vec3f h;  // imleç altındaki yüzeye zoom (raycast) → sınırsız yakınlaşma
          kamera.imlecYakinlastir(imlecYuzey(lx, ly, h) ? h : imlecDunya(lx, ly),
                                  io.MouseWheel * 1.7f);
        }
        if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
          // Poligon trap aktifse çift-tık = poligonu kapat + uygula (odaklama değil).
          if (alan.aktifMi() && alan.poligonKipMi() && alan.iz().size() >= 3) {
            const TrapModifier m = io.KeyShift  ? TrapModifier::Cikar
                                   : io.KeyCtrl ? TrapModifier::Ekle
                                                : TrapModifier::Degistir;
            const Mat4f vp = kamera.izdusum(sonEnBoy) * kamera.gorunum();
            const u32 adet = alanBitirUygula(vp, {avail.x, avail.y}, m);
            bilgiVer(std::to_string(adet) + " öğe seçildi (" + trapAdi(alan.tip) + ")");
          } else if (Dugum* d = sahne.bul(sahne.vurgu.govde)) {
            // Çift-tıklanan katı bir feature'ın sonuç (Ithal) gövdesi mi? →
            // düzenleme penceresi aç (Pad/Pocket/Shaft/Groove/Hole). Değilse
            // eski davranış: seç + odakla.
            u32 featKimlik = 0;
            for (u32 fk : partD.featureler())
              if (parca::Feature* f = partD.bul(fk);
                  f && f->sonucDugum() == sahne.vurgu.govde) {
                featKimlik = fk;
                break;
              }
            if (featKimlik) {
              featurePencereAc(featKimlik);
            } else {
              sahne.sec(sahne.vurgu, false);
              kamera.sigdir(d->dunyaMerkez(), d->dunyaYaricap() + 0.4f);
            }
          } else {
            tumunuSigdir();
          }
        }
      }

      // Bırakışta kip geçişi (web onMouseUp): Orta basılı kalırsa DÖNDÜR→ZOOM.
      if (birakildi) {
        if (btn == 4) fare.kip = (fare.kip == 0) ? 1 : 2;  // orta kaldı: döndür→zoom, yoksa pan
        else fare.kip = kipEsle(btn);                      // kalan tuşlara göre (btn=0 → -1)
      }

      // A/S/D klavye override (web keyState): herhangi bir tuş basılıyken geçerli.
      const int klavyeKip = ImGui::IsKeyDown(ImGuiKey_A)   ? 0
                            : ImGui::IsKeyDown(ImGuiKey_S) ? 1
                            : ImGui::IsKeyDown(ImGuiKey_D) ? 2
                                                           : -1;
      const int etkinKip = (klavyeKip >= 0 && btn != 0) ? klavyeKip : fare.kip;

      // Aktif kipe göre kamerayı sür (delta ImGui IO'dan).
      if (etkinKip == 0) { kamera.dondur(mdx, mdy); if (hareket) fare.kameraKullandi = true; }
      else if (etkinKip == 1) { kamera.yakinlastir(-mdy * 0.03f); if (hareket) fare.kameraKullandi = true; }
      else if (etkinKip == 2) { kamera.kaydir(mdx, mdy, io.KeyShift); if (hareket) fare.kameraKullandi = true; }

      // Trap TEMBEL başlatma: sol basılı + 6px kayma → dikdörtgen/fırça o zaman
      // başlar (çapa = basış noktası). Hareketsiz basılı tutma uzun-bas'a kalır;
      // uzun-bas tetiklendiyse veya kamera kullanıldıysa trap açılmaz.
      if (fare.sol && trapUygun && !alan.aktifMi() && !alan.poligonKipMi() &&
          !fare.uzunBasIslendi && !fare.kameraKullandi) {
        const f32 kayma = std::abs(lx - fare.solBasX) + std::abs(ly - fare.solBasY);
        if (kayma >= 6.0f) alan.basla(fare.solBasX, fare.solBasY);
      }
      // Trap sürükleme: dikdörtgen köşe / fırça izi noktası biriktir (poligon
      // sürüklemez, tıkla köşe ekler). Sol-drag kamerayı sürmez → çakışma yok.
      if (alan.aktifMi() && !alan.poligonKipMi() && hareket) alan.guncelle(lx, ly);

      // Uzun basış → "Hızlı 3D" ağırlık/kütük widget'ı (sol, hareketsiz, kamera yok)
      // Trap sürüklemesi aktifken long-press'i atla (dikdörtgen çizimiyle çakışmasın).
      if (fare.sol && !fare.uzunBasIslendi && !fare.kameraKullandi && !alan.aktifMi()) {
        const f32 kayma = std::abs(lx - fare.solBasX) + std::abs(ly - fare.solBasY);
        const auto sure = std::chrono::duration<f32>(simdi - fare.solBasZaman).count();
        if (sure > 0.55f && kayma < 6.0f && sahne.vurgu.govde) {
          fare.uzunBasIslendi = true;
          // Yalnız GERÇEK KATI/mesh'li gövdede ağırlık/kütük widget'i aç. Datum
          // (eksen/düzlem/EksenSistemi) + wireframe geometrisiz → açma (çökme fix).
          const Dugum* ubd = sahne.bul(sahne.vurgu.govde);
          if (ubd && ubd->indeksSayi > 0) {
            hizli3dKimlik = sahne.vurgu.govde; hizli3dAcik = true;
            hizliDurum.hizli3dAcilisX = io.MousePos.x; hizliDurum.hizli3dAcilisY = io.MousePos.y;
          }
        }
      }

      // Bırakma aksiyonları
      bool trapUygulandi = false;
      if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
        // Trap (dikdörtgen/iz) sürüklemesi: gerçek sürükleme ise adayları uygula,
        // mikro-sürükleme ise iptal edip normal tık seçimine düş. Poligon burada
        // bitmez (çift-tık/Enter kapatır).
        if (alan.aktifMi() && !alan.poligonKipMi()) {
          const f32 kayma = std::abs(lx - fare.solBasX) + std::abs(ly - fare.solBasY);
          if (kayma >= 6.0f) {
            const TrapModifier m = io.KeyShift  ? TrapModifier::Cikar
                                   : io.KeyCtrl ? TrapModifier::Ekle
                                                : TrapModifier::Degistir;
            const Mat4f vp = kamera.izdusum(sonEnBoy) * kamera.gorunum();
            const u32 adet = alanBitirUygula(vp, {avail.x, avail.y}, m);
            bilgiVer(std::to_string(adet) + " öğe seçildi (" + trapAdi(alan.tip) + ")");
            trapUygulandi = true;  // normal tık seçim zincirini atla
          } else {
            alan.iptal();  // mikro-sürükleme → normal tık
          }
        }
        if (!trapUygulandi && fare.sol && !fare.uzunBasIslendi && !fare.kameraKullandi) {
          const f32 kayma = std::abs(lx - fare.solBasX) + std::abs(ly - fare.solBasY);
          const auto sure = std::chrono::duration<f32>(simdi - fare.solBasZaman).count();
          // ---- Alt + Sol Tık: Selection Stack (CATIA "Other Selection") ----
          // İmleç ışınının kestiği üst üste tüm adayları topla + yığın penceresi aç.
          if (io.KeyAlt && kayma < 6.0f && sure < 0.45f) {
            const Mat4f vpM = kamera.izdusum(sonEnBoy) * kamera.gorunum();
            const f32 ndcX = 2.0f * lx / avail.x - 1.0f;
            const f32 ndcY = 2.0f * ly / avail.y - 1.0f;
            const Mat4f invVP = vpM.ters();
            const Vec4f yH = invVP * Vec4f{ndcX, ndcY, 0.0f, 1.0f};
            const Vec4f uH = invVP * Vec4f{ndcX, ndcY, 1.0f, 1.0f};
            const Vec3f yakin = yH.xyz() * (1.0f / yH.w);
            const Vec3f uzak = uH.xyz() * (1.0f / uH.w);
            secimYigini.ac(sahne, yakin, (uzak - yakin).birim(), vpM,
                           ImVec2(vpRectMinX, vpRectMinY), ImVec2(avail.x, avail.y), io.MousePos,
                           secimSis,
                           refSecim.aktif() && refSecim.beklenenTur() >= 1
                               ? refSecim.beklenenTur()
                               : -1);
            if (secimYigini.acikMi()) secimSis.hoverBastir(true);
            trapUygulandi = true;  // normal seçim zincirini atla
          } else if (kayma < 6.0f && sure < 0.45f) {
            SketchNesnesi* skTik = sketchNesne();
            // Sketcher tıklaması: araç aktifse uv noktası koy / geometri tamamla,
            // araç yoksa imleç-altı eskiz geometrisini seçime ekle/çıkar (kısıt için).
            if (skTik && cekirdek.workbenchler().aktif() == "sketcher") {
              Dugum* dz = sahne.bul(skTik->duzlem());
              eskiz::EskizVeri& ev = skTik->eskiz();
              f64 u, v;
              const bool duzenMod = sketchDuzenMi(sketchArac);
              if (duzenMod && dz && ekranUV(dz, lx, ly, u, v)) {
                // ---- 2B Düzenleme tık akışı (araç bazlı çoklu tık) ----
                if (sonSnap.tip != eskiz::SnapTip::Yok) { u = sonSnap.u; v = sonSnap.v; }
                if (sketchArac == "trim" || sketchArac == "uzat" || sketchArac == "kapat" ||
                    sketchArac == "hizlikirp" || sketchArac == "tumleyen") {
                  // Tek tık: imleç-altı geometri → işlem. Araç aktif kalır (continue).
                  const u32 g = sketchEnYakin(ev, u, v);
                  if (g) {
                    bool ok = sketchArac == "trim"      ? ev.trim(g, u, v)
                            : sketchArac == "uzat"      ? ev.uzat(g, u, v)
                            : sketchArac == "hizlikirp" ? ev.hizliKirp(g, u, v, eskiz::kirpModDon(sketchDurum.kirpMod))
                            : sketchArac == "tumleyen"  ? ev.tumleyen(g) != 0
                            : ev.kapat(g);
                    if (ok) { ev.coz(); bilgiVer("Uygulandı — devam edebilirsiniz"); }
                    else bilgiVer("İşlem uygulanamadı (kesişim/uygun uç yok)");
                  } else bilgiVer("İmleç altında geometri yok");
                } else if (sketchArac == "bol") {
                  const u32 g = sketchEnYakin(ev, u, v);
                  if (g) { if (ev.bol(g, u, v)) { ev.coz(); bilgiVer("Bölündü"); } else bilgiVer("Bölünemedi"); }
                  else bilgiVer("İmleç altında geometri yok");
                } else if (sketchArac == "fillet" || sketchArac == "pah") {
                  // CATIA tarzı: 2 çizgi seç → fareyi oynat (canlı önizleme) → TIKLA yerleştir.
                  // Değer kutusu doluysa onu, boşsa imleç konumundan türetilen yarıçap/mesafe.
                  if (sketchSecili.size() < 2) {
                    const u32 g = sketchEnYakin(ev, u, v);
                    if (g) {
                      auto it = std::find(sketchSecili.begin(), sketchSecili.end(), g);
                      if (it == sketchSecili.end()) sketchSecili.push_back(g);
                      if (sketchSecili.size() >= 2) {
                        sketchDuzenFaz = 1;
                        bilgiVer("Fareyi oynat → yarıçap/mesafe, sonra tıkla");
                      } else bilgiVer("1 çizgi — 2. çizgiyi seçin");
                    }
                  } else {
                    // Faz 1: tıklanan konum boyutu belirler → uygula (araç aktif kalır).
                    const eskiz::Geometri* gA = ev.geoBul(sketchSecili[0]);
                    const eskiz::Geometri* gB = ev.geoBul(sketchSecili[1]);
                    f64 val = sketchDuzGiris[0][0] ? std::strtod(sketchDuzGiris[0], nullptr) : 0.0;
                    if (val <= 1e-9 && gA && gB && gA->tip == eskiz::GeoTip::Cizgi &&
                        gB->tip == eskiz::GeoTip::Cizgi) {
                      const f64 A1x = ev.noktaX(gA->kimlik, 0), A1y = ev.noktaY(gA->kimlik, 0);
                      const f64 A2x = ev.noktaX(gA->kimlik, 1), A2y = ev.noktaY(gA->kimlik, 1);
                      const f64 B1x = ev.noktaX(gB->kimlik, 0), B1y = ev.noktaY(gB->kimlik, 0);
                      const f64 B2x = ev.noktaX(gB->kimlik, 1), B2y = ev.noktaY(gB->kimlik, 1);
                      const f64 rx = A2x - A1x, ry = A2y - A1y, sx = B2x - B1x, sy = B2y - B1y;
                      const f64 payda = rx * sy - ry * sx;
                      if (std::abs(payda) > 1e-9) {  // köşe = iki doğrunun kesişimi
                        const f64 tt = ((B1x - A1x) * sy - (B1y - A1y) * sx) / payda;
                        const f64 kx = A1x + rx * tt, ky = A1y + ry * tt;
                        const auto yon = [&](f64 p1x, f64 p1y, f64 p2x, f64 p2y, f64& ux, f64& uy) {
                          const f64 d1 = std::hypot(p1x - kx, p1y - ky);
                          const f64 d2 = std::hypot(p2x - kx, p2y - ky);
                          const f64 ux0 = (d1 > d2 ? p1x : p2x) - kx, uy0 = (d1 > d2 ? p1y : p2y) - ky;
                          const f64 L = std::hypot(ux0, uy0); ux = L > 1e-9 ? ux0 / L : 0; uy = L > 1e-9 ? uy0 / L : 0;
                        };
                        f64 uAx, uAy, uBx, uBy;
                        yon(A1x, A1y, A2x, A2y, uAx, uAy);
                        yon(B1x, B1y, B2x, B2y, uBx, uBy);
                        const f64 pu = u - kx, pv = v - ky;
                        val = std::max(std::abs(pu * uAx + pv * uAy), std::abs(pu * uBx + pv * uBy));
                      }
                    }
                    if (val < 1e-6) val = sketchDurum.sketchFilletYedekYari;  // yedek (AyarSistemi: sketcher.fillet_yedek_yari)
                    const auto trimMod = eskiz::trimModDon(sketchDurum.filletTrimMod);
                    u32 yeni = 0;
                    if (sketchArac == "fillet") {
                      yeni = ev.koseYuvarlat(sketchSecili[0], sketchSecili[1], val, trimMod);
                    } else {
                      // Pah: kutu(lar) doluysa ölçü moduna göre iki kenar setback'i;
                      // boşsa imleçten türetilen simetrik mesafe (val, val).
                      f64 mA = val, mB = val;
                      if (sketchDuzGiris[0][0]) {
                        const f64 v0 = std::strtod(sketchDuzGiris[0], nullptr);
                        const f64 v1 = sketchDuzGiris[1][0]
                                           ? std::strtod(sketchDuzGiris[1], nullptr)
                                           : (sketchDurum.pahOlcuMod == 1 ? v0 : 45.0);
                        pahMesafeHesapla(sketchDurum.pahOlcuMod, v0, v1, mA, mB);
                      }
                      yeni = ev.pah(sketchSecili[0], sketchSecili[1], mA, mB, trimMod);
                    }
                    ev.coz();
                    sketchSecili.clear(); sketchDuzenFaz = 0;
                    for (auto& s : sketchDuzGiris) s[0] = 0;
                    bilgiVer(yeni ? "Uygulandı — 2 çizgi daha seçebilirsiniz" : "Uygulanamadı (kesişim yok)");
                  }
                } else if (sketchArac == "aciortay" || sketchArac == "tegetcizgi" ||
                           sketchArac == "kesisimnokta" || sketchArac == "izdusumnokta") {
                  // Seç-seç-uygula (CATIA Bisecting/Bi-Tangent Line + Intersection/
                  // Projection Point): 2. öğe seçilince hemen uygulanır, araç aktif kalır.
                  if (sketchSecili.size() < 2) {
                    const u32 g = sketchEnYakin(ev, u, v);
                    if (g && std::find(sketchSecili.begin(), sketchSecili.end(), g) ==
                                 sketchSecili.end())
                      sketchSecili.push_back(g);
                  }
                  if (sketchSecili.size() >= 2) {
                    const eskiz::Geometri* gA = ev.geoBul(sketchSecili[0]);
                    const eskiz::Geometri* gB = ev.geoBul(sketchSecili[1]);
                    const auto cemberMi = [](const eskiz::Geometri* g) {
                      return g && (g->tip == eskiz::GeoTip::Daire ||
                                   g->tip == eskiz::GeoTip::Yay);
                    };
                    bool ok = false;
                    std::string mesaj;
                    if (sketchArac == "aciortay") {
                      // 2 çizgi → açıortay YAPI çizgisi (paralelde ortadan geçen doğru).
                      if (gA && gB && gA->tip == eskiz::GeoTip::Cizgi &&
                          gB->tip == eskiz::GeoTip::Cizgi) {
                        const f64 A1x = ev.noktaX(gA->kimlik, 0), A1y = ev.noktaY(gA->kimlik, 0);
                        const f64 A2x = ev.noktaX(gA->kimlik, 1), A2y = ev.noktaY(gA->kimlik, 1);
                        const f64 B1x = ev.noktaX(gB->kimlik, 0), B1y = ev.noktaY(gB->kimlik, 0);
                        const f64 B2x = ev.noktaX(gB->kimlik, 1), B2y = ev.noktaY(gB->kimlik, 1);
                        const f64 rx = A2x - A1x, ry = A2y - A1y, sx = B2x - B1x, sy = B2y - B1y;
                        const f64 payda = rx * sy - ry * sx;
                        f64 kx, ky, ux, uy;  // taban noktası + açıortay birim yönü
                        bool gecerli = true;
                        if (std::abs(payda) < 1e-9) {
                          // Paralel çizgiler: açıortay = iki çizgi ortasından geçen paralel.
                          kx = (A1x + A2x + B1x + B2x) * 0.25;
                          ky = (A1y + A2y + B1y + B2y) * 0.25;
                          const f64 L = std::hypot(rx, ry);
                          gecerli = L > 1e-9;
                          ux = gecerli ? rx / L : 0; uy = gecerli ? ry / L : 0;
                        } else {
                          const f64 tt = ((B1x - A1x) * sy - (B1y - A1y) * sx) / payda;
                          kx = A1x + rx * tt; ky = A1y + ry * tt;  // köşe (kesişim)
                          // Köşeden uzak uçlara birim yönler → toplamın birimi = açıortay
                          // (fillet köşe/yön deseni ile aynı).
                          const auto yon = [&](f64 p1x, f64 p1y, f64 p2x, f64 p2y,
                                               f64& vx, f64& vy) {
                            const f64 d1 = std::hypot(p1x - kx, p1y - ky);
                            const f64 d2 = std::hypot(p2x - kx, p2y - ky);
                            const f64 vx0 = (d1 > d2 ? p1x : p2x) - kx;
                            const f64 vy0 = (d1 > d2 ? p1y : p2y) - ky;
                            const f64 L = std::hypot(vx0, vy0);
                            vx = L > 1e-9 ? vx0 / L : 0; vy = L > 1e-9 ? vy0 / L : 0;
                          };
                          f64 uAx, uAy, uBx, uBy;
                          yon(A1x, A1y, A2x, A2y, uAx, uAy);
                          yon(B1x, B1y, B2x, B2y, uBx, uBy);
                          ux = uAx + uBx; uy = uAy + uBy;
                          const f64 L = std::hypot(ux, uy);
                          if (L > 1e-9) { ux /= L; uy /= L; }
                          else { ux = -uAy; uy = uAx; }  // zıt yönler (düz açı) → dik açıortay
                        }
                        if (gecerli) {
                          constexpr f64 SL = 1e4;  // sonsuz çizgi ile aynı pragmatik uzunluk
                          const u32 yeni = ev.cizgiEkle(kx - ux * SL, ky - uy * SL,
                                                        kx + ux * SL, ky + uy * SL);
                          ev.yapiAyarla(yeni, true);
                          ok = true; mesaj = "Açıortay eklendi (yapı)";
                        } else mesaj = "Çizgi yönü sıfır";
                      } else mesaj = "Açıortay: 2 çizgi seçin";
                    } else if (sketchArac == "tegetcizgi") {
                      // 2 daire/yay → ortak DIŞ teğet çizgi (tıka yakın aday) + Teğet kısıt.
                      if (cemberMi(gA) && cemberMi(gB)) {
                        const f64 C1x = ev.noktaX(gA->kimlik, 2), C1y = ev.noktaY(gA->kimlik, 2);
                        const f64 C2x = ev.noktaX(gB->kimlik, 2), C2y = ev.noktaY(gB->kimlik, 2);
                        const f64 r1 = ev.px(gA->ir), r2 = ev.px(gB->ir);
                        const f64 dx = C2x - C1x, dy = C2y - C1y, d = std::hypot(dx, dy);
                        if (d > 1e-9 && std::abs(r1 - r2) <= d) {
                          const f64 alfa = std::atan2(dy, dx);
                          const f64 beta = std::acos(std::clamp((r1 - r2) / d, -1.0, 1.0));
                          // İki dış teğet adayından son tık tarafına yakın olanı seç.
                          f64 enIyi = 1e30, P1x = 0, P1y = 0, P2x = 0, P2y = 0;
                          for (const f64 isaret : {1.0, -1.0}) {
                            const f64 aa = alfa + isaret * beta;
                            const f64 nx = std::cos(aa), ny = std::sin(aa);
                            const f64 q1x = C1x + r1 * nx, q1y = C1y + r1 * ny;
                            const f64 q2x = C2x + r2 * nx, q2y = C2y + r2 * ny;
                            const f64 md = std::hypot((q1x + q2x) * 0.5 - u,
                                                      (q1y + q2y) * 0.5 - v);
                            if (md < enIyi) { enIyi = md; P1x = q1x; P1y = q1y; P2x = q2x; P2y = q2y; }
                          }
                          const u32 yeni = ev.cizgiEkle(P1x, P1y, P2x, P2y);
                          // Parametrik: iki daireye Teğet kısıtı (slotEkle deseni) →
                          // daireler sürüklenince çizgi teğet kalır.
                          ev.kisitEkle(eskiz::KisitTip::Tegel, {yeni, gA->kimlik});
                          ev.kisitEkle(eskiz::KisitTip::Tegel, {yeni, gB->kimlik});
                          ok = true; mesaj = "Teğet çizgi eklendi";
                        } else mesaj = "Dış teğet yok (iç içe / eş merkez)";
                      } else mesaj = "Teğet çizgi: 2 daire/yay seçin";
                    } else if (sketchArac == "kesisimnokta") {
                      // Kesişim matematiği TEK OTORİTE = motor (EskizVeri::kesisimNoktalari).
                      std::vector<std::array<eskiz::Skalar, 2>> ks;
                      if (ev.kesisimNoktalari(sketchSecili[0], sketchSecili[1], ks)) {
                        for (const auto& p : ks) ev.noktaEkle(p[0], p[1]);
                        ok = true;
                        mesaj = std::to_string(ks.size()) + " kesişim noktası eklendi";
                      } else mesaj = "Kesişim yok";
                    } else {  // izdusumnokta: nokta + çizgi/daire → dik izdüşüm noktası
                      const eskiz::Geometri* gN =
                          gA && gA->tip == eskiz::GeoTip::Nokta   ? gA
                          : gB && gB->tip == eskiz::GeoTip::Nokta ? gB
                                                                  : nullptr;
                      const eskiz::Geometri* gH = gN == gA ? gB : gA;
                      if (gN && gH && (gH->tip == eskiz::GeoTip::Cizgi || cemberMi(gH))) {
                        const f64 Px = ev.noktaX(gN->kimlik, 0), Py = ev.noktaY(gN->kimlik, 0);
                        f64 qx = 0, qy = 0; bool var = false;
                        if (gH->tip == eskiz::GeoTip::Cizgi) {
                          // Sonsuz doğruya dik ayak (CATIA taşıyıcıya izdüşürür).
                          const f64 ax = ev.noktaX(gH->kimlik, 0), ay = ev.noktaY(gH->kimlik, 0);
                          const f64 bx = ev.noktaX(gH->kimlik, 1), by = ev.noktaY(gH->kimlik, 1);
                          const f64 dx = bx - ax, dy = by - ay, L2 = dx * dx + dy * dy;
                          if (L2 > 1e-12) {
                            const f64 t = ((Px - ax) * dx + (Py - ay) * dy) / L2;
                            qx = ax + t * dx; qy = ay + t * dy; var = true;
                          }
                        } else {  // daire/yay: merkezden noktaya ışın ∩ çember
                          const f64 cx = ev.noktaX(gH->kimlik, 2), cy = ev.noktaY(gH->kimlik, 2);
                          const f64 d = std::hypot(Px - cx, Py - cy);
                          if (d > 1e-9) {
                            const f64 r = ev.px(gH->ir);
                            qx = cx + (Px - cx) / d * r; qy = cy + (Py - cy) / d * r; var = true;
                          }
                        }
                        if (var) { ev.noktaEkle(qx, qy); ok = true; mesaj = "İzdüşüm noktası eklendi"; }
                        else mesaj = "İzdüşüm hesaplanamadı (çakışık nokta)";
                      } else mesaj = "İzdüşüm: 1 nokta + 1 çizgi/daire seçin";
                    }
                    if (ok) ev.coz();
                    sketchSecili.clear();
                    bilgiVer(mesaj);
                  } else if (sketchSecili.size() == 1) {
                    bilgiVer("1. öğe seçildi — 2. öğeyi seçin");
                  }
                } else if (sketchArac == "ayna" || sketchArac == "simetri") {
                  // Faz 0: geometri seç (çoklu). Faz 1: eksen çizgi seç → uygula.
                  const u32 g = sketchEnYakin(ev, u, v);
                  if (sketchDuzenFaz == 0) {
                    if (g) {
                      auto it = std::find(sketchSecili.begin(), sketchSecili.end(), g);
                      if (it != sketchSecili.end()) sketchSecili.erase(it);
                      else sketchSecili.push_back(g);
                      // İlk eleman seçilince otomatik faz 1'e geç (klavye beklemeden);
                      // ek eleman gerekiyorsa Ctrl+tık ile seçime dön.
                      if (!io.KeyCtrl) sketchDuzenIlerlet();
                      else bilgiVer(std::to_string(sketchSecili.size()) + " eleman — Ctrl'süz tıkla: eksen seç");
                    }
                  } else if (g) {  // eksen seçimi
                    const bool kopya = sketchDurum.sketchAynaKopya;
                    if (kopya) ev.aynala(sketchSecili, g);
                    else ev.simetriTasi(sketchSecili, g);
                    ev.coz();
                    sketchSecili.clear(); sketchDuzenFaz = 0; sketchArac.clear();
                    bilgiVer(kopya ? "Aynalandı (kopya)" : "Simetri taşındı");
                  }
                } else if (sketchArac == "otele" || sketchArac == "dondur" ||
                           sketchArac == "olcekle" || sketchArac == "ofset") {
                  // Faz 0: geometri seç. Faz 1: referans nokta al → değer penceresi.
                  if (sketchDuzenFaz == 0) {
                    const u32 g = sketchEnYakin(ev, u, v);
                    if (g) {
                      auto it = std::find(sketchSecili.begin(), sketchSecili.end(), g);
                      if (it != sketchSecili.end()) sketchSecili.erase(it);
                      else sketchSecili.push_back(g);
                      // Ctrl'süz ilk seçimde otomatik faz 1 (klavye beklemez); Ctrl = çoklu seç.
                      if (!io.KeyCtrl) sketchDuzenIlerlet();
                      else bilgiVer(std::to_string(sketchSecili.size()) + " eleman seçildi (Ctrl)");
                    }
                  } else {  // referans nokta tıklandı → değer girişi açılır
                    sketchDuzenRef = {u, v};
                    bilgiVer("Referans alındı — değer girin (Enter)");
                  }
                }
              } else if (!sketchArac.empty() && !duzenMod && dz && ekranUV(dz, lx, ly, u, v)) {
                // SmartPick: overlay'de bu kare çözülen snaplı noktayı kullan
                // (ham fare yerine uç/merkez/izgara vb. yakalanmış nokta).
                if (sonSnap.tip != eskiz::SnapTip::Yok) { u = sonSnap.u; v = sonSnap.v; }
                sketchNoktalar.push_back({u, v});
                if (sketchNoktalar.size() >= sketchAracTikSayi()) sketchAracTamamla(ev);
              } else if (sketchArac.empty() && dz && ekranUV(dz, lx, ly, u, v)) {
                // (a) Önce kısıt sembolü hit-test (overlay ekran-uzayı kutuları doldurdu).
                //     Simgeye tıklanırsa geometri seçimine düşülmez → sil'e hazır seçim.
                u32 kisitVur = 0;
                for (const auto& hk : sketchDurum.kisitHitKutulari)
                  if (io.MousePos.x >= hk.x0 && io.MousePos.x <= hk.x1 &&
                      io.MousePos.y >= hk.y0 && io.MousePos.y <= hk.y1) { kisitVur = hk.kimlik; break; }
                if (kisitVur) {
                  auto it = std::find(sketchSeciliKisit.begin(), sketchSeciliKisit.end(), kisitVur);
                  if (it != sketchSeciliKisit.end()) sketchSeciliKisit.erase(it);
                  else {
                    if (!io.KeyCtrl) { sketchSeciliKisit.clear(); sketchSecili.clear(); }
                    sketchSeciliKisit.push_back(kisitVur);
                  }
                } else {
                  // (b) En yakın geometri (uv-uzayı mesafesi) → seçim toggle.
                  const u32 enYakin = sketchEnYakin(ev, u, v);
                  if (enYakin) {
                    if (!io.KeyCtrl) sketchSeciliKisit.clear();  // geometri seçimi kısıt seçimini bırakır
                    auto it = std::find(sketchSecili.begin(), sketchSecili.end(), enYakin);
                    if (it != sketchSecili.end()) sketchSecili.erase(it);
                    else {
                      if (!io.KeyCtrl) sketchSecili.clear();
                      sketchSecili.push_back(enYakin);
                    }
                    sketchSecimYay();  // K5: seçim değişti → olay
                  } else if (!io.KeyCtrl) {
                    sketchSeciliKisit.clear();
                    if (!sketchSecili.empty()) { sketchSecili.clear(); sketchSecimYay(); }
                  }
                }
              }
            } else
            // Kütük "Yön Seç" modu: tıklanan yüzün normali kütük eksenine olur (çift yön mümkün)
            if (hizliYonSecim > 0) {
              Sahne::IsinVurus vr;
              if (imlecVurus(lx, ly, vr) && vr.govde && vr.normal.uzunlukKare() > 1e-8f) {
                const Vec3f n = vr.normal.birim();
                if (hizliYonSecim == 1) { hizliYon1 = n; hizliYon1Var = true; hizliKutukMod = 2; }
                else { hizliYon2 = n; hizliYon2Var = true; }
                hizliYonSecim = 0;
                bilgiVer("Kütük yönü seçildi");
              }
            } else
            // GSD wireframe / Hole "sahneden nokta/yüz seç" modu aktifse: tık →
            // dünya noktası (+yüz normali +gövde) yakalanır, feature param'a yazılır.
            if (refSecim.aktif()) {
              SahneRefSecim::Vurus vr{};
              if (refSecim.serbestNoktaMi()) {
                // Serbest yüzey noktası: ışın vuruşu (nokta + normal + gövde).
                Sahne::IsinVurus iv;
                if (imlecVurus(lx, ly, iv) && iv.govde) {
                  vr.nokta = {iv.nokta.x, iv.nokta.y, iv.nokta.z};
                  if (iv.normal.uzunlukKare() > 1e-8f) {
                    const Vec3f n = iv.normal.birim();
                    vr.normal = {n.x, n.y, n.z};
                    vr.normalVar = true;
                    vr.kaynak = SahneRefSecim::Kaynak::Yuz;
                  }
                  vr.govde = iv.govde;
                  vr.alt = sahne.vurgu.alt;
                  refSecim.tik(vr);
                  // K5: SahneRefSecim pick olayı (makro/plugin/denetçi erişimi).
                  cekirdek.olaylar().yayinla(
                      RefSecimYapildi{refSecim.sahip(), vr.govde, vr.alt, (u32)vr.kaynak},
                      "RefSecim");
                }
              } else if (sahne.vurgu.govde) {
                // Topolojik köşe/kenar/yüz: vurgulanan gövde+ordinal. Nokta konumu ışın-YÜZ
                // kesişiminden (mesh'li gövde); sketch gibi MESH'SİZ gövdede imlecVurus
                // başarısız olsa da govde/alt topolojik referans yeterli → tık yine kaydolur.
                vr.govde = sahne.vurgu.govde;
                vr.alt = sahne.vurgu.alt;
                Sahne::IsinVurus iv;
                if (imlecVurus(lx, ly, iv) && iv.govde)
                  vr.nokta = {iv.nokta.x, iv.nokta.y, iv.nokta.z};  // opsiyonel dünya noktası
                refSecim.tik(vr);
                cekirdek.olaylar().yayinla(
                    RefSecimYapildi{refSecim.sahip(), vr.govde, vr.alt, (u32)vr.kaynak},
                    "RefSecim");
              }
            } else
            // Analiz sahneden-seçim (çekim yönü / hedef / kesit düzlem normali).
            if (analizSis.secimAktif()) {
              analizSis.tik(sahne.vurgu.govde, sahne.vurgu.alt, secimSis.vurguTur(),
                            [&](Vec3f& nokta, Vec3f& normal) -> bool {
                              Sahne::IsinVurus vr;
                              if (imlecVurus(lx, ly, vr) && vr.govde) {
                                nokta = vr.nokta;
                                normal = vr.normal;
                                return true;
                              }
                              return false;
                            });
            } else
            // CAM sahneden-seçim modu aktifse tıklama op geometrisine beslenir
            // (yüz/kenar konturu, delik noktası, düzlem Z, takım ekseni, stok gövde).
            if (camSis.secim().aktif()) {
              const bool ciftTik = ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left);
              camSis.secim().tik(
                  sahne.vurgu, secimSis.vurguTur(),
                  [&](Vec3f& nokta, Vec3f& normal) -> bool {
                    Sahne::IsinVurus vr;
                    if (imlecVurus(lx, ly, vr) && vr.govde) {
                      nokta = vr.nokta;
                      normal = vr.normal;
                      return true;
                    }
                    return false;
                  },
                  ciftTik);
            } else
            // Ölçüm penceresi açıksa tıklama seçim yerine ölçüme beslenir (CATIA akışı)
            if (olcum.acik && olcum.kip != OlcumKip::Ayarlar) {
              // "Nokta Koy" modu: fare altındaki yüzeye izdüşen SERBEST noktayı al
              // (gövde-yerel/parametrik). Diğer filtreler topolojik seçime gider.
              Sahne::IsinVurus vr;
              if (olcum.aktifFiltre() == SecimFiltre::SerbestNokta) {
                if (imlecVurus(lx, ly, vr) && vr.govde) {
                  olcum.serbestNoktaAl(vr.govde, vr.nokta);
                  // K5: Ölçüm seçim ara-olayı (makro/plugin/denetçi erişimi).
                  cekirdek.olaylar().yayinla(OlcumSecimYapildi{vr.govde, 0, true}, "Ölçüm");
                }
              } else if (sahne.vurgu.govde) {
                olcum.secimAl(sahne.vurgu.govde, sahne.vurgu.alt, secimSis.vurguTur());
                cekirdek.olaylar().yayinla(
                    OlcumSecimYapildi{sahne.vurgu.govde, sahne.vurgu.alt, false}, "Ölçüm");
              }
            } else {
              secimSis.tikla(io.KeyCtrl);
            }
          }
        }
        fare.sol = false;
        if (olcumSurukle && !ImGui::IsMouseDown(ImGuiMouseButton_Left) &&
            !ImGui::IsMouseDown(ImGuiMouseButton_Right))
          olcumSurukle = 0;
      }
      if (ImGui::IsMouseReleased(ImGuiMouseButton_Right)) {
        // Hareketsiz sağ tık → nesneye özel bağlam menüsü (vurgudaki gövde)
        // Orta tuş basılıyken (kamera pan/zoom) menü AÇILMASIN.
        if (fare.sag && !fare.kameraKullandi && !fare.orta &&
            !ImGui::IsMouseDown(ImGuiMouseButton_Middle)) {
          const f32 kayma = std::abs(lx - fare.sagBasX) + std::abs(ly - fare.sagBasY);
          const auto sure = std::chrono::duration<f32>(simdi - fare.sagBasZaman).count();
          if (kayma < 6.0f && sure < 0.5f) {
            const bool sketcherWb = cekirdek.workbenchler().aktif() == "sketcher";
            SketchNesnesi* skR = sketchNesne();
            // Sketcher'da araç aktifken sağ tık = aracı iptal (bağlam menüsü açma).
            if (sketcherWb && !sketchArac.empty()) {
              sketchArac.clear(); sketchNoktalar.clear();
              sketchDuzenFaz = 0; sketchSecili.clear();
            }
            // Sketcher'da araç yokken sağ tık = DURUMA GÖRE bağlam menüsü (kısıt/geometri/boş).
            else if (sketcherWb && skR && aktifSketch != VARLIK_YOK) {
              eskiz::EskizVeri& evR = skR->eskiz();
              u32 hitK = 0;  // kısıt sembolü hit-test (overlay ekran-uzayı kutuları)
              for (const auto& hk : sketchDurum.kisitHitKutulari)
                if (io.MousePos.x >= hk.x0 && io.MousePos.x <= hk.x1 &&
                    io.MousePos.y >= hk.y0 && io.MousePos.y <= hk.y1) { hitK = hk.kimlik; break; }
              u32 hitG = 0;
              if (!hitK) {
                Dugum* dzR = sahne.bul(skR->duzlem());
                f64 ur, vr;
                if (dzR && ekranUV(dzR, lx, ly, ur, vr)) hitG = sketchEnYakin(evR, ur, vr);
                // Hedef geometri seçili değilse seçime al → menü onun üzerinde çalışsın.
                if (hitG && std::find(sketchSecili.begin(), sketchSecili.end(), hitG) ==
                                sketchSecili.end()) {
                  if (!io.KeyCtrl) sketchSecili.clear();
                  sketchSecili.push_back(hitG);
                }
              }
              sketchDurum.baglamKisit = hitK;
              sketchDurum.baglamGeo = hitG;
              sketchDurum.baglamAc = true;  // sketchBaglamMenuCiz bu kare OpenPopup yapar
            }
            // Jest yolu bu tık için menü açmadıysa: sağ-tık bağlam menüsünü BURADAN aç
            // (işaret/radyal menü — legacy dikey popup yerine). sahne.vurgu güvenilir
            // çözülü olduğundan bu, jest hover zamanlamasından bağımsız GARANTİ tetikler.
            // Menü ZATEN açıksa da yeniden açar (başka yere sağ-tık → oraya taşı).
            else if (!baglamMenuAcildi) {
              girdi::FareJesti sj;
              sj.tur = girdi::JestTur::Tik;
              sj.dugme = girdi::Dugme::Sag;
              sj.hedef.govdeId = sahne.vurgu.govde;
              // DATUM (referans düzlem / Origin eksen-sistemi / eksen) → Govde menüsü (Eskiz
              // Aç). Kip ne olursa datum'a kenar/pah menüsü açılmaz (kip=filtre, tür değil).
              Dugum* vd2 = sahne.bul(sahne.vurgu.govde);
              const bool datumDugum2 = vd2 && (vd2->tip == DugumTip::Duzlem ||
                                               vd2->tip == DugumTip::Eksen ||
                                               vd2->tip == DugumTip::EksenSistemi ||
                                               vd2->tip == DugumTip::Nokta ||
                                               vd2->tip == DugumTip::Cizgi);
              if (!sahne.vurgu.govde) {
                sj.hedef.tur = girdi::HedefTur::Hic;
              } else if (datumDugum2) {
                sj.hedef.tur = girdi::HedefTur::Govde;
              } else {
                switch (sahne.kip()) {
                  case SecimKip::Yuz:   sj.hedef.tur = girdi::HedefTur::Yuz;   break;
                  case SecimKip::Kenar: sj.hedef.tur = girdi::HedefTur::Kenar; break;
                  case SecimKip::Nokta: sj.hedef.tur = girdi::HedefTur::Nokta; break;
                  default:              sj.hedef.tur = girdi::HedefTur::Govde; break;
                }
              }
              // Dalga 3: motor kapsıyorsa halka motordan; değilse BaglamPolitika (regresyon yok).
              girdi::PolitikaKarar k;
              const auto& mk = belge.baglamMotoru.sonKarar();
              if (!datumDugum2 && baglamMotorKapsar(mk.hedef.kategori) && !mk.ogeler.empty()) {
                sonMotorOneri_ = mk.ogeler;
                baglamPolitikaYap(sonMotorOneri_, k);
              } else {
                k = baglamPol.karar(sj, cekirdek.workbenchler().aktif());  // datum → Eskiz Aç
              }
              if (k.gecerli()) {
                // Eksen DOĞRUSU (Eksen düğümü veya EksenSistemi eksen-ordinali 1/2/3) düzlem
                // DEĞİL → "Eskiz Aç" öğesini kaldır (eksende sketch olmaz). Yalnız referans
                // düzlem / eksen-sistemi düzlem-ordinali (4/5/6) / düz yüzde kalır.
                if (datumDugum2) {
                  bool duzlemUygun = false;
                  if (vd2->tip == DugumTip::Duzlem) duzlemUygun = true;
                  else if (vd2->tip == DugumTip::EksenSistemi)
                    duzlemUygun = eksensis::duzlemMi(sahne.vurgu.alt);
                  if (!duzlemUygun)
                    k.ogeler.erase(
                        std::remove_if(k.ogeler.begin(), k.ogeler.end(),
                                       [](const girdi::MenuOgeSecim& o) {
                                         return o.komutKimlik == "sketch_yuz";
                                       }),
                        k.ogeler.end());
                }
                // Açık menü varsa yeni konumda yeniden aç: eski halka durumunu sıfırla.
                if (sonPolitika.gecerli()) ui::aracHalkasiSifirla("baglam");
                sonPolitika = std::move(k);
                sonPolitikaKonum = ImGui::GetMousePos();
                sonPolitikaHedef = sahne.vurgu.govde;
                sonPolitikaHedefAlt = sahne.vurgu.alt;  // datum düzlem 4/5/6 / yüz (Eskiz Aç)
                // aracHalkasi (radyal, nested) çizim bloğunda açılır.
              }
            }
          }
        }
        fare.sag = false;
      }
      baglamMenuAcildi = false;  // her karede sıfırla (jest yeniden set eder)
      if (ImGui::IsMouseReleased(ImGuiMouseButton_Middle)) {
        // Hareketsiz orta tık = merkezle + döndürme pivotunu oraya taşı (imleç yüzeyi).
        if (fare.orta && !fare.kameraKullandi) {
          const f32 kayma = std::abs(lx - fare.ortaBasX) + std::abs(ly - fare.ortaBasY);
          const auto sure = std::chrono::duration<f32>(simdi - fare.ortaBasZaman).count();
          if (kayma < 5.0f && sure < 0.35f) {
            Vec3f h;
            kamera.merkezle(imlecYuzey(lx, ly, h) ? h : imlecDunya(lx, ly));
          }
        }
        fare.orta = false;
      }
      // Tüm tuşlar bırakılınca kamera-kullanım bayrağını sıfırla (yeni sürükleme temiz).
      if (!fare.sol && !fare.orta && !fare.sag) fare.kameraKullandi = false;
    } else {
      vpHover = false;
    }
    ImGui::End();
    ImGui::PopStyleVar();

    // ---- Ek belge pencereleri (MDI Dalga 2): Image + jest + tık seçimi ----
    belgePencereleriCiz(belgeYon);
    wtecKafaPencereCiz(vulkan);  // WTEC açılı kafa paneli (üstte 3D + altta kontroller)

    // ---- Taşınabilir workbench araç çubukları (default: 3D Görünüm içinde) ----
    // Aktif workbench + global komutların grup'ları başına yüzen toolbar. Default
    // konum 3D Görünüm penceresi içinde (üst kenarda yatay dizili); sürüklenip
    // bırakılınca en yakın kenara dayanır/yapışır (snap). Yeni araç = MenuOge'ye
    // simge + workbench vermek (merkezi sistem). 3D Görünüm'den SONRA çizilir → üstte kalır.
    // GEÇİCİ: kullanıcı isteğiyle taşınabilir araç çubukları şimdilik gizli
    // (oturum-içi bayrak; kodu silme — false→true ile kolayca geri açılır).
    static bool g_wbToolbarGoster = false;
    if (g_wbToolbarGoster) {
      const std::string& aktifWb = cekirdek.workbenchler().aktif();
      const auto gorunur = [&](const MenuOge& o) {
        return !o.simge.empty() && (o.workbench.empty() || o.workbench == aktifWb);
      };
      std::vector<std::string> gruplar;
      for (const MenuOge& o : cekirdek.menuler().hepsi())
        if (gorunur(o) && std::find(gruplar.begin(), gruplar.end(), o.grup) == gruplar.end())
          gruplar.push_back(o.grup);

      // 3D Görünüm ekran dikdörtgeni (bu kare): sol-üst + boyut.
      const f32 vx0 = vpRectMinX, vy0 = vpRectMinY;
      const f32 vx1 = vx0 + (f32)istenenVpG, vy1 = vy0 + (f32)istenenVpY;
      const f32 bosluk = 8.0f, snapEsik = 22.0f;
      // 3D Görünüm konumlanana kadar (ilk kareler, dockspace kurulumu) toolbar çizme:
      // aksi halde FirstUseEver (0,0)'a kilitlenir ve ana pencerede kalır.
      const bool vpGecerli = istenenVpG >= 120 && istenenVpY >= 120 && vx0 >= 1.0f;

      // CATIA tarzı kompakt görünüm: küçük sıkışık ikon butonları, ince kenar boşluğu.
      ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4, 3));
      ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 2));
      ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3, 3));
      ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));  // düz (flat) — hover'da belirir
      const ImVec2 dugmeBoyut(26, 24);

      int idx = 0;
      for (const std::string& g : gruplar) {
        if (!vpGecerli) break;  // 3D Görünüm hazır değil → bu kare toolbar yok
        const std::string pencereKimlik = g + "###wbtb_" + g;
        ImGui::SetNextWindowPos({vx0 + bosluk + (f32)idx * 190.0f, vy0 + bosluk},
                                ImGuiCond_FirstUseEver);
        ustteTutSinif(true);  // ana pencereye owner → 3D Görünüm arkasına düşmez
        if (ImGui::Begin(pencereKimlik.c_str(), nullptr,
                         ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_AlwaysAutoResize |
                             ImGuiWindowFlags_NoFocusOnAppearing |
                             ImGuiWindowFlags_NoSavedSettings)) {
          // Sürükleme tutamacı (CATIA grip) — görsel; sürükleme başlık çubuğundan.
          ImGui::TextDisabled("⁝⁝");
          ImGui::SameLine(0.0f, 4.0f);
          int bid = 0;
          for (const MenuOge& o : cekirdek.menuler().hepsi()) {
            if (o.grup != g || !gorunur(o)) continue;
            if (o.etkinMi && !o.etkinMi(cekirdek)) continue;  // etkin değil → gizle
            if (bid) ImGui::SameLine(0.0f, 2.0f);
            ImGui::PushID(bid++);
            if (ImGui::Button(o.simge.c_str(), dugmeBoyut)) cekirdek.komutCalistir(o, "toolbar");
            if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", o.ad.c_str());
            ImGui::PopID();
          }
          // Kenara dayama/snap: sürüklenmiyorken 3D Görünüm kenarına yakınsa yapış.
          const bool suruklu =
              ImGui::IsWindowFocused() && ImGui::IsMouseDragging(ImGuiMouseButton_Left);
          if (!suruklu) {
            const ImVec2 pos = ImGui::GetWindowPos();
            const ImVec2 sz = ImGui::GetWindowSize();
            f32 nx = pos.x, ny = pos.y;
            if (std::abs(pos.x - (vx0 + bosluk)) < snapEsik) nx = vx0 + bosluk;
            else if (std::abs((pos.x + sz.x) - (vx1 - bosluk)) < snapEsik) nx = vx1 - bosluk - sz.x;
            if (std::abs(pos.y - (vy0 + bosluk)) < snapEsik) ny = vy0 + bosluk;
            else if (std::abs((pos.y + sz.y) - (vy1 - bosluk)) < snapEsik) ny = vy1 - bosluk - sz.y;
            // 3D Görünüm dışına tamamen kaçmasın (başlık hep erişilebilir kalsın).
            nx = std::clamp(nx, vx0 - sz.x + 46.0f, vx1 - 46.0f);
            ny = std::clamp(ny, vy0, vy1 - 24.0f);
            if (nx != pos.x || ny != pos.y) ImGui::SetWindowPos({nx, ny});
          }
        }
        ImGui::End();
        idx++;
      }
      ImGui::PopStyleColor();
      ImGui::PopStyleVar(3);
    }

    // ---- Ürün ağacı (urun_agaci_paneli.*'ye taşındı) ----
    // ---- Polimorfik panel çizimi (FikoPencereYonetici) — eski 6 tek tek çağrı yerine.
    //      Kayıt sırası (kur()): Ürün Ağacı → Işık → Özellikler → Olay → Makro → Eklenti.
    //      Paneller görünürlüğü kendi içinde denetler → cizTumu her kareyi sürer (BİREBİR).
    g_sonAdim = "kareCiz: erken paneller";  // faz izleyici
    panelYon_.cizTumu(bglm);
    ui::galeriCiz();

    // ---- Kontrol Merkezi (Ctrl+Space) — eski üst menünün yerine tek merkez ----
    // Bağlam her kare ucuz doldurulur: küçük tanımlayıcı vektörleri (bool* / std::function).
    // Panel toggle'ları, seçim filtreleri, trap, workbench ve compass hepsi buraya taşındı.
    if (ort.komutPalet.acikMi()) {
      KontrolMerkeziBaglam km;
      km.palet = &ort.komutPalet;
      km.cekirdek = &cekirdek;
      km.fpsMs = kareSuresiMs;
      km.hizliEylemler = {
          {"✕", "Sil", false, true, [&] { seciliSil(); }, "Seçili gövdeleri sil"},
          {"⛶", "Sığdır", false, true, [&] { tumunuSigdir(); }, "Tümünü ekrana sığdır"},
          {"▭", "Alt Şerit", g_serit.gorunurMu(), true,
           [&] { if (g_serit.gorunurMu()) g_serit.gizle(); else g_serit.goster(); },
           "Alt şerit çubuğunu aç/kapat"},
      };
      km.paneller = {
          {"🗂", "Ürün Ağacı", &panelAgac, nullptr},
          {"↳", "Ağaç 3B İçinde", &agacIcinde, "Ürün ağacını 3B görünüm içinde göster"},
          {"💡", "Işıklar", &panelIsik, nullptr},
          {"📋", "Özellikler", &panelOzellik, nullptr},
          {"📜", "Olay Denetçi", &panelOlay, nullptr},
          {"⚙", "İşlemler", &panelIsler, nullptr},
          {"✏", "Sketcher", &panelSketch, nullptr},
          {"📏", "Ölçüm", &olcum.acik, "Ölçüm penceresi (M)"},
          {"🏭", "Üretim Programı", &camSis.acik, nullptr},
          {"🐢", "Makro Konsolu", &panelMakro, nullptr},
          {"🧩", "Eklenti Yöneticisi", &panelEklenti, nullptr},
          {"✨", "FikoUi Galerisi", &ui::galeriAcik(), nullptr},
      };
      km.secimFiltreleri = {
          {"◧", "Yüz", &secimSis.fYuz, "1"},
          {"▤", "Kenar", &secimSis.fKenar, "2"},
          {"•", "Nokta", &secimSis.fNokta, "3"},
          {"◼", "Gövde", &secimSis.fGovde, "4"},
      };
      {
        struct { const char* em; TrapTip t; } tr[] = {
            {"▭", TrapTip::Dikdortgen},      {"▢", TrapTip::KesenDikdortgen},
            {"⬠", TrapTip::Poligon},         {"✎", TrapTip::FircaIzi},
            {"▣", TrapTip::DisRadikdortgen}, {"◪", TrapTip::DisKesen}};
        for (auto& d : tr)
          km.trapAraclari.push_back(
              {d.em, trapAdi(d.t), alan.tip == d.t, true, [&alan, tip = d.t] { alan.tip = tip; },
               "T ile döngü"});
      }
      for (const WorkbenchOge& wb : cekirdek.workbenchler().hepsi())
        km.workbenchler.push_back(
            {wb.simge.empty() ? "🧭" : wb.simge.c_str(), wb.ad,
             wb.kimlik == cekirdek.workbenchler().aktif(), true,
             [&cekirdek, kimlik = wb.kimlik] { cekirdek.workbenchler().aktifAyarla(cekirdek, kimlik); },
             nullptr});
      {
        const bool secimVar = !sahne.seciliGovdeler().empty();
        km.compassEylemleri = {
            {"🧲", "Seçilene Yapıştır", pusula.bagli, secimVar,
             [&] { const auto sg = sahne.seciliGovdeler();
                   if (!sg.empty()) { pusula.bagli = true; pusula.govde = sg.back(); pusula.tutamac = -1; } },
             "Compass'ı seçili gövdeye yapıştır (G)"},
            {"↩", "Bırak (köşeye)", false, pusula.bagli,
             [&] { pusula.bagli = false; pusula.tutamac = -1; }, "Compass'ı köşeye bırak (Esc)"},
        };
      }
      kontrolMerkeziCiz(km);
    }

    // ---- Dinamik araç çubuğu (B): görünür toolbar'ları çiz + sürükle/snap ----
    g_araclar.ciz();

    // ---- Dinamik durum-farkında alt şerit (registry→filtre→çiz, kopyasız) ----
    g_serit.ciz(cekirdek);

    // ---- Hover bilgi kartı (G): imleç-altı B-Rep bilgisini kart olarak göster ----
    // hoverProbu.simdiki() → sahne.bul(govde)->brep sorgusu (yuz/kenar/nokta/hacim)
    // → ui::HoverIcerik (static char[] tampon, kare-ömürlü) → hoverBilgiCiz.
    if (hoverBilgiAcik && vpHover) {
      const girdi::HoverBilgi& hb = hoverProbu.simdiki();
      static char b0[48], b1[48], b2[48], b3[48], bBaslik[64];
      // KASMA ÖNLEMİ: yuzBilgi/kenarBilgi = OCCT ordinal-tarama + GProp integrali
      // (yoğun üçgenli/B-spline yüzeyde ms mertebesi). Hedef (gövde+alt+tür+brep)
      // DEĞİŞMEDİKÇE kart içeriği önbellekten kullanılır — kare-başı sorgu YOK.
      static ui::HoverIcerik hic;               // önbellekli içerik (static tamponlara işaret eder)
      static u32 onGovde = ~0u, onAlt = ~0u;    // önbellek anahtarı
      static girdi::HedefTur onTur = girdi::HedefTur::Hic;
      static const void* onBrep = nullptr;
      static f64 hkToplamUs = 0; static u32 hkYenile = 0;  // [perf] örneklem
      Dugum* hd = hb.govdeId ? sahne.bul(hb.govdeId) : nullptr;
      const void* brepPtr = hd ? (const void*)hd->brep.get() : nullptr;
      const bool yenile = hb.govdeId != onGovde || hb.altId != onAlt ||
                          hb.tur != onTur || brepPtr != onBrep;
      if (yenile) {
      const auto hkT0 = std::chrono::steady_clock::now();
      hic.temizle();
      if (hd) {
        std::snprintf(bBaslik, sizeof(bBaslik), "%s",
                      hd->ad.empty() ? "Gövde" : hd->ad.c_str());
        hic.baslik = bBaslik;
        const auto* brep = hd->brep.get();
        if (hb.tur == girdi::HedefTur::Yuz && brep && hb.altId) {
          geometri::YuzBilgi yb;
          if (brep->yuzBilgi(hb.altId, yb)) {
            hic.tur = ui::HoverTur::Yuz;
            std::snprintf(b0, sizeof(b0), "%s",
                          yb.duzlemMi ? "Düzlem" : yb.silindirMi ? "Silindir"
                                        : yb.kureMi ? "Küre" : "Yüzey");
            hic.satirEkle("Tip", b0);
            std::snprintf(b1, sizeof(b1), "%.3f mm²", yb.alan);
            hic.satirEkle("Alan", b1);
            if (yb.silindirMi || yb.kureMi) {
              std::snprintf(b2, sizeof(b2), "%.3f mm", yb.yaricap);
              hic.satirEkle("Yarıçap", b2);
            }
          }
        } else if (hb.tur == girdi::HedefTur::Kenar && brep && hb.altId) {
          geometri::KenarBilgi kb2;
          if (brep->kenarBilgi(hb.altId, kb2)) {
            hic.tur = ui::HoverTur::Kenar;
            std::snprintf(b0, sizeof(b0), "%s",
                          kb2.cemberMi ? "Çember/Yay" : kb2.dogruMu ? "Doğru" : "Eğri");
            hic.satirEkle("Tip", b0);
            std::snprintf(b1, sizeof(b1), "%.3f mm", kb2.uzunluk);
            hic.satirEkle("Uzunluk", b1);
            if (kb2.cemberMi) {
              std::snprintf(b2, sizeof(b2), "%.3f mm", kb2.yaricap);
              hic.satirEkle("Yarıçap", b2);
            }
          }
        } else if (hb.tur == girdi::HedefTur::Nokta && brep && hb.altId) {
          Vec3d k;
          if (brep->noktaKonum(hb.altId, k)) {
            hic.tur = ui::HoverTur::Nokta;
            std::snprintf(b0, sizeof(b0), "%.3f", k.x); hic.satirEkle("X", b0);
            std::snprintf(b1, sizeof(b1), "%.3f", k.y); hic.satirEkle("Y", b1);
            std::snprintf(b2, sizeof(b2), "%.3f", k.z); hic.satirEkle("Z", b2);
          }
        } else if (hb.tur == girdi::HedefTur::Govde) {
          hic.tur = ui::HoverTur::Govde;
          std::snprintf(b1, sizeof(b1), "%.3f mm³", hd->hacim);
          hic.satirEkle("Hacim", b1);
          std::snprintf(b3, sizeof(b3), "%.3f mm²", hd->alan);
          hic.satirEkle("Alan", b3);
        }
      }
      onGovde = hb.govdeId; onAlt = hb.altId; onTur = hb.tur; onBrep = brepPtr;
      hkToplamUs += std::chrono::duration<f64, std::micro>(
                        std::chrono::steady_clock::now() - hkT0).count();
      ++hkYenile;
      }  // yenile
      if (kare % 600 == 0 && hkYenile > 0) {
        gunluk::bilgi("[perf] hover kart: {} yenileme, ort {:.0f} µs (hedef sabitken kare-başı brep sorgusu YOK)",
                      hkYenile, hkToplamUs / hkYenile);
        hkToplamUs = 0; hkYenile = 0;
      }
      if (hic.tur != ui::HoverTur::Yok)
        ui::hoverBilgiCiz("sahne_hover", ImGui::GetMousePos(), true, hic);
    }

    // ---- Perf HUD (Görünüm menüsü toggle): VAR OLAN [perf] sayılarını köşede göster ----
    // Salt okuma/gösterim overlay'i (serit_cubugu sagCiz FPS kalıbının genişletilmişi).
    // Değerler belge_ciz'in hesapladığı sayılardan; render/pipeline'a dokunmaz.
    if (perfGosterge().hudAcik) {
      const PerfGosterge& pg = perfGosterge();
      const ImGuiIO& pio = ImGui::GetIO();
      const ImGuiViewport* mvp = ImGui::GetMainViewport();
      ImGui::SetNextWindowPos({mvp->WorkPos.x + mvp->WorkSize.x - 12.0f,
                               mvp->WorkPos.y + 48.0f},
                              ImGuiCond_Always, {1.0f, 0.0f});  // sağ-üst köşe
      ImGui::SetNextWindowBgAlpha(0.70f);
      if (ImGui::Begin("##perfHud", nullptr,
                       ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
                       ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
                       ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove)) {
        ImGui::Text("%.0f FPS   %.2f ms", pio.Framerate,
                    pio.Framerate > 0.0f ? 1000.0f / pio.Framerate : 0.0f);
        ImGui::Separator();
        ImGui::Text("Draw-call : %u%s", pg.modelDrawCagri,
                    pg.megaIndeks > 0 ? "  (toplu)" : "");
        ImGui::Text("Model cook: %.1f ms", pg.modelCookMs);
        ImGui::Text("Kenar cook: %.1f ms", pg.kenarCookMs);
      }
      ImGui::End();
    }

    // ---- Bağlam ipucu HUD'u (Dalga 3): imleç-yanı "ne + ne yapabilirsin" ----
    // TEK motordan (belge.baglamMotoru). Hafif: alloc yok, salt drawlist. Boşta gizli
    // (kategori Bos → çizmez). Debounce: kategori 0.30 sn stabil olunca belirir. Menü
    // açıkken bastırılır (mükerrer bilgi). Opsiyonel toggle (Görünüm ▸ Bağlam İpucu).
    if (ort.hoverHudAcik && vpHover && !sonPolitika.gecerli()) {
      const auto& mk = belge.baglamMotoru.sonKarar();
      const baglam::HedefKategori kat = mk.hedef.kategori;
      static baglam::HedefKategori hudSonKat = baglam::HedefKategori::Bos;
      static double hudBasla = 0.0;
      const double simdiSn = ImGui::GetTime();
      if (kat != hudSonKat) { hudSonKat = kat; hudBasla = simdiSn; }
      if (kat != baglam::HedefKategori::Bos && (simdiSn - hudBasla) > 0.30) {
        const ui::Tema& t = ui::tema();
        ImDrawList* dl = ImGui::GetForegroundDrawList();
        ImFont* f = ImGui::GetFont();
        const float baslikB = 13.0f, satirB = 11.5f;
        char baslik[96];
        if (mk.hedef.olcu > 0.0)
          std::snprintf(baslik, sizeof(baslik), "%s  ·  %.2f", baglamKategoriAd(kat), mk.hedef.olcu);
        else
          std::snprintf(baslik, sizeof(baslik), "%s", baglamKategoriAd(kat));
        // En uygun 2-3 komut etiketi (yaprak + ara-öğe; pasifleri atla).
        char eylem[128];
        int ekli = 0;
        int uzun = std::snprintf(eylem, sizeof(eylem), "Sağ-tık: ");
        for (const auto& o : mk.ogeler) {
          if (!o.etkin || o.etiket.empty() || ekli >= 3) continue;
          const int yaz = std::snprintf(eylem + uzun, sizeof(eylem) - uzun, "%s%s",
                                        ekli > 0 ? " · " : "", o.etiket.c_str());
          if (yaz <= 0 || uzun + yaz >= (int)sizeof(eylem)) break;
          uzun += yaz; ++ekli;
        }
        const ImVec2 bB = f->CalcTextSizeA(baslikB, FLT_MAX, 0.0f, baslik);
        const ImVec2 eB = ekli > 0 ? f->CalcTextSizeA(satirB, FLT_MAX, 0.0f, eylem) : ImVec2{0, 0};
        const float padX = 9.0f, padY = 6.0f, satirAra = 3.0f;
        const float w = std::max(bB.x, eB.x) + padX * 2.0f;
        const float h = padY * 2.0f + bB.y + (ekli > 0 ? satirAra + eB.y : 0.0f);
        ImVec2 p = ImGui::GetMousePos(); p.x += 18.0f; p.y += 20.0f;
        const ImGuiViewport* mvp = ImGui::GetMainViewport();
        if (p.x + w > mvp->WorkPos.x + mvp->WorkSize.x) p.x = ImGui::GetMousePos().x - w - 12.0f;
        if (p.y + h > mvp->WorkPos.y + mvp->WorkSize.y) p.y = ImGui::GetMousePos().y - h - 12.0f;
        const ImVec2 mn = p, mx{p.x + w, p.y + h};
        dl->AddRectFilled(mn, mx, ui::u32(ui::karisim(t.panel, t.arka, 0.15f), 0.95f), t.yuvarlak);
        dl->AddRect(mn, mx, ui::u32(t.vurgu, 0.55f), t.yuvarlak, 0, 1.2f);
        dl->AddText(f, baslikB, {mn.x + padX, mn.y + padY},
                    ui::u32(ui::karisim(t.metin, t.vurgu, 0.20f)), baslik);
        if (ekli > 0)
          dl->AddText(f, satirB, {mn.x + padX, mn.y + padY + bB.y + satirAra},
                      ui::u32(t.metinSoluk), eylem);
      }
    }

    // ---- Bağlam politikası menüsü (A): jest kararını guarded çiz + çalıştır ----
    // sonPolitika.ogeler[i].komutKimlik → gerçek registry komutu (cekirdek.menuler).
    // Eşleşme: sabit {kimlik→(grup,ad)} haritası registry'de aranır; karşılığı yoksa
    // öğe pasif (etkin=false). Seçilince o->calistir(cekirdek) → menü kapanır.
    // CATIA: menü DrawList overlay; jest sistemi girdiyi tüketmez, kamera bozulmaz.
    if (sonPolitika.gecerli()) {
      // Kimlik → registry (grup, ad) eşlemesi (merkezi tek tanım).
      // İKİ format: (1) motor önerileri regKimlik("grup\x1fad") — doğrudan çözülür;
      // (2) BaglamPolitika kısa kimliği ("fillet"...) — harita ile çözülür (fallback).
      const auto komutBul = [&](const std::string& kimlik) -> const MenuOge* {
        std::string rgGrup, rgAd;
        if (baglam::regAyristir(kimlik, rgGrup, rgAd)) {  // motor kaynaklı registry kimliği
          for (const MenuOge& o : cekirdek.menuler().hepsi())
            if (o.grup == rgGrup && o.ad == rgAd) return &o;
          return nullptr;
        }
        struct Esle { const char* kimlik; const char* grup; const char* ad; };
        static const Esle harita[] = {
            {"fillet", "İşlem", "Kenar Yuvarlat (3B)"},
            {"chamfer", "İşlem", "Pah (3B)"},
            {"olc", "Ölçüm", "Ölçüm Penceresi (M)"},
            {"pocket", "Feature", "Pocket (Cep)"},
            {"sketch_yuz", "Sketch", "Sketch Oluştur"},
            {"sil", "Düzenle", "Seçileni Sil"},
            {"cizgi", "Çizim", "Çizgi"},
            {"cember", "Çizim", "Daire"},
            {"dikdortgen", "Çizim", "Dikdörtgen"},
            {"kutu_ekle", "Geometri", "Kutu"},
            {"silindir_ekle", "Geometri", "Silindir"},
            {"tumunu_sigdir", "Görünüm", "Tümünü Sığdır"},
            {"gorunum", "Görünüm", "İzometrik"},
        };
        const char* grup = nullptr; const char* ad = nullptr;
        for (const auto& e : harita)
          if (kimlik == e.kimlik) { grup = e.grup; ad = e.ad; break; }
        if (!grup) return nullptr;  // gizle/cam_kontur/kopya/duzenle/kisit → pasif
        for (const MenuOge& o : cekirdek.menuler().hepsi())
          if (o.grup == grup && o.ad == ad) return &o;
        return nullptr;
      };
      // Hedef gövdeye doğrudan uygulanan inline eylemler (registry'de komut yok).
      const auto inlineKimlikMi = [](const std::string& k) {
        return k == "sec" || k == "yakinlas" || k == "agirlik" || k == "gizle" ||
               k == "sil" || k == "kisit";
      };
      // Bir öğenin main-inline/registry ile çalıştırılabilir olup olmadığı.
      const auto ogeEtkin = [&](const girdi::MenuOgeSecim& s) {
        return s.etkin &&
               (inlineKimlikMi(s.komutKimlik) || komutBul(s.komutKimlik) != nullptr);
      };
      // Seçilen YAPRAK öğe → inline eylem VEYA eşlenmiş komut + kapat.
      const auto ogeCalistir = [&](const girdi::MenuOgeSecim& s) {
        const std::string k = s.komutKimlik;
        const u32 hedef = sonPolitikaHedef;
        Dugum* hd = hedef ? sahne.bul(hedef) : nullptr;
        if (k == "sec" && hd) { sahne.secimTemizle(); sahne.sec({hedef, 0}, false); }
        else if (k == "yakinlas" && hd) kamera.sigdir(hd->dunyaMerkez(), hd->dunyaYaricap() + 0.4f);
        else if (k == "agirlik" && hd && hd->indeksSayi > 0) {  // yalnız katı/mesh (datum çökme fix)
          hizli3dKimlik = hedef; hizli3dAcik = true;
          hizliDurum.hizli3dAcilisX = io.MousePos.x; hizliDurum.hizli3dAcilisY = io.MousePos.y;
        }
        else if (k == "gizle" && hd) gorunurDegistir(hedef, !hd->gorunur);
        else if (k == "sil" && hedef)
          cekirdek.uygula(std::make_unique<SilIslem>(sahne, std::vector<u32>{hedef}));
        else if (k == "kisit") panelSketch = true;  // Sketcher paneli (kısıt bölümü)
        else if (const MenuOge* o = komutBul(k)) cekirdek.komutCalistir(*o, "radyal");
        sonPolitika = girdi::PolitikaKarar{};  // menü kapat
      };

      // Tüm bağlamlar radyal (nested destekli) araç halkası ile çizilir. Ara-öğe
      // (cocuklar dolu) → alt-halka; yaprak → ogeCalistir. Çocuk AracOge dizileri
      // frame boyunca yaşamalı (raw pointer) → cocukBuf'ta tutulur.
      std::array<ui::AracOge, 16> oge{};
      std::array<std::array<ui::AracOge, 8>, 16> cocukBuf{};
      // Etkin olmayan öğeler radyal bağlam menüsünde GİZLENİR (soluk/pasif DEĞİL).
      // aracHalkasi GÖRÜNÜR sırayla indeks döndürür → orijinal politika indeksine
      // harita ile çeviririz (yaprak + alt-halka çocuğu ayrı ayrı).
      std::array<int, 16> ogeHarita{};
      std::array<std::array<int, 8>, 16> cocukHarita{};
      const int kaynak = (int)std::min<size_t>(sonPolitika.ogeler.size(), oge.size());
      int n = 0;
      for (int i = 0; i < kaynak; ++i) {
        const auto& s = sonPolitika.ogeler[i];
        if (!s.cocuklar.empty()) {  // ara-öğe: yalnız ETKİN çocuklarını göster
          const int cn = (int)std::min<size_t>(s.cocuklar.size(), cocukBuf[n].size());
          int cv = 0;
          for (int j = 0; j < cn; ++j) {
            const auto& cs = s.cocuklar[j];
            if (!ogeEtkin(cs)) continue;  // etkin olmayan çocuk gizle
            cocukBuf[n][cv].etiket = cs.etiket.c_str();
            cocukBuf[n][cv].simge = ui::Simge(cs.simge);
            cocukBuf[n][cv].etkin = true;
            cocukHarita[n][cv] = j;
            ++cv;
          }
          if (cv == 0) continue;  // hiç etkin çocuk yok → ara-öğeyi tümüyle gizle
          oge[n].etiket = s.etiket.c_str();
          oge[n].simge = ui::Simge(s.simge);
          oge[n].cocuklar = cocukBuf[n].data();
          oge[n].cocukSayi = cv;
          oge[n].etkin = true;
        } else {  // yaprak
          if (!ogeEtkin(s)) continue;  // etkin değil → gizle
          oge[n].etiket = s.etiket.c_str();
          oge[n].simge = ui::Simge(s.simge);
          oge[n].etkin = true;
        }
        ogeHarita[n] = i;
        ++n;
      }
      const int dizilim = sonPolitika.radyal ? (int)ui::AracDizilim::Radyal
                                             : (int)ui::AracDizilim::Yatay;
      int cocukIdx = -1;
      const int e = ui::aracHalkasi("baglam", sonPolitikaKonum, true, oge.data(), n,
                                    (ui::AracDizilim)dizilim, &cocukIdx);
      if (e == ui::ARAC_KAPAT) sonPolitika = girdi::PolitikaKarar{};
      else if (e >= 0 && e < n) {
        const auto& sel = sonPolitika.ogeler[ogeHarita[e]];  // görünür→orijinal
        if (cocukIdx >= 0 && cocukIdx < oge[e].cocukSayi)
          ogeCalistir(sel.cocuklar[cocukHarita[e][cocukIdx]]);  // alt-halka yaprağı (harita)
        else if (sel.cocuklar.empty())
          ogeCalistir(sel);  // üst-seviye yaprak
      }
    }

    // ---- Hızlı görünümler ----
    if (hizliGorunumAcik) { ImGui::OpenPopup("hizliGorunum"); hizliGorunumAcik = false; }
    if (ImGui::BeginPopup("hizliGorunum")) {
      struct { const char* ad; HizliGorunum g; } gorunumler[] = {
          {"Ön", HizliGorunum::On}, {"Arka", HizliGorunum::Arka}, {"Sol", HizliGorunum::Sol},
          {"Sağ", HizliGorunum::Sag}, {"Üst", HizliGorunum::Ust}, {"Alt", HizliGorunum::Alt},
          {"İzometrik", HizliGorunum::Izometrik}};
      for (auto& g : gorunumler)
        if (ImGui::MenuItem(g.ad)) kamera.gorunumeGit(g.g);
      ImGui::EndPopup();
    }

    // ---- Bağlam menüsü ----
    if (menuAc) { ImGui::OpenPopup("sahneMenu"); menuAc = false; }
    if (ImGui::BeginPopup("sahneMenu")) {
      Dugum* hedef = sahne.bul(menuKimlik);
      if (hedef) {
        ImGui::TextDisabled("%s", hedef->ad.c_str());
        ImGui::Separator();
        if (ImGui::MenuItem("Seç")) sahne.sec({menuKimlik, 0}, false);
        if (ImGui::MenuItem("Yakınlaş"))
          kamera.sigdir(hedef->dunyaMerkez(), hedef->dunyaYaricap() + 0.4f);
        if (hedef->indeksSayi > 0 && ImGui::MenuItem("⚖ Ağırlık / Kütük")) {  // yalnız katı/mesh (datum çökme fix)
          hizli3dKimlik = menuKimlik; hizli3dAcik = true;
          hizliDurum.hizli3dAcilisX = io.MousePos.x; hizliDurum.hizli3dAcilisY = io.MousePos.y;
        }
        if (ImGui::MenuItem(hedef->gorunur ? "Gizle" : "Göster")) gorunurDegistir(menuKimlik, !hedef->gorunur);
        if (ImGui::MenuItem("Sil"))
          cekirdek.uygula(std::make_unique<SilIslem>(sahne, std::vector<u32>{menuKimlik}));
      } else {
        if (ImGui::MenuItem("Tümünü Sığdır")) tumunuSigdir();
        if (ImGui::MenuItem("Seçimi Temizle")) sahne.secimTemizle();
      }
      ImGui::EndPopup();
    }

    // ---- "Hızlı 3D" ağırlık/kütük widget'ı → hizli3d_penceresi.cpp
    //      (hizli3dPencereCiz, davranış birebir). Panel açılışı (long-press +
    //      bağlam menüsü) main.cpp'de kalır; bu çağrı yalnız pencereyi çizer. ----
    hizli3dPanel_->ciz(bglm);

    // ---- Merkezi Ayarlar penceresi (kategorili; AyarSistemi'nden beslenir) ----
    ayarlarPanel_->ciz(bglm);

    // ---- İşlemler (arka plan görev ilerlemesi) ----
    // Taşınabilir + kapatılabilir yüzer pencere: başlık + ilerleme çubuğu + iptal.
    // Veri GorevSistemi'nden atomik okunur (event spam yok, her kare taze). Yeni iş
    // gelince otomatik geri gelir; X ile kapatılır, Pencereler menüsünden açılır.
    {
      const auto isler = cekirdek.gorevler().anlik();
      if (isler.size() > oncekiIsSayi) panelIsler = true;  // yeni iş → otomatik göster
      oncekiIsSayi = isler.size();
      if (panelIsler && !isler.empty()) {
        ImGui::SetNextWindowBgAlpha(0.92f);
        // Yeniden boyutlandırılabilir; makul başlangıç boyutu + min/max sınır (taşma yok).
        ImGui::SetNextWindowSize({320, 118}, ImGuiCond_FirstUseEver);
        ImGui::SetNextWindowPos({ekranG - 332, ekranY - 150}, ImGuiCond_FirstUseEver);
        ImGui::SetNextWindowSizeConstraints({220, 60}, {ekranG, ekranY});
        ustteTutSinif(islerUstte);
        if (ImGui::Begin("İşlemler", &panelIsler,
                         ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoFocusOnAppearing)) {
          ustteTutDugmesi("isler", islerUstte);
          for (const auto& g : isler) {
            ImGui::PushID((int)g.kimlik);
            const bool hatali = g.durum == GorevDurum::Hata;
            const ImVec4 renk = hatali ? ImVec4{1.0f, 0.45f, 0.4f, 1.0f}
                                       : ImVec4{0.35f, 0.85f, 0.9f, 1.0f};
            // Başlık kırpılır (uzunsa taşmaz), iptal düğmesi sağda sabit.
            const f32 btnG = 20.0f;
            ImGui::PushTextWrapPos(ImGui::GetContentRegionAvail().x - btnG);
            ImGui::TextColored(renk, "%s", g.baslik.c_str());
            ImGui::PopTextWrapPos();
            ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - btnG + 2);
            if (ImGui::SmallButton("✕")) cekirdek.gorevler().iptalEt(g.kimlik);
            char etk[80];
            std::snprintf(etk, sizeof(etk), "%.0f%%  %s", g.oran * 100.0f,
                          g.mesaj.empty() ? "" : g.mesaj.c_str());
            ImGui::ProgressBar(g.oran, ImVec2(-1, 14), etk);  // pencere genişliğine uyar
            ImGui::PopID();
          }
        }
        ImGui::End();
      }
    }

    // ---- Sketcher paneli → sketch_ui.cpp (sketcherPaneliCiz), davranış birebir ----
    sketcherPanel_->ciz(bglm);

    // ---- CATIA "Sketch Tools" SABİT toolbar → sketch_ui.cpp (sketchAraclariCiz) ----
    sketchAraclariPanel_->ciz(bglm);

    // ---- Sahnede sağ-tık bağlam menüsü (duruma göre) → sketch_ui.cpp ----
    sketchBaglamMenuCiz(bglm);

    // ---- CATIA "Sketch Analysis" penceresi (sketchAnalizAcik iken) → sketch_ui.cpp ----
    sketchAnalizPenceresiCiz(bglm);

    // ---- CATIA "Animate Constraint" penceresi (animAcik iken) → sketch_ui.cpp ----
    sketchAnimasyonPenceresiCiz(bglm);

    // ---- 3B Pah / Kenar Yuvarlatma değer penceresi (CATIA Chamfer/Fillet) ----
    if (pah3DAcik) {
      ImGui::SetNextWindowSize({260, 0}, ImGuiCond_FirstUseEver);
      ImGui::SetNextWindowPos({ekranG * 0.5f - 130, 96}, ImGuiCond_FirstUseEver);
      ImGui::SetNextWindowBgAlpha(0.96f);
      if (ImGui::Begin(pah3DYuvarlat ? "Kenar Yuvarlat (3B)" : "Pah (3B)", &pah3DAcik,
                       ImGuiWindowFlags_NoCollapse)) {
        // Seçili kenar sayısı (yalnız alt>0 = kenar ögeleri).
        int kenarSayi = 0;
        for (const auto& o : sahne.secililer()) if (o.alt > 0) ++kenarSayi;
        ImGui::TextDisabled("%d kenar seçili", kenarSayi);
        ImGui::SetNextItemWidth(120);
        const bool enter = ImGui::InputText(pah3DYuvarlat ? "Yarıçap" : "Mesafe",
                                            pah3DGiris, sizeof(pah3DGiris),
                                            ImGuiInputTextFlags_EnterReturnsTrue);
        ImGui::Spacing();
        const bool uygula = ImGui::Button("Uygula") || enter;
        ImGui::SameLine();
        if (ImGui::Button("Kapat")) pah3DAcik = false;
        if (uygula) {
          const f64 deger = std::strtod(pah3DGiris, nullptr);
          if (kenarSayi == 0) bilgiVer("Önce kenar seçin (Kenar seçim kipi)");
          else if (deger <= 1e-9) bilgiVer("Geçerli bir değer girin");
          else {
            const int n = sahne.pahYuvarlatUygula(deger, pah3DYuvarlat);
            if (n > 0) { bilgiVer(pah3DYuvarlat ? "Kenar yuvarlatıldı" : "Pah kırıldı"); pah3DAcik = false; }
            else bilgiVer("İşlem başarısız (kenar/değer geçersiz veya OCCT reddetti)");
          }
        }
      }
      ImGui::End();
    }

    // ---- Geç panel batch (polimorfik): CAM Programı → Op → ParçaOp → Takım →
    //      TakımEkseni → Feature → GSD → Analiz → Kesit → Döküm → CAM Sim.
    //      Kayıt sırası (kur()) = eski çağrı sırası birebir. Alt sistem ref'leri:
    //      cekirdek/sahne/camSis/partD/gsdSis/analizSis/refSecim/pencere UygulamaBaglam'dan;
    //      camUstte/dokumSis/vulkan ctor'da bağlı (UygulamaBaglam'da olmayanlar). ----
    g_sonAdim = "kareCiz: gec paneller";  // faz izleyici
    panelYonGec_.cizTumu(bglm);

    // ---- FikoYT Ölçüm penceresi (CATIA tarzı: Kumpas/Cetvel/Ayarlar) ----
    if (olcum.acik) {
      ImGui::SetNextWindowSize({296, 0}, ImGuiCond_FirstUseEver);
      ImGui::SetNextWindowPos({ekranG - 316, 92}, ImGuiCond_FirstUseEver);
      ImGui::SetNextWindowBgAlpha(0.96f);
      ustteTutSinif(olcumUstte);
      if (ImGui::Begin("FikoYT Ölçüm", &olcum.acik, ImGuiWindowFlags_NoDocking)) {
        ustteTutDugmesi("olcum", olcumUstte);
        // Mod sekmeleri (Kumpas tekli / Cetvel ikili / Ayarlar)
        const auto sekme = [&](const char* et, OlcumKip k, const char* ipucu) {
          const bool aktif = olcum.kip == k;
          if (aktif) ImGui::PushStyleColor(ImGuiCol_Button, {0.13f, 0.50f, 0.85f, 1.0f});
          if (ImGui::Button(et, {88, 0})) { olcum.kip = k; olcum.secimSifirla(); }
          if (aktif) ImGui::PopStyleColor();
          if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", ipucu);
        };
        sekme("⌖ Kumpas", OlcumKip::Kumpas, "Tekli öğe özellikleri (çap, alan, uzunluk, koordinat)");
        ImGui::SameLine();
        sekme("↔ Cetvel", OlcumKip::Cetvel, "İki öğe arası mesafe / açı (5 mod)");
        ImGui::SameLine();
        sekme("⚙", OlcumKip::Ayarlar, "Ayarlar");
        ImGui::Separator();

        const char* filtreAd[] = {"Her Şey", "Sadece Yüzey", "Sadece Çizgi", "Sadece Nokta",
                                  "✚ Nokta Koy (Yüzey)"};
        constexpr int filtreSayi = 5;

        // Per-seçim mesafe modu adları (enum sırası: Akıllı,KısaDüz,EnKısa,Merkez,EnUzak,UzakDüz)
        const char* modAd[] = {"✦ Akıllı", "⊥ Kısa Düz", "↔ En Kısa", "⊙ Merkez", "⤢ En Uzak",
                               "⇕ Uzak Düz"};
        constexpr int modSayi = 6;

        if (olcum.kip == OlcumKip::Ayarlar) {
          ImGui::TextDisabled("Ölçüm Ayarları");
          ImGui::Checkbox("Ölçümler Kalıcı Olsun (otomatik)", &olcum.kaliciVarsayilan);
          ImGui::SetNextItemWidth(-1);
          if (ImGui::SliderInt("Ondalık Hane", &olcum.ondalik, 0, 6, "%d hane"))
            olcum.metinleriYenile();  // tüm ölçü metinleri anında yeni hassasiyetle
          int mm = (int)olcum.mod1;
          ImGui::SetNextItemWidth(-1);
          if (ImGui::Combo("Varsayılan Mod (ikisi)", &mm, modAd, modSayi)) {
            olcum.mod1 = olcum.mod2 = (MesafeModu)mm; olcum.yenidenHesapla();
          }
          ImGui::Separator();
          ImGui::TextDisabled("Akıllı: geometriye göre otomatik seçer\n(silindir→merkez, düzlem→dik, diğer→en kısa).");
          ImGui::TextDisabled("Kalıcı ölçümler ürün ağacı/geometri ile\nparametrik bağlı — çizim değişince güncellenir.");
        } else {
          const bool cetvel = olcum.kip == OlcumKip::Cetvel;
          // Seçim filtreleri
          int f1 = (int)olcum.filtre1;
          ImGui::SetNextItemWidth(-1);
          if (ImGui::Combo(cetvel ? "Seçim 1:" : "Filtre:", &f1, filtreAd, filtreSayi))
            olcum.filtre1 = (SecimFiltre)f1;
          if (cetvel) {
            int f2 = (int)olcum.filtre2;
            ImGui::SetNextItemWidth(-1);
            if (ImGui::Combo("Seçim 2:", &f2, filtreAd, filtreSayi)) olcum.filtre2 = (SecimFiltre)f2;
          }

          ImGui::Separator();
          ImGui::TextColored({0.35f, 0.85f, 0.9f, 1.0f}, "Seçimler:");
          const auto refSatir = [&](const char* et, const OlcumRef& r) {
            ImGui::TextDisabled("%s", et);
            ImGui::SameLine();
            if (!r.var) { ImGui::TextUnformatted("Seçilen Yok"); return; }
            ImGui::TextColored({0.9f, 0.85f, 0.5f, 1.0f}, "%s", r.tipAd.c_str());
            if (!r.altTipAd.empty()) {
              ImGui::SameLine();
              ImGui::TextDisabled("(%s)", r.altTipAd.c_str());
            }
          };
          refSatir("Seçim 1 Tipi:", olcum.ref1);
          if (cetvel) refSatir("Seçim 2 Tipi:", olcum.ref2);

          // Per-seçim mesafe modu (sadece Cetvel) — her seçim ayrı ayarlanır (CATIA/web).
          if (cetvel) {
            int md1 = (int)olcum.mod1;
            ImGui::SetNextItemWidth(-1);
            if (ImGui::Combo("Seçim 1 Modu", &md1, modAd, modSayi)) {
              olcum.mod1 = (MesafeModu)md1; olcum.yenidenHesapla();
            }
            int md2 = (int)olcum.mod2;
            ImGui::SetNextItemWidth(-1);
            if (ImGui::Combo("Seçim 2 Modu", &md2, modAd, modSayi)) {
              olcum.mod2 = (MesafeModu)md2; olcum.yenidenHesapla();
            }
          }

          ImGui::Separator();
          ImGui::TextColored({0.35f, 0.85f, 0.9f, 1.0f}, "Sonuç:");
          if (olcum.canliVar) {
            if (!olcum.canli.aciklama.empty())
              ImGui::TextWrapped("%s", olcum.canli.aciklama.c_str());
            std::string sonuc = olcum.canli.metin;
            for (char& ch : sonuc) if (ch == '\n') ch = ' ';
            ImGui::TextColored({1.0f, 0.82f, 0.35f, 1.0f}, "  %s", sonuc.c_str());
          } else {
            ImGui::TextDisabled(cetvel ? "3D'de iki öğe seçin" : "3D'de bir öğe seçin");
          }

          // Kalıcılaştır
          ImGui::Dummy({0, 2});
          const bool kalicilanabilir = olcum.canliVar;
          if (!kalicilanabilir) ImGui::BeginDisabled();
          ImGui::PushStyleColor(ImGuiCol_Button, {0.30f, 0.68f, 0.32f, 1.0f});
          ImGui::PushStyleColor(ImGuiCol_ButtonHovered, {0.34f, 0.76f, 0.36f, 1.0f});
          if (ImGui::Button("📌 Ölçüm Kalıcı Olsun", {-1, 0})) {
            if (olcum.kalicilastir()) { olcum.secimSifirla(); bilgiVer("Ölçüm kalıcı yapıldı"); }
          }
          ImGui::PopStyleColor(2);
          if (!kalicilanabilir) ImGui::EndDisabled();

          ImGui::Checkbox("Ölçümler Kalıcı Olsun", &olcum.kaliciVarsayilan);
        }

        // Kalıcı ölçüm listesi (her modda görünür)
        if (!olcum.hepsi().empty()) {
          ImGui::Separator();
          ImGui::TextDisabled("Kalıcı Ölçümler (%zu)", olcum.hepsi().size());
          u32 silinecek = 0;
          for (const Olcum& oc : olcum.hepsi()) {
            ImGui::PushID((int)oc.kimlik);
            std::string tek = oc.metin;
            for (char& ch : tek) if (ch == '\n') ch = ' ';
            if (!oc.gecerli) ImGui::PushStyleColor(ImGuiCol_Text, {0.6f, 0.4f, 0.4f, 1.0f});
            ImGui::Text("%s", tek.c_str());
            if (!oc.gecerli) ImGui::PopStyleColor();
            ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - 18);
            if (ImGui::SmallButton("✕")) silinecek = oc.kimlik;
            ImGui::PopID();
          }
          if (silinecek) olcum.sil(silinecek);
          if (ImGui::SmallButton("Tümünü Temizle")) olcum.temizle();
        }
      }
      ImGui::End();
    }

    // ---- Durum çubuğu ---- ÜST-SAĞA dayalı (alt araç şeridiyle çakışmasın; ayrı üst-orta
    // toast'a gerek kalmaz). Ana viewport'a sabit (çoklu-viewport'ta kopmasın).
    const ImGuiViewport* durumVp = ImGui::GetMainViewport();
    ImGui::SetNextWindowViewport(durumVp->ID);
    ImGui::SetNextWindowPos(
        {durumVp->WorkPos.x + durumVp->WorkSize.x - 12.0f, durumVp->WorkPos.y + 8.0f},
        ImGuiCond_Always, {1.0f, 0.0f});  // sağ-üst köşe pivotu → sağa dayalı
    ImGui::SetNextWindowBgAlpha(0.45f);
    if (ImGui::Begin("##durum", nullptr,
                     ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
                         ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
      ImGui::TextColored({0.4f, 0.8f, 1.0f, 1.0f}, "[%s]", kipAd[(int)kip]);
      ImGui::SameLine();
      if (const Dugum* v = sahne.bul(sahne.vurgu.govde)) {
        ImGui::Text("%s", v->ad.c_str());
        if (sahne.vurgu.alt) { ImGui::SameLine(); ImGui::TextDisabled("#%u", sahne.vurgu.alt); }
        ImGui::SameLine();
      }
      if (zaman < durumMesajBitis) {
        ImGui::TextColored({1.0f, 0.75f, 0.3f, 1.0f}, "%s", durumMesaj.c_str());
        ImGui::SameLine();
      }
      ImGui::TextDisabled("1-4: seçim filtresi · Orta: kaydır · Orta+Sağ: döndür · Sol+Sağ: zoom · "
                          "Orta tık: merkezle · Sol: seç · Teker: yakınlaş · F: çerçevele · M: ölç");
    }
    ImGui::End();

    // ---- Kare çizimi ----
    g_sonAdim = "kareCiz: GPU kare";  // faz izleyici
    VkCommandBuffer kb = vulkan.kareBasla();
    if (!kb) { arayuz.kareIptal(); return false; }  // kare atlandı (lambda: continue→return)

    // Pick filtresi zorlaması (refSecim/camSecim/olcum). Mod başlarken kullanıcının
    // 1/2/3/4 filtresini yedekle, mod bitince geri yükle → filtre sızmaz (O1).
    const bool pickModAktif = refSecim.aktif() || camSis.secim().aktif() ||
                              analizSis.secimAktif() ||
                              (olcum.acik && olcum.kip != OlcumKip::Ayarlar);
    if (pickModAktif && !pickFiltreYedekVar) {
      pickYedYuz = secimSis.fYuz; pickYedKenar = secimSis.fKenar;
      pickYedNokta = secimSis.fNokta; pickYedGovde = secimSis.fGovde;
      pickFiltreYedekVar = true;
    } else if (!pickModAktif && pickFiltreYedekVar) {
      secimSis.fYuz = pickYedYuz; secimSis.fKenar = pickYedKenar;
      secimSis.fNokta = pickYedNokta; secimSis.fGovde = pickYedGovde;
      pickFiltreYedekVar = false;
    }

    // GSD/Hole sahne-ref seçim modu aktifse GPU pick filtresini kilitle
    // (serbest nokta/yüz → Yüz; kenar ucu → Kenar; köşe → Nokta). En yüksek öncelik.
    if (refSecim.aktif()) {
      const int bt = refSecim.beklenenTur();
      secimSis.fYuz = (bt == 0 || bt == 1);
      secimSis.fKenar = (bt == 2);
      secimSis.fNokta = (bt == 3);
      secimSis.fGovde = false;
    } else
    // Analiz seçim modu (çekim yönü/kesit normali → Yüz, hedef → Gövde).
    if (analizSis.secimAktif()) {
      const int bt = analizSis.beklenenVurguTur();
      secimSis.fYuz = (bt == 1);
      secimSis.fKenar = false;
      secimSis.fNokta = false;
      secimSis.fGovde = (bt == 0);
    } else
    // CAM seçim modu aktifse GPU pick filtresini beklenen topolojiye kilitle
    // (Kontur/Ada/Düzlem/ToolAxis→Yüz, Kenar→Kenar, Stok→Gövde). Hover doğru
    // öğeyi vurgular; ölçümden önce gelir (op penceresi öncelikli).
    if (camSis.secim().aktif()) {
      const int bt = camSis.secim().beklenenVurguTur();
      secimSis.fYuz = (bt == 1);
      secimSis.fKenar = (bt == 2);
      secimSis.fNokta = (bt == 3);
      secimSis.fGovde = (bt == 0);
    } else
    // Ölçüm penceresi açıkken GPU pick filtresini aktif seçim filtresinden sür
    // (Her Şey / Yüzey / Kenar / Nokta) → doğru topoloji türü hover'lanır.
    if (olcum.acik && olcum.kip != OlcumKip::Ayarlar) {
      const SecimFiltre af = olcum.aktifFiltre();
      secimSis.fYuz = (af == SecimFiltre::HerSey || af == SecimFiltre::Yuzey);
      secimSis.fKenar = (af == SecimFiltre::HerSey || af == SecimFiltre::Cizgi);
      secimSis.fNokta = (af == SecimFiltre::HerSey || af == SecimFiltre::Nokta);
      secimSis.fGovde = false;
    }

    // ---- Belge çizimi (belge_ciz.cpp — MDI Dalga 2): hover çözümü + kamera
    // matrisi + GPU pick geçişi + model/analiz/kenar/nokta/seçim geçişleri.
    // Ana belge varsayılan viewport hedefine çizer; ek belgeler kendi
    // ViewportHedef + CizimOnbellek örnekleriyle aynı fonksiyondan geçer.
    BelgeCizimGirdi cizGirdi;
    cizGirdi.zaman = zaman;
    cizGirdi.kare = kare;
    cizGirdi.imlecX = fare.sonX;
    cizGirdi.imlecY = fare.sonY;
    cizGirdi.vpHover = vpHover;
    // Selection Stack "peek" lens: aktif adayın gövdesini sabit imleç dairesinde
    // occluder'sız göster (merkez=tık noktası, framebuffer-yerel piksel).
    if (secimYigini.acikMi() && secimGorunum().peekAcik) {
      if (const SecimAday* pa = secimYigini.aktifAday()) {
        cizGirdi.peekGovde = pa->govde;
        cizGirdi.peekAlt = pa->alt;
        cizGirdi.peekTur = pa->tur;
        cizGirdi.peekMerkezX = secimYigini.merkez().x - vpRectMinX;
        cizGirdi.peekMerkezY = secimYigini.merkez().y - vpRectMinY;
        cizGirdi.peekYari = secimYigini.yaricap();
      }
    }
    g_sonAdim = "GPU: belgeCiz";
    belgeCiz(belge, vulkan.viewportVarsayilan(), vulkan, hatlar, anaCizim,
             isikDurum, kb, cizGirdi, sonEnBoy, sonG, sonY_);
    // Ek belgeler: her açık belge penceresi kendi hedefine (pick + sahne geçişleri).
    belgeleriKaydet(belgeYon, vulkan, hatlar, isikDurum, kb, zaman, kare);
    wtecKafaKaydet(vulkan, hatlar, isikDurum, kb, zaman, kare);  // WTEC kafa GPU record

    // ---- Girdi hover probu (A): çözülmüş hover'ı girdi soyutlamasına besle ----
    // Yalnız değişimde HoverDegisti olayı yayar (probun dinleyicisi). Girdi tüketmez.
    {
      girdi::HoverBilgi hb;
      hb.govdeId = sahne.vurgu.govde;
      hb.altId = sahne.vurgu.alt;
      hb.tur = girdi::SahneHoverProbu::turCevir(secimSis.vurguTur(), sahne.vurgu.govde);
      hoverProbu.besle(hb);

      // ---- Bağlam farkındalık motoru (Dalga 1): ham hover + durum → semantik hedef ----
      // Motor 18 kategoriyi + durumu (workbench/sketch/araç/seçim) çözer, önbellekli brep
      // sorgusuyla (tek OCCT taraması) + değişimde BaglamCozuldu yayar. SUNUM YOK
      // (komut haritası/HUD Dalga 2/3); mevcut politika/menü/hover akışı DEĞİŞMEZ.
      baglam::BaglamDurum bd;
      bd.workbench = cekirdek.workbenchler().aktif();
      bd.sketchAktif = (aktifSketch != VARLIK_YOK);
      bd.arac = refSecim.aktif()          ? baglam::BaglamDurum::Arac::RefSecim
                : camSis.secim().aktif()  ? baglam::BaglamDurum::Arac::CamSecim
                : analizSis.secimAktif()  ? baglam::BaglamDurum::Arac::Analiz
                : (olcum.acik && olcum.kip != OlcumKip::Ayarlar)
                    ? baglam::BaglamDurum::Arac::Olcum
                    : baglam::BaglamDurum::Arac::Yok;
      bd.secimSayi = (u32)sahne.secililer().size();
      bd.secimHomojen = bd.secimSayi > 0;  // seçim kipi tekil topoloji dayatır
      switch (sahne.kip()) {
        case SecimKip::Yuz:   bd.secimTuru = girdi::HedefTur::Yuz; break;
        case SecimKip::Kenar: bd.secimTuru = girdi::HedefTur::Kenar; break;
        case SecimKip::Nokta: bd.secimTuru = girdi::HedefTur::Nokta; break;
        default:              bd.secimTuru = girdi::HedefTur::Govde; break;
      }
      belge.baglamMotoru.guncelle(hb, bd);
    }

    // ---- Workbench hızlı seçici (sağ-alt FAB + yay). Panellerin üstünde. ----
    workbenchSecici(cekirdek);

    // ---- Toast bildirimleri (tüm pencerelerin üstünde, kare-sonu tek çağrı) ----
    ui::toastlariCiz();

    // ---- Ana geçiş (yalnız ImGui, viewport dokusu içinde gösterilir) ----
    g_sonAdim = "GPU: anaGecisBasla";
    vulkan.anaGecisBasla(0.04f, 0.045f, 0.06f);
    g_sonAdim = "GPU: arayuz.ciz (ImGui render)";
    arayuz.ciz(kb);
    g_sonAdim = "GPU: kareBitir";
    vulkan.kareBitir();
    // Multi-viewport: dışarı taşınan panellerin OS pencereleri (ana submit SONRASI).
    g_sonAdim = "GPU: viewport pencereleri";
    arayuz.viewportPencereleriCiz();
    return true;
}  // ---- FikoBelgePenceresi::kareCiz() sonu -----------------------------------

// ---- FikoBelgePenceresi::baslat() — kare döngüsü öncesi tek-sefer init -----------
void FikoBelgePenceresi::baslat() {
  const bool senaryoDokum = senaryoDokum_;
  std::filesystem::path& senaryoDizin = senaryoDizin_;
  int& snAdim = snAdim_;

  baslangic_ = std::chrono::steady_clock::now();
  oncekiKare_ = baslangic_;
  kareSuresiMs_ = 0.0f;
  kare_ = 0;

  // ---- Görsel oto-senaryo durumu (--senaryo-dokum): kutu → döküm sim → PNG dökümü.
  // Komutlar registry'den (cekirdek.menuler) çalıştırılır; PNG'ler insa/senaryo/'ya.
  snAdim = -1;
  if (senaryoDokum) {
    wchar_t exeYol[MAX_PATH]{};
    GetModuleFileNameW(nullptr, exeYol, MAX_PATH);
    // insa/<cfg>/uygulama/fikocad/fikocad.exe → insa/senaryo
    senaryoDizin = std::filesystem::weakly_canonical(
        std::filesystem::path(exeYol).parent_path() / ".." / ".." / ".." / "senaryo");
    std::error_code ec;
    std::filesystem::create_directories(senaryoDizin, ec);
    snAdim = 0;
    gunluk::bilgi("Senaryo modu: PNG dizini {}", senaryoDizin.string());
  }
}

// ---- FikoBelgePenceresi::kareCalistir() — bir kare (zaman + kareCiz + senaryo) ---
// return false = bu pencere çıkmak istiyor (oto-test/aygıt-kayıp/senaryo/kapanış).
bool FikoBelgePenceresi::kareCalistir() {
  // Döngü/senaryo gövdesi bare isimlerini korumak için üye alias'ları.
  Pencere& pencere = pencere_;
  VulkanBaglam& vulkan = vulkan_;
  const std::string& shaderDizin = shaderDizin_;
  BelgeYonetici& belgeYon = belgeYon_;
  PencereOrtami& ort = ort_;
  Belge& belge = *ort.belgePtr;
  UygulamaBaglam& bglm = *ort.bglmPtr;
  Cekirdek& cekirdek = belge.cekirdek;
  Sahne& sahne = belge.sahne;
  DokumSistemi& dokumSis = belge.dokumSis;
  auto& menuK = cekirdek.menuler();
  auto& tumunuSigdir = bglm.tumunuSigdir;
  f32& simdikiZaman = ort.simdikiZaman;
  const int otoTestKare = otoTestKare_;
  std::filesystem::path& senaryoDizin = senaryoDizin_;
  int& snAdim = snAdim_;
  f32& snBekle = snBekle_;
  f32& snTimeout = snTimeout_;
  u32& snKutu = snKutu_;
  u32& snSim = snSim_;
  int& snScrub = snScrub_;
  (void)pencere; (void)shaderDizin;

  // Pencere kapandıysa (WM_CLOSE) bu pencere çıkmak ister (döngü sahibi kaldırır).
  if (pencere_.kapatildi()) return false;

  {
    const auto simdi = std::chrono::steady_clock::now();
    const f32 zaman = std::chrono::duration<f32>(simdi - baslangic_).count();
    simdikiZaman = zaman;
    cekirdek.olaylar().zamanAyarla(zaman);  // olay kayıtları uygulama saatini damgalar
    const f32 dt = std::clamp(std::chrono::duration<f32>(simdi - oncekiKare_).count(), 0.0f, 0.1f);
    kareSuresiMs_ = kareSuresiMs_ * 0.95f + dt * 1000.0f * 0.05f;
    oncekiKare_ = simdi;

    // kareCiz() için kare-yerel zaman durumu (üye üzerinden geçir).
    dt_ = dt;
    zaman_ = zaman;
    simdi_ = simdi;
    if (!kareCiz()) return true;  // kare atlandı (swapchain hazır değil) → devam et
    int& kare = kare_;  // senaryo tail sayaç alias'ı (++kare üye üzerinde)

    // ---- Görsel oto-senaryo (--senaryo-dokum): kare gönderildi → görüntü hazır.
    // Her adım görseli kurar, sonraki karelerde çizilmesini bekler, PNG döker.
    if (snAdim >= 0 && zaman >= snBekle) {
      const auto png = [&](const char* ad) {
        vulkan.viewportPngKaydet((senaryoDizin / ad).string().c_str());
      };
      const auto komutCalistir = [&](const char* grup, const char* ad) {
        for (const auto& o : menuK.hepsi())
          if (o.grup == grup && o.ad == ad) { cekirdek.komutCalistir(o, "senaryo"); return; }
      };
      dokum::DokumSimulasyonu* sn = snSim ? dokumSis.bul(snSim) : nullptr;
      switch (snAdim) {
        case 0:  // kare 10: kutu ekle (60 mm)
          if (kare < 10) break;
          komutCalistir("Geometri", "Kutu");
          if (!sahne.dugumler().empty()) {
            Dugum& d = sahne.dugumler().back();
            snKutu = d.kimlik;
            d.p1 = d.p2 = d.p3 = 60.0f;
            d.yenidenInsaGerek = true;
          }
          gunluk::bilgi("Senaryo: kutu eklendi (kimlik {})", snKutu);
          snAdim = 1;
          snBekle = zaman + 0.3f;
          break;
        case 1:  // kamera tümünü sığdır (yumuşatma otursun diye bekle)
          tumunuSigdir();
          snAdim = 2;
          snBekle = zaman + 1.0f;
          break;
        case 2:  // sahne PNG + döküm sim ekle + katılaşma çözümü
          png("01_sahne.png");
          snSim = dokumSis.ekle(snKutu);
          if (dokum::DokumSimulasyonu* s = dokumSis.bul(snSim)) s->cozunurluk = 24;
          if (!dokumSis.cozumBaslat(snSim)) {
            gunluk::hata("Senaryo: cozumBaslat basarisiz");
            snAdim = 99;
            break;
          }
          gunluk::bilgi("Senaryo: katilasma cozumu basladi (cozunurluk 24)");
          snAdim = 3;
          snTimeout = zaman + 120.0f;
          break;
        case 3:  // katılaşma bitene kadar bekle → dolum başlat
          if (dokumSis.cozuluyorMu(snSim)) {
            if (zaman > snTimeout) { gunluk::hata("Senaryo: cozum TIMEOUT"); snAdim = 99; }
            break;
          }
          if (!sn || !sn->sonuc.hazir) {
            gunluk::hata("Senaryo: katilasma sonucu yok");
            snAdim = 99;
            break;
          }
          gunluk::bilgi("Senaryo: katilasma tamam ({} s)", sn->sonuc.toplamSure);
          if (!dokumSis.dolumBaslat(snSim)) {
            gunluk::hata("Senaryo: dolumBaslat basarisiz");
            snAdim = 99;
            break;
          }
          snAdim = 4;
          snTimeout = zaman + 120.0f;
          break;
        case 4:  // dolum bitene kadar bekle
          if (dokumSis.cozuluyorMu(snSim)) {
            if (zaman > snTimeout) { gunluk::hata("Senaryo: dolum TIMEOUT"); snAdim = 99; }
            break;
          }
          if (!sn || !sn->dolum.hazir) {
            gunluk::hata("Senaryo: dolum sonucu yok");
            snAdim = 99;
            break;
          }
          gunluk::bilgi("Senaryo: dolum tamam ({} s)", sn->dolum.toplamSure);
          snScrub = 0;
          snAdim = 5;
          snBekle = zaman + 0.2f;
          break;
        case 5: {  // scrubber anı: pürüzsüz sıvı yüzeyi görselini kur
          if (!sn) { snAdim = 99; break; }
          static const f64 kSnOran[5] = {0.2, 0.4, 0.6, 0.8, 1.0};
          const f64 an =
              kSnOran[snScrub] >= 1.0 ? -1.0 : kSnOran[snScrub] * sn->dolum.toplamSure;
          dokumSis.dolumGorsellestir(snSim, an, DolumGorunum::Sivi, DolumRenk::Sicaklik);
          snAdim = 6;
          snBekle = zaman + 0.4f;  // görsel birkaç kare çizilsin
          break;
        }
        case 6: {  // scrubber PNG; 5 an bitince katılaşma sıcaklık katmanına geç
          static const char* kSnAd[5] = {"02_dolum_20.png", "02_dolum_40.png",
                                         "02_dolum_60.png", "02_dolum_80.png",
                                         "02_dolum_100.png"};
          png(kSnAd[snScrub]);
          if (++snScrub < 5) {
            snAdim = 5;
            snBekle = zaman + 0.1f;
          } else {
            dokumSis.dolumKaldir(snSim);
            dokumSis.xrayAyarla(snSim, false, DokumSistemi::XrayHepsi);
            dokumSis.katmanGorsellestir(snSim, "Sıcaklık (Son)");
            snAdim = 7;
            snBekle = zaman + 0.4f;
          }
          break;
        }
        case 7:  // katılaşma sıcaklık PNG → porozite (varsayılan: baloncuk izo-yüzey)
          png("03_katilasma_sicaklik.png");
          if (sn) {
            dokumSis.voxelKaldir(snSim);  // sıcaklık ısı-haritasını bırak
            dokumSis.poroziteBaloncukGoster(snSim, true, 0.5f);
          }
          snAdim = 8;
          snBekle = zaman + 0.4f;
          break;
        case 8:  // porozite PNG → Feedmod katmanı (MAGMA riser doğrulama dili)
          png("04_porozite.png");
          if (sn) {
            dokumSis.poroziteBaloncukGoster(snSim, false);
            dokumSis.katmanGorsellestir(snSim, "Feedmod");
          }
          snAdim = 9;
          snBekle = zaman + 0.4f;
          break;
        case 9: {  // Feedmod PNG → değer-eşiği taraması ortası ("soyma" yarıda)
          png("05_feedmod.png");
          if (const dokum::SonucKatmanlari* kl = dokumSis.katmanlarAl(snSim))
            if (const dokum::KatmanBilgi* fkb = kl->bul("Feedmod"))
              dokumSis.katmanGorsellestir(snSim, "Feedmod",
                                          fkb->min + 0.5f * (fkb->maks - fkb->min));
          snAdim = 10;
          snBekle = zaman + 0.4f;
          break;
        }
        case 10:  // eşik tarama PNG → kesit süpürme ortası (X yarı dilim)
          png("06_esik_tarama.png");
          if (sn) {
            sn->gorselAyar.kesitEksen = 0;
            sn->gorselAyar.kesitOran = 0.5f;
            dokumSis.katmanGorsellestir(snSim, "Sıcaklık (Son)");
          }
          snAdim = 11;
          snBekle = zaman + 0.4f;
          break;
        case 11: {  // kesit PNG → yalnız-sıvı kipi (katılaşma ortası adacıklar)
          png("07_kesit.png");
          if (sn) {
            sn->gorselAyar.kesitEksen = -1;
            sn->gorselAyar.kesitOran = 1.0f;
            dokumSis.voxelKaldir(snSim);
            // An: FSTIME yüzdelik ortası (zaman ortası DEĞİL — Al'da Biot küçük,
            // hücrelerin çoğu son dilimde katılaşır → yarı-zaman hep sıvı görünür).
            f64 an = 0.5 * sn->sonuc.toplamSure;
            if (const dokum::SonucKatmanlari* kl = dokumSis.katmanlarAl(snSim))
              if (const dokum::KatmanBilgi* skb = kl->bul("Katılaşma Süresi"))
                an = skb->min + 0.5 * (skb->maks - skb->min);
            dokumSis.cepheGorsellestir(snSim, an, /*yalnizSivi=*/true);
          }
          snAdim = 12;
          snBekle = zaman + 0.4f;
          break;
        }
        case 12:  // yalnız-sıvı PNG → SİLİNDİR (eğri yüzey) taşma doğrulaması
          png("08_yalniz_sivi.png");
          // Eski kutu simini + gövdesini temizle, silindir ekle (r=25, boy=60).
          if (snSim) { dokumSis.voxelKaldir(snSim); dokumSis.sil(snSim); snSim = 0; }
          if (snKutu) { sahne.sil(snKutu); snKutu = 0; }
          komutCalistir("Geometri", "Silindir");
          if (!sahne.dugumler().empty()) {
            Dugum& d = sahne.dugumler().back();
            snKutu = d.kimlik;  // (silindir kimliğini snKutu'da tut)
            d.p1 = 25.0f; d.p3 = 60.0f;  // yarıçap 25, boy 60
            d.yenidenInsaGerek = true;
          }
          snAdim = 13;
          snBekle = zaman + 0.3f;
          break;
        case 13:  // silindir katılaşma çöz (küp görünüm taşma testi)
          tumunuSigdir();
          snSim = dokumSis.ekle(snKutu);
          if (dokum::DokumSimulasyonu* s = dokumSis.bul(snSim)) s->cozunurluk = 28;
          if (!dokumSis.cozumBaslat(snSim)) { snAdim = 99; break; }
          snAdim = 14;
          snTimeout = zaman + 120.0f;
          break;
        case 14:  // çözüm bekle → KÜP görünümü Sıcaklık + PNG (eğri yüzeyde taşma?)
          if (dokumSis.cozuluyorMu(snSim)) {
            if (zaman > snTimeout) { snAdim = 99; }
            break;
          }
          dokumSis.katmanGorsellestir(snSim, "Katılaşma Süresi");  // küp mesh
          snAdim = 15;
          snBekle = zaman + 0.5f;
          break;
        case 15:  // silindir küp PNG → GPU SPH dispatch doğrulaması (Vulkan compute)
          png("09_silindir_kup.png");
          // İzole GPU SPH testi: silindir kafesi (case 13-14) zaten kurulu. Döküm
          // girişi ekle + sphGpuCoz'u DOĞRUDAN çağır (worker/UI'ya dokunmaz; GPU
          // yoksa hazir=false ile temiz döner). RTX 4090 dispatch süresi loglanır.
          if (dokum::DokumSimulasyonu* s = dokumSis.bul(snSim)) {
            dokum::DokumGirisi g;
            g.konum = {0, 0, 55};  // silindir üst bölgesi (boy 60)
            g.yon = {0, 0, -1};
            g.kutleKg = 0.5; g.sureS = 0.3; g.capMm = 16;
            s->kurulum.girisler.clear();
            s->kurulum.girisler.push_back(g);
            s->kurulum.graviteYon = {0, 0, -1};
            const auto t0 = std::chrono::steady_clock::now();
            const dokum::SphDolumSonuc gs =
                dokum::sphGpuCoz(vulkan, s->kafes, s->kurulum, s->alasim, s->sphAyar);
            const f64 ms = std::chrono::duration<f64, std::milli>(
                               std::chrono::steady_clock::now() - t0).count();
            u32 dolanIc = 0, tumIc = 0;
            for (u32 c = 0; c < s->kafes.hucreSayi(); ++c) {
              if (s->kafes.tip[c] != dokum::HucreTip::Ic) continue;
              ++tumIc;
              if (c < gs.dolum.varisZamani.size() && gs.dolum.varisZamani[c] >= 0.0)
                ++dolanIc;
            }
            gunluk::bilgi("GPU SPH sonuc: hazir={} partikul={} adim={} maksHiz={:.2f} "
                          "dolum={}/{} kare={} sure={:.0f}ms",
                          gs.hazir, gs.partikulSayi, (unsigned long long)gs.adimSayi,
                          gs.maksHiz, dolanIc, tumIc, gs.kareler.size(), ms);
          }
          snAdim = 16;
          snBekle = zaman + 0.3f;
          break;
        case 16: {  // MDI SMOKE: 2. belge aç + kendi sahnesine kutu ekle
          BelgeKaydi& bk = belgeYon.yeniBelge(vulkan, shaderDizin);
          bk.belge->cekirdek.uygula(
              std::make_unique<EkleIslem>(bk.belge->sahne, DugumTip::Kutu));
          if (!bk.belge->sahne.dugumler().empty()) {
            Dugum& d = bk.belge->sahne.dugumler().back();
            d.p1 = d.p2 = d.p3 = 60.0f;
            d.yenidenInsaGerek = true;
          }
          bk.belge->kamera.sigdir({0, 0, 30}, 90.0f);
          gunluk::bilgi("Senaryo MDI: 2. belge acildi (kimlik {}, {} dugum)",
                        bk.kimlik, bk.belge->sahne.dugumler().size());
          snAdim = 17;
          snBekle = zaman + 1.0f;  // pencere boyutlansın + kamera otursun
          break;
        }
        case 17:  // MDI SMOKE: her iki belgenin hedefinden PNG (bağımsız içerik)
          png("10_mdi_ana_belge.png");
          if (!belgeYon.hepsi().empty()) {
            BelgeKaydi& bk = *belgeYon.hepsi().back();
            const std::string yol = (senaryoDizin / "10_mdi_belge2.png").string();
            if (bk.hedef->pngKaydet(yol.c_str()))
              gunluk::bilgi("Senaryo MDI: belge-2 PNG yazildi ({}x{})",
                            bk.hedef->boyut().width, bk.hedef->boyut().height);
            else
              gunluk::hata("Senaryo MDI: belge-2 PNG YAZILAMADI");
          }
          gunluk::bilgi("Senaryo tamam — PNG dizini: {}", senaryoDizin.string());
          snAdim = 99;
          break;
        default:
          break;
      }
      if (snAdim == 99) return false;  // temiz kapanış (senaryo tamam → pencere çık)
    }

    ++kare;
    if (otoTestKare > 0 && kare >= otoTestKare) {
      gunluk::bilgi("Oto-test tamam: {} kare çizildi ({})", kare, vulkan.aygitAdi());
      return false;  // oto-test bitti → pencere çık
    }
    // GPU aygıtı kayıp/yanıtsız → çık (daha fazla GPU çağrısı yapma, temiz kapan).
    if (vulkan.aygitKayipMi()) {
      gunluk::hata("GPU aygiti kayip — uygulama kapatiliyor");
      return false;
    }
  }
  return true;  // normal kare → devam et
}

}

// Üst-düzey çökme yakalayıcı: normal modda konsol yok → sessiz çökmeleri exe yanına
// "cokme.log" yaz (istisna kodu + adres). SEH filter AV/stack-fastfail'i de yakalar.
static std::wstring cokmeLogYolu() {
  wchar_t exeYol[MAX_PATH]{};
  GetModuleFileNameW(nullptr, exeYol, MAX_PATH);
  return std::filesystem::path(exeYol).replace_filename(L"cokme.log").wstring();
}
static LONG WINAPI cokmeYakala(EXCEPTION_POINTERS* bilgi) {
  FILE* f = nullptr;
  _wfopen_s(&f, cokmeLogYolu().c_str(), L"w");
  if (f) {
    fprintf(f, "FikoCad cokme (yakalanmamis istisna)\n");
    if (bilgi && bilgi->ExceptionRecord) {
      fprintf(f, "kod:   0x%08X\n", (unsigned)bilgi->ExceptionRecord->ExceptionCode);
      fprintf(f, "adres: %p\n", (void*)bilgi->ExceptionRecord->ExceptionAddress);
    }
    fprintf(f, "son adim: %s\n", g_sonAdim ? g_sonAdim : "?");
    fprintf(f, "teshis: %s\n", fiko::gunluk::g_teshisAdim ? fiko::gunluk::g_teshisAdim : "?");
    fclose(f);
  }
  return EXCEPTION_EXECUTE_HANDLER;  // süreç sonlanır ama teşhis yazıldı
}

int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR komutSatiri, int) {
  SetUnhandledExceptionFilter(cokmeYakala);  // AV/fastfail → cokme.log
  geometri::occtSinyalKur();  // OCCT donanim sinyalleri → Standard_Failure (boolean/fillet çökme koruması)
  int otoTestKare = 0;
  bool senaryoDokum = false;
  const std::wstring args = komutSatiri ? komutSatiri : L"";
  if (args.find(L"--oto-test") != std::wstring::npos) otoTestKare = 30;
  if (args.find(L"--senaryo-dokum") != std::wstring::npos) senaryoDokum = true;
  if (otoTestKare || senaryoDokum) {
    AllocConsole();
    FILE* f;
    freopen_s(&f, "CONOUT$", "w", stdout);
    freopen_s(&f, "CONOUT$", "w", stderr);
    // Senaryo modu otomatik doğrulama: gunluk çıktısını exe yanındaki dosyaya
    // yönlendir (betikle GPU dispatch/perf denetimi; konsol pencere kalabalık).
    if (senaryoDokum) {
      wchar_t exeYol[MAX_PATH]{};
      GetModuleFileNameW(nullptr, exeYol, MAX_PATH);
      const std::wstring logYol =
          std::filesystem::path(exeYol).replace_filename(L"senaryo.log").wstring();
      FILE* lf;
      _wfreopen_s(&lf, logYol.c_str(), L"w", stdout);   // BILGI/UYARI → dosya
      _dup2(_fileno(stdout), _fileno(stderr));  // HATA/[perf] aynı fd → sıralı, kayıpsız
      setvbuf(stdout, nullptr, _IONBF, 0);      // abort öncesi tampon kaybı olmasın
      setvbuf(stderr, nullptr, _IONBF, 0);
    }
  }
  try {
    return calistir(otoTestKare, senaryoDokum);
  } catch (const std::exception& e) {
    FILE* f = nullptr;
    _wfopen_s(&f, cokmeLogYolu().c_str(), L"w");
    if (f) {
      fprintf(f, "FikoCad cokme (C++ istisnasi)\nwhat: %s\nteshis: %s\n", e.what(),
              fiko::gunluk::g_teshisAdim ? fiko::gunluk::g_teshisAdim : "?");
      fclose(f);
    }
    return 3;
  }
}
