# Three.js Sistemi - FikoYT

## 🎯 Mimari

**FikoThreeJsComponent** - Ana render motoru
- WebGL + CSS3D renderer
- OrthographicCamera
- FikoKontrolCad (FreeCAD style)
- On-demand/continuous rendering

## 🚀 Performans

### Optimizasyonlar
- **Frustum Culling:** Her 2 frame'de bir
- **LOD System:** Level of Detail management
- **Object Pooling:** Memory efficients
- **On-Demand Render:** Sadece gerektiğinde
- **TWEEN:** Smooth animasyonlar

### Rendering Pipeline
```typescript
sun(): void {
  // Frustum culling (frame 2'de bir)
  if (this.frameCount % 2 === 0) {
    this.frustumCuller.updateFrustum(this.aktifKamera);
  }
  // LOD updates (frame 3'te bir)
  if (this.frameCount % 3 === 0) {
    this.lodManager.updateLODs(this.aktifKamera);
  }
}
```

## 📱 Touch Desteği

**FikoKontrolCad** mobil optimize
- **Tek parmak:** Rotate
- **İki parmak:** Zoom + Pan
- **Passive events:** Scroll performansı
- **Gesture recognition:** Akıllı menü

## 🎨 STEP Import

**FikoImporterSTP** - Dual processing
1. **OCCT-WASM:** Client-side
2. **FreeCAD API:** Server-side

**Özellikler:**
- Face-level color preservation
- Edge detection
- Scale conversion (mm→world)
- Progress tracking

## 🔧 Seçim Sistemi

**FikoSecimler1** - Multi-selection
- **Raycast:** Optimized
- **Highlight:** Face/edge/point
- **Multi-select:** Ctrl+click
- **Visual feedback:** Real-time

## 📍 İşaretleyiciler

**Fiko3DIsaretleyiciler** - 3D labels
- **Otomatik:** Ağırlık merkezine
- **Sprite/3D:** Seçimli yazı
- **Sabit boyut:** Kamera bağımsız
- **Dialog entegrasyonu**

## ⚠️ Kritik

- **Vertex color + alpha:** Zorunlu
- **matrixAutoUpdate = false:** Manuel update
- **NgZone dışı:** Render et
- **Dispose:** Memory cleanup

---
2025-11-06

**Değişiklik:** FikoKontrolCad mobil/tablet dokunmatik ekranlar için optimize edildi

**Sorun:**
- Touch event'ler `passive: true` ile dinleniyordu → `preventDefault()` çalışmıyordu
- Pointer events sadece mouse/pen kontrolü yapıyordu → touch desteklenmiyordu
- Pan, rotate, zoom mobilde çalışmıyordu

**Çözüm:**

**1. Touch Event Listener'ları**
```javascript
// passive: false ile preventDefault() çalışır hale geldi
this.domElement.addEventListener("touchstart", M, { passive: false });
this.domElement.addEventListener("touchend", x, { passive: false });
this.domElement.addEventListener("touchmove", X, { passive: false });
```

**2. Pointer Events - Touch Desteği**
```javascript
function k(e) {
    if (e.pointerType === "touch") {
        e.preventDefault();
        e.stopPropagation();

        var t = i !== n.NONE ? i : s;
        if (t === n.TOUCH_ROTATE && !o.noRotate) {
            p.copy(m);
            m.copy(Y(e.pageX, e.pageY));
        }
    }
}
```

**3. Touch Start (M)**
```javascript
// Tek parmak → TOUCH_ROTATE
case 1:
    if (!o.noRotate) {
        s = n.TOUCH_ROTATE;
        m.copy(Y(e.touches[0].pageX, e.touches[0].pageY));
        p.copy(m);
    }
    break;

// İki parmak → TOUCH_ZOOM_PAN
case 2:
    s = n.TOUCH_ZOOM_PAN;
    // Parmaklar arası mesafe (zoom için)
    g = l = Math.sqrt(t * t + c * c);
    // Orta nokta (pan için)
    b.copy(N(a, i));
    y.copy(b);
    break;
```

**4. Touch Move (X)**
```javascript
case 1:
    // Tek parmak - rotate
    if (s === n.TOUCH_ROTATE && !o.noRotate) {
        p.copy(m);
        m.copy(Y(e.touches[0].pageX, e.touches[0].pageY));
    }
    break;

case 2:
    // İki parmak - zoom ve pan
    if (s === n.TOUCH_ZOOM_PAN) {
        g = Math.sqrt(t * t + touchY * touchY); // Zoom hesaplama
        b.copy(y);
        y.copy(N(c, a)); // Pan hesaplama
    }
    break;
```

**5. Touch End (x)**
```javascript
case 0:
    s = n.NONE; // Tüm parmaklar kalktı
    break;

case 1:
    // Bir parmak kaldı → rotate moduna geç
    s = n.TOUCH_ROTATE;
    break;

default:
    // 2+ parmak → zoom/pan modunda kal
    s = n.TOUCH_ZOOM_PAN;
```

**Kullanım:**
- **Tek parmak:** 3D sahneyi döndürme (rotate)
- **İki parmak:**
  - Pinch (zoom): Parmakları açıp kapama
  - Pan: İki parmakla kaydırma
- **State Management:** `o.aktifMod` ile aktif mod takip edilir

**Not:** Artık mobil ve tablet cihazlarda dokunmatik kontroller tam olarak çalışıyor.

### Touch Gesture Context Menü Optimizasyonu (Son Güncelleme: 2025-01-02)

**Değişiklik:** İki parmakla pan yaparken yanlışlıkla context menü açılması sorunu çözüldü

**Sorun:**
- İki parmak dokunur dokunmaz context menü açılıyordu
- Pan hareketi yapmak isteyen kullanıcı için kötü deneyim
- Hareket algılaması yoktu - sadece parmak sayısı kontrolü vardı

**Çözüm - Akıllı Gesture Tanıma:**

**1. Hareket Takibi Değişkenleri**
```typescript
// FikoSecililer.ts
private touchStartTime: number = 0;
private touchStartX: number = 0;
private touchStartY: number = 0;
private readonly touchMoveThreshold: number = 10; // Piksel - hareket eşiği
private readonly touchHoldDelay: number = 300; // Milisaniye - context menü için bekleme
private touchMoved: boolean = false;
private touchHoldTimeout: any = null;
```

**2. Touch Start - Başlangıç Pozisyonu Kaydet**
```typescript
touchstart() {
    return fromEvent<TouchEvent>(...).subscribe(($event) => {
        if ($event.touches.length < 2) {
            // Tek parmak - menüleri kapat
            this.anaGosterim.Dialog3DHizliMenu?.kapa();
            return;
        }

        // İki parmak - hareket takibini başlat
        this.touchMoved = false;
        this.touchStartTime = Date.now();
        this.touchStartX = $event.touches[0].pageX;
        this.touchStartY = $event.touches[0].pageY;

        // 300ms bekle - hareket yoksa context menü aç
        this.touchHoldTimeout = setTimeout(() => {
            if (!this.touchMoved && $event.touches.length >= 2) {
                this.showContextMenuForTouch($event);
            }
        }, this.touchHoldDelay);
    });
}
```

**3. Touch Move - Hareket Algılama**
```typescript
touchmove() {
    return fromEvent<TouchEvent>(...).subscribe(($event) => {
        if ($event.touches.length >= 2 && this.touchStartTime > 0) {
            const currentX = $event.touches[0].pageX;
            const currentY = $event.touches[0].pageY;
            const deltaX = Math.abs(currentX - this.touchStartX);
            const deltaY = Math.abs(currentY - this.touchStartY);

            // 10 piksel hareket = pan işlemi
            if (deltaX > this.touchMoveThreshold || deltaY > this.touchMoveThreshold) {
                this.touchMoved = true;
                // Context menü timeout'unu iptal et
                if (this.touchHoldTimeout) {
                    clearTimeout(this.touchHoldTimeout);
                    this.touchHoldTimeout = null;
                }
            }
        }
    });
}
```

**4. Touch End - Temizlik**
```typescript
touchend() {
    return fromEvent<TouchEvent>(...).subscribe(($event) => {
        if (this.touchHoldTimeout) {
            clearTimeout(this.touchHoldTimeout);
            this.touchHoldTimeout = null;
        }
        this.touchStartTime = 0;
        this.touchMoved = false;
    });
}
```

**5. Context Menü Gösterme**
```typescript
private showContextMenuForTouch($event: TouchEvent) {
    // Raycast ile objeyi tespit et
    let odaktakiler = this.odaktakiler($event);
    if (odaktakiler.length > 0) {
        for (let element of odaktakiler) {
            if (element?.object?.sagMenu) {
                // Context menü overlay'i oluştur
                const x = $event.touches[0].pageX;
                const y = $event.touches[0].pageY;
                // ... overlay açılır
                break;
            }
        }
    }
}
```

**Kullanıcı Deneyimi:**
- ✅ **Pan hareketi:** İki parmak ekrana dokunur dokunmaz hareket ettirilirse → context menü açılmaz
- ✅ **Context menü:** İki parmak 300ms boyunca sabit tutulursa → context menü açılır
- ✅ **Eşik değeri:** 10 piksel - küçük titreşimleri göz ardı eder
- ✅ **Timeout temizliği:** Dispose metodunda timeout'lar temizlenir

**Avantajlar:**
- Doğal pan deneyimi - anında tepki verir
- Yanlışlıkla context menü açılması engellenir
- Kasıtlı context menü açma hala mümkün (basılı tutma)
- Performans dostu - minimum overhead

**Dosyalar:**
- [FikoSecililer.ts](../fikoyt-zorro.com/src/service/fikoyt-ui/src/lib/fiko-three-js/IFikoThreeJS/FikoSecililer.ts) (Touch gesture logic)
- [FikoKontrolCad.js](../fikoyt-zorro.com/public/js/three/FikoKontrolCad.js) (Pan/zoom/rotate controls)

**Not:** Bu optimizasyon sayesinde mobilde 3D model inceleme deneyimi masaüstü seviyesine çıkmıştır.

## Performans Optimizasyonları

**1. On-Demand Rendering**
```typescript
markNeedsRender() {
    this.needsRender = true;
    if (this.renderMode === 'on-demand') {
        this.ciz();
    }
}
```

**2. Frustum Culling**
```typescript
frustumCuller.updateFrustum(camera);
obj.visible = frustumCuller.isVisible(obj);
```

**3. LOD (Level of Detail)**
```typescript
lod.addLevel(highPolyMesh, 0);
lod.addLevel(mediumPolyMesh, 100);
lod.addLevel(lowPolyMesh, 500);
```

**4. Object Pooling**
```typescript
const mesh = meshPool.acquire();
// kullan...
meshPool.release(mesh);
```

**5. Geometry Merging**
```typescript
const merged = BufferGeometryUtils.mergeGeometries(geometries);
```

## Animasyon (TWEEN.js)

```typescript
new TWEEN.Tween(camera.position)
    .to({ x: 10, y: 5, z: 0 }, 1000)
    .easing(TWEEN.Easing.Quadratic.Out)
    .onUpdate(() => this.markNeedsRender())
    .start();

// Render loop'ta
sun() {
    TWEEN.update();
}
```

### Ekrana Sığdır Animasyon Best Practices (Son Güncelleme: 2025-01-02)

**Değişiklik:** `ekranaSigdir()` ve `ekranaSigdir1()` metodlarında animasyon render sorunu çözüldü

**Dosya:** [FikoSahne.ts:237-371](../fikoyt-zorro.com/src/service/fikoyt-ui/src/lib/fiko-three-js/IFikoThreeJS/FikoSahne.ts#L237-L371)

**Sorun:**
- TWEEN animasyonları için continuous render loop gerekiyordu
- Desktop'ta çalışıyor ama mobilde çalışmıyordu
- `markNeedsRender()` sadece tek frame render ediyordu, TWEEN için yetersizdi

**Çözüm - Continuous Render + Promise.all Pattern:**

**1. `ekranaSigdir()` Metodu**
```typescript
// ✅ DOĞRU - Animasyon başında continuous render başlat
this.anaGosterim.surekliCiz();

const kameraTween = new TWEEN.Tween({...})
    .onUpdate((val) => {
        this.anaGosterim.aktifKamera.position.set(val.x, val.y, val.z);
        this.anaGosterim.aktifKamera.zoom = val.zoom;
        this.anaGosterim.aktifKamera.updateProjectionMatrix();
        // markNeedsRender() GEREKMİYOR - continuous render aktif
    });

const kontrolTween = new TWEEN.Tween({...})
    .onUpdate((val) => {
        this.anaGosterim.aktifKontrol.target.set(val.x, val.y, val.z);
        this.anaGosterim.aktifKontrol.update();
        // markNeedsRender() GEREKMİYOR - continuous render aktif
    });

kameraTween.start();
kontrolTween.start();

// ✅ İKİ TWEEN de bitince iptal et - Promise.all ile
Promise.all([
    new Promise<void>(resolve => kameraTween.onComplete(() => resolve())),
    new Promise<void>(resolve => kontrolTween.onComplete(() => resolve()))
]).then(() => {
    if (dondur) {
        // Döndürme animasyonu için continuous render devam etsin
    } else {
        // ✅ Animasyon bitti, continuous render'ı durdur
        this.anaGosterim.surekliCizIptal();
    }
});
```

**2. `ekranaSigdir1()` Metodu**
```typescript
// ✅ Animasyon başında continuous render başlat
this.anaGosterim.surekliCiz();

const kameraTween1 = new TWEEN.Tween(this.anaGosterim.aktifKamera)
    .to({ zoom: yakinlik, position: { x, y, z } }, 500)
    .onUpdate(() => {
        this.anaGosterim.aktifKamera.updateProjectionMatrix()
        // markNeedsRender() GEREKMİYOR
    })
    .start()

const kontrolTween1 = new TWEEN.Tween(this.anaGosterim.aktifKontrol)
    .to({ target: { x, y, z } }, 500)
    .onUpdate(() => {
        this.anaGosterim.aktifKontrol.update();
        // markNeedsRender() GEREKMİYOR
    })
    .start()

// ✅ İki TWEEN de bitince continuous render'ı durdur
Promise.all([
    new Promise<void>(resolve => kameraTween1.onComplete(() => resolve())),
    new Promise<void>(resolve => kontrolTween1.onComplete(() => resolve()))
]).then(() => {
    this.anaGosterim.surekliCizIptal();
});
```

**Neden Bu Çözüm?**

1. **Mobil Uyumluluk:** Mobilde TWEEN animasyonları için continuous render loop şart
2. **Promise.all:** İki paralel TWEEN'in de bitmesini bekler - erken iptal yok
3. **Tek İptal Noktası:** Her TWEEN'de ayrı iptal yerine, hepsi bitince tek seferde iptal
4. **TWEEN.update():** Component'in `sun()` metodunda her frame çağrılıyor (satır 569)

**Avantajlar:**
- ✅ **Desktop + Mobil + Tablet:** Tüm platformlarda çalışır
- ✅ **Smooth Animasyonlar:** Continuous render ile her frame render edilir
- ✅ **Paralel TWEEN Sorunu Yok:** Promise.all ile doğru iptal yönetimi
- ✅ **Performans:** Animasyon bitince otomatik iptal

**Dezavantajlar:**
- ⚠️ Animasyon sırasında daha fazla CPU/GPU kullanımı (kabul edilebilir)

**Not:** Bu pattern tüm TWEEN animasyonları (zoom, pan, rotate, fit-to-screen) için geçerlidir.

### Ekrana Sığdır Parametre Sorunu (Son Güncelleme: 2025-01-02)

**Değişiklik:** `ekranaSigdir()` ve `ekranaSigdir1()` metodlarının `obj` parametresi opsiyonel yapıldı

**Dosya:** [FikoSahne.ts:181-313](../fikoyt-zorro.com/src/service/fikoyt-ui/src/lib/fiko-three-js/IFikoThreeJS/FikoSahne.ts#L181-L313)

**Sorun:**
- HTML'den `ekranaSigdir()` parametre olmadan çağrılıyordu: `(click)="sahne.ekranaSigdir()"`
- Metod tanımı zorunlu parametre bekliyordu: `ekranaSigdir(obj: FikoObje3D)`
- Parametre olmadan çağrılınca `obj.traverse()` satırında hata veriyordu
- Animasyon hiç başlamıyordu

**Çözüm:**
```typescript
// ✅ DOĞRU - obj parametresi opsiyonel, yoksa sahneyi kullan
ekranaSigdir(obj?: FikoObje3D, ayar?: {...}) {
    // Eğer obj verilmemişse sahnenin kendisini kullan
    const hedefObj = obj || this;

    const geometriler: THREE.BufferGeometry[] = [];
    hedefObj.traverse((elm: any) => {
        // Sahnenin tüm geometrilerini topla
    });
    // ... animasyon devam eder
}

ekranaSigdir1(obj?: FikoObje3D) {
    // Eğer obj verilmemişse sahnenin kendisini kullan
    const hedefObj = obj || this;

    let geometriler: THREE.BufferGeometry[] = [];
    hedefObj.traverse((elm: any) => {
        // Sahnenin tüm geometrilerini topla
    });
    // ... animasyon devam eder
}
```

**Kullanım:**
```typescript
// HTML'den parametre olmadan
<button (click)="sahne.ekranaSigdir()">Ekrana Sığdır</button>

// veya programatik - belirli bir obje için
sahne.ekranaSigdir(myObject);

// veya ayarlarla
sahne.ekranaSigdir(myObject, {
    paddingOrani: 0.15,
    animasyonSuresiMs: 1000,
    otomatikDondur: true
});
```

**Sonuç:**
- ✅ Parametre olmadan çağrılabilir - tüm sahneyi ekrana sığdırır
- ✅ Parametre ile çağrılabilir - sadece belirtilen objeyi sığdırır
- ✅ Geriye dönük uyumlu - mevcut kullanımlar bozulmaz

### TWEEN.removeAll() Mobil Animasyon Sorunu (Son Güncelleme: 2025-01-02)

**Değişiklik:** `surekliCizIptal()` metodundan `TWEEN.removeAll()` çağrısı kaldırıldı

**Dosya:** [fiko-three-js.component.ts:614-630](../fikoyt-zorro.com/src\service\fikoyt-ui\src\lib\fiko-three-js\fiko-three-js.component.ts#L614-L630)

**Sorun:**
- Desktop'ta ekrana sığdır animasyonlu çalışıyordu
- Mobilde animasyonsuz, anlık snap ediyordu
- `surekliCizIptal()` içinde `TWEEN.removeAll()` **TÜM aktif TWEEN'leri iptal ediyordu**
- Paralel çalışan iki TWEEN'den biri bitince `surekliCizIptal()` çağrılıyor
- `TWEEN.removeAll()` diğer TWEEN'i de iptal ediyor → Animasyon yarıda kesiliyor

**Timing Farkı:**
```
Desktop (hızlı):
- t=0ms    : İki TWEEN başlar (kamera + kontrol)
- t=500ms  : İki TWEEN AYNI ANDA biter
- surekliCizIptal() → TWEEN.removeAll() çağrılır ama zaten bitmişler

Mobil (yavaş):
- t=0ms    : İki TWEEN başlar
- t=480ms  : Kamera TWEEN biter → surekliCizIptal() → TWEEN.removeAll()
- t=480ms  : Kontrol TWEEN yarıda kesilir! ❌
- Sonuç: Animasyon tamamlanmadan snap eder
```

**Çözüm:**
```typescript
// ❌ ESKİ KOD:
surekliCizIptal(): void {
    if (this.animationFrameId) {
      cancelAnimationFrame(this.animationFrameId);
      this.animationFrameId = undefined;
    }
    this.sunucu.setAnimationLoop(null);

    if (typeof TWEEN !== 'undefined') {
      TWEEN.removeAll(); // ❌ TÜM TWEEN'leri iptal ediyor!
    }

    this.renderMode = 'on-demand';
}

// ✅ YENİ KOD:
surekliCizIptal(): void {
    if (this.animationFrameId) {
      cancelAnimationFrame(this.animationFrameId);
      this.animationFrameId = undefined;
    }
    this.sunucu.setAnimationLoop(null);

    // ✅ TWEEN.removeAll() KALDIRILDI
    // Aktif TWEEN animasyonlarını iptal etmeyin!
    // Promise.all ile kontrollü şekilde bitmelerini bekleyin

    this.renderMode = 'on-demand';
}
```

**Neden Promise.all Gerekli:**
```typescript
// FikoSahne.ts - ekranaSigdir() metodunda
Promise.all([
    new Promise<void>(resolve => kameraTween.onComplete(() => resolve())),
    new Promise<void>(resolve => kontrolTween.onComplete(() => resolve()))
]).then(() => {
    // ✅ İKİ TWEEN de BİTTİ, şimdi güvenle iptal et
    this.anaGosterim.surekliCizIptal();
});
```

**Sonuç:**
- ✅ Desktop: Animasyonlar çalışmaya devam ediyor
- ✅ Mobil: Artık animasyonlar kesintisiz, smooth çalışıyor
- ✅ TWEEN'ler doğal olarak bitiyor, yarıda kesilmiyor

**Not:** `TWEEN.removeAll()` sadece gerçekten tüm animasyonları durdurmak istediğiniz acil durumlarda kullanılmalı. Normal animasyon bitişlerinde kullanmayın!

## Servisler

- `FikoCadTransformService` - Transform controls
- `FikoCadExportService` - glTF/STL/OBJ export
- `FikoCadMeasurementService` - Ölçüm araçları
- `FikoCadHistoryService` - Undo/redo

## FikoCizimYalin - Kamera Konumları (Son Güncelleme: 2025-01-01)

**Değişiklik:** FikoCizimYalin'a özel kamera konumları desteği eklendi
**Dosya:** [FikoCizimYalin.ts](../fikoyt-zorro.com/src/service/fikoyt-ui/src/lib/fiko-three-js/IFikoThreeJS/FikoCizimYalin.ts)

**Özellikler:**
- Her FikoCizimYalin instance'ı artık kendi `FikoKonumlar` nesnesine sahip
- Kaynakça'daki FikoKonumlar yapısıyla aynı API
- Kamera pozisyonları otomatik kaydedilir ve yüklenir (JSON serialization)

**Kullanım:**
```typescript
// Yeni çizim oluştur
const cizim = new FikoCizimYalin(sahne, "MyCizim", meshGeom, edgesGeom, vertexGeom);

// Kamera konumu ekle (sağ menüden "Konumlar" seçeneği)
// veya programatik olarak:
cizim.konumlar.kaydet("Önden Görünüm", "Ön taraf", veri);

// Konumlara erişim
cizim.konumlar.children.forEach(konum => {
    // Her konum FikoKonum tipinde
});
```

**Menü Entegrasyonu:**
- Sağ tıklama menüsünde "Konumlar" seçeneği (kamera simgesi)
- Dialog üzerinden konum ekleme/düzenleme/silme
- Konumlar görünmez (agactaGorunur: false) ama data olarak saklanır

**JSON Formatı:**
```json
{
    "type": "FikoCizimYalin",
    "isim": "MyCizim",
    "konumlar": {
        "type": "FikoKonumlar",
        "liste": [
            {
                "ismi": "Konum1",
                "aciklama": "Açıklama",
                "veri": {
                    "position": [x, y, z],
                    "quaternion": [x, y, z, w],
                    "hedef": [x, y, z],
                    "yakinlik": 1.5,
                    "oran": 1.77
                }
            }
        ]
    }
}
```

## 3D İşaretleyiciler Sistemi (Son Güncelleme: 2025-01-01)

**Değişiklik:** FikoCizimYalin'a 3D yazı işaretleyicileri eklendi (FikoKonumlar pattern'i gibi)
**Dosyalar:**
- [Fiko3DIsaretleyiciler.ts](../fikoyt-zorro.com/src/service/fikoyt-ui/src/lib/fiko-three-js/IFikoThreeJS/Fiko3DIsaretleyiciler.ts)
- [Fiko3DYaziIsaretleyici.ts](../fikoyt-zorro.com/src/service/fikoyt-ui/src/lib/fiko-three-js/IFikoThreeJS/Fiko3DYaziIsaretleyici.ts)
- [Fiko3DYazi.ts](../fikoyt-zorro.com/src/service/fikoyt-ui/src/lib/fiko-three-js/IFikoThreeJS/Fiko3DYazi.ts)
- [fiko-3dyazi-isaretleyici.component.ts](../fikoyt-zorro.com/src/service/fikoyt-ui/src/lib/fiko-three-js/IFikoThreeJS/fiko-3dyazi-isaretleyici/fiko-3dyazi-isaretleyici.component.ts)

### Özellikler:
- **Container:** Her FikoCizimYalin otomatik olarak `Fiko3DIsaretleyiciler` container'ına sahip
- **İlk İşaretleyici:** FikoCizimYalin oluşturulduğunda otomatik olarak (0,0,0) konumunda bir işaretleyici eklenir
- **Default Yazı:** İşaretleyici oluşturulurken parent'ın ismi default yazı olarak kullanılır
- **İşaretleyici Tipi:** `Fiko3DYaziIsaretleyici` (seçimli: 3D yazı veya Catia stili sprite)
- **Yazı Tipi Seçimi:** `yaziTipi: '3D' | 'Sprite'` - Default: Sprite (Catia stili)
- **Dialog Entegrasyonu:** FikoEksen pattern'i ile Dialog Core entegrasyonu
- **JSON Desteği:** Tam serileştirme/deserialize desteği (her iki tip için)
- **Raycast:** Tıklama ve çift tıklama desteği
- **Render Önceliği:** FikoEksen pattern'i ile her zaman en üstte görünür (renderOrder: 999999999)
- **TypeScript Güvenliği:** Union type ve helper metodlarla tip güvenliği

### Kullanım:

```typescript
// FikoCizimYalin oluştur
// Otomatik olarak isaretleyiciler container oluşur
// ve (0,0,0) konumunda ilk işaretleyici eklenir
const cizim = new FikoCizimYalin(sahne, "MyCizim", meshGeom, edgesGeom, vertexGeom);

// İlk işaretleyici otomatik eklendi, yeni işaretleyici ekle:
// Sağ menüden "İşaretleyiciler" → otomatik yeni işaretleyici ekler
// veya programatik:
const isaretleyici = cizim.isaretleyiciler.ekleFikoIsaretci("A");
isaretleyici.position.set(10, 20, 30);

// Default olarak parent'ın ismi yazı olarak kullanılır:
// cizim.isaretleyiciler.ekleFikoIsaretci() → yazı: "MyCizim"

// Çift tıklama ile dialog açılır (yazı, boyut, renk, açıklama düzenleme)
isaretleyici.ciftTiklandi();
```

### Sağ Menü Entegrasyonu:
```typescript
// FikoCizimYalin sağ menüsüne otomatik eklendi
{
    simge: "flag",
    baslik: "İşaretleyiciler",
    tiklandi: () => {
        const yeni = this.isaretleyiciler.ekleFikoIsaretci();
        yeni.position.set(0, 0, 0);
    }
}
```

### JSON Formatı:
```json
{
    "type": "FikoCizimYalin",
    "isim": "MyCizim",
    "isaretleyiciler": {
        "type": "Fiko3DIsaretleyiciler",
        "isaretleyiciler": [
            {
                "type": "Fiko3DIsaretleyici",
                "isim": "A",
                "position": [10, 20, 30],
                "yazi": "Nokta A",
                "boyut": 0.3,
                "renk": "#FFFF00",
                "sabitBoyut": true,
                "sabitYon": true
            }
        ]
    }
}
```

### Dialog Özellikleri:
- **Yazı:** İşaretleyici metni (default: parent name)
- **Açıklama:** Uzun açıklama (textarea)
- **Boyut:** Yazı boyutu (slider 0.01-5)
- **Renk:** Renk seçici (color picker)
- **Sabit Boyut:** Kamera zoom'undan bağımsız (checkbox)
- **Ekrana Paralel:** Kamera ile beraber dönme (checkbox)

### Component Yapısı:
- **Fiko3DYaziIsaretleyici:** Ana işaretleyici (seçimli yazı)
  - `point`: THREE.Points (işaretleyici nokta)
  - `line`: LineSegments2 (yukarı doğru çizgi)
  - `yazi3d`: Fiko3DYazi | THREE.Sprite (3D yazı veya Catia stili label)
- **Fiko3DIsaretleyiciler:** Container (liste yönetimi)
- **Fiko3DYazi:** 3D metin rendering (FontLoader + TextGeometry)
- **Helper Metodlar:** `getYazi()`, `setYazi()`, `getRenk()`, `setBoyut()` vb.

### Önemli Metotlar:
- `merkezle()`: Yazıyı merkeze hizalar (yazı/boyut değişince otomatik çağrılır)
- `yaziDegisti()`: Yazı değiştiğinde merkezlemeyi tetikler (component'ten çağrılır)
- `boyutDegisti()`: Boyut değiştiğinde merkezlemeyi tetikler (component'ten çağrılır)
- `ciftTiklandi()`: Dialog Core ile düzenleme dialog'unu açar
- `toJSON()` / `fromJson()`: Serileştirme/deserialize

### Render Önceliği Ayarları (FikoEksen Pattern'i) - Son Güncelleme: 2025-01-01

**Değişiklik:** 3D yazıların her zaman en üstte görünmesi için FikoEksen'in yöntemi uygulandı

**Çözülen Sorun:** 3D yazılar FikoCizimYalin meshlerinin arkasında kalıyordu

**Kod:**
```typescript
// Render order ayarları
this.renderOrder = 999999; // Grup seviyesi (Fiko3DYazi)
this.yaziMesh.renderOrder = 999999999; // Mesh seviyesi
this.line.renderOrder = 999999999; // Çizgi
this.point.renderOrder = 999999999; // Nokta

// Materyal ayarları
materyal: THREE.MeshToonMaterial({
    depthTest: false,  // Derinlik testi yok - her zaman görünür
    depthWrite: false, // Derinlik buffer'a yazmaz
});

// Her frame'de depth buffer'ı temizle
this.onBeforeRender = (renderer: any) => {
    renderer.clearDepth();
};
```

### Bug Fixes (Async Initialization) - Son Güncelleme: 2025-01-01

**Değişiklik:** Async font loading sırasındaki race condition'lar çözüldü

**Dosya:** `Fiko3DYazi.ts`, `Fiko3DYaziIsaretleyici.ts`

**Sorun:** `Fiko3DYazi.hazirla()` async (font loading), ama yazi setter hemen tetikleniyor

**Kod:**
```typescript
// Fiko3DYazi.yaziDegisti()
if (!this.yaziMesh) {
    return;
}

// Fiko3DYazi.renkDegisti()
if (!this.yaziMesh) {
    return;
}

// Fiko3DYaziIsaretleyici.merkezle()
if (!this.yazi3d || !this.yazi3d.yaziMesh) {
    return;
}
```

**Not:** Safety check'ler sayesinde async hazırlık bitmeden işlem yapılmaz, TypeError'lar önlenir.

### Seçimli Yazı Sistemi - Son Güncelleme: 2025-01-01

**Değişiklik:** Kullanıcı isteği üzerine default yazı tipi Sprite olarak değiştirildi

**Neden:** FikoCizimYalin ilk yüklemede Catia stili sprite label eklemesi istendi

**Kod:**
```typescript
// Constructor'da default değiştirildi
this.yaziTipi = 'Sprite'; // Default olarak Sprite (Catia stili)
this.yazi3d = this.createYaziObject(isim || "İşaretleyici");

// Helper metodlar eklendi
getYazi(): string
setYazi(text: string): void
getRenk(): string
setBoyut(boyut: number): void
getSabitBoyut(): boolean
setSabitBoyut(deger: boolean): void
```

**TypeScript Hataları Çözüldü:**
- Union type: `Fiko3DYazi | THREE.Sprite`
- Template binding: Helper metodlarla güvenli erişim
- Component metodları: Tip kontrollü özellik erişimi

**Özellikler:**
- **Default Sprite:** İlk yüklemede Catia stili label (sarı arka plan, siyah çerçeve)
- **Seçimli:** `yaziTipi` ile 3D yazı/Sprite geçişi
- **JSON Destek:** Her iki format da kaydedilebilir/yüklenebilir
- **Dialog Entegrasyon:** Helper metodlarla güvenli özellik yönetimi

**Bug Fix (FikoEksen Hatası):**
- **Sorun:** `Cannot read properties of undefined (reading 'createLabel')` - FikoEksen henüz mevcut değil
- **Çözüm:** Fallback sprite oluşturma metodu eklendi
- **Metod:** `createSpriteLabel()` - Kendi canvas-based sprite üreticimiz
- **Güvenlik:** `this.sahne && this.sahne.eksen && this.sahne.eksen.createLabel` kontrolü

**Sprite ve Çizgi Görünürlük İyileştirmeleri:**
- **Sprite Scale:** 3x büyütüldü (`width * 0.03` instead of `width * 0.01`)
- **Catia Stili Çizgi:** `dashed: true, dashScale: 0.5, dashSize: 0.3`
- **Çizgi Kalınlığı:** `linewidth: 3` piksel
- **Render Önceliği:** Çizgi ve nokta için depth kontrolü
- **Depth Test:** `depthTest: false, depthWrite: false` tüm elemanlar için

**Bug Fix (Renderer State Hatası):**
- **Sorun:** `renderer.state.setDepthTest is not a function` - THREE.js'de bu metod yok
- **Çözüm:** Depth test doğrudan materyal üzerinden ayarlandı
- **Kod:** `(this.line.material as any).depthTest = false`
- **Not:** `onBeforeRender/onAfterRender` kaldırıldı, materyal seviyesinde kontrol

### Sabit Boyutlu Label Sistemi - Son Güncelleme: 2025-01-01

**Değişiklik:** Sprite label'larının kamera zoom'dan bağımsız sabit boyutta kalması sağlandı

**Neden:** Kullanıcı isteği "şimdi label boyutu sabit kalmalı çok küçük kalıyor aynı fikoeksendeki gibi incele ayarla"

**Özellikler:**
- **Sabit Boyut:** `sabitBoyut: boolean = true` property'si eklendi
- **Kamera Dinleyicisi:** Camera zoom değişimlerini dinleyerek sprite scale'ini otomatik ayarlar
- **FikoEksen Pattern:** FikoEksen'deki sabit boyut mantığı ile aynı algoritma kullanılır

**Kod:**
```typescript
// Fiko3DYaziIsaretleyici constructor'da
sabitBoyut: boolean = true;

// createSpriteLabel metodunda - orantılı scale
const scaleFactor = 0.01; // FikoEksen ile aynı scaleFactor
sprite.scale.set(width * scaleFactor, height * scaleFactor, 1);

// Sabit boyut için scale değerlerini sakla
(sprite as any)._originalScaleX = width * scaleFactor;
(sprite as any)._originalScaleY = height * scaleFactor;

// Sabit boyut kontrolü için kamera dinleyicisi
if (this.sabitBoyut && this.sahne?.anaGosterim?.aktifKontrol) {
    this.sahne.anaGosterim.aktifKontrol.addEventListener("change", () => {
        const factor = (this.sahne.anaGosterim.aktifKamera.top - this.sahne.anaGosterim.aktifKamera.bottom) / this.sahne.anaGosterim.aktifKamera.zoom;
        const newScale = factor * 0.1; // FikoEksen ile aynı

        // Orijinal aspect ratio'yu koru
        sprite.scale.set(
            (sprite as any)._originalScaleX * newScale,
            (sprite as any)._originalScaleY * newScale,
            1
        );
    });
}

// Component helper metodları
getSabitBoyut(): boolean {
    if (this.isaretleyici.yaziTipi === 'Sprite') {
        return this.isaretleyici.sabitBoyut;
    }
    return this.isaretleyici.sabitBoyut;
}

setSabitBoyut(deger: boolean): void {
    if (this.isaretleyici.yaziTipi === 'Sprite') {
        this.isaretleyici.sabitBoyut = deger;
    } else {
        this.isaretleyici.sabitBoyut = deger;
    }
}
```

**Dialog Entegrasyonu:**
- **Checkbox:** "Sabit Boyut" checkbox'ı dialog'da mevcut
- **Sprite Kontrolü:** Sadece Sprite tipi için sabit boyut özelliği aktif
- **3D Yazı:** 3D yazı için kendi sabit boyut mekanizması korunur

**Not:** Bu sistem sayesinde sprite label'lar her zaman aynı görsel boyutta kalır, kameranın yakınlaştırmasından etkilenmez.

### Sprite Label Optimizasyonları - Son Güncelleme: 2025-01-01

**Değişiklik:** Sprite label'larının orantısız gözükmesi ve yazıyı uzatması sorunu çözüldü

**Neden:** Kullanıcı geri bildirimi "orantılı gözükmüyor büyük gözüküyor ve yazıı uzatıyor"

**Optimizasyonlar:**
- **Max Genişlik:** Çok uzun yazılar için `maxTextWidth = 200` piksel kısıtlaması
- **Font Boyutu:** 24'ten 18'e düşürüldü, daha kontrollü görünüm
- **Padding:** 8'den 12'ye çıkarıldı, daha iyi okunabilirlik
- **Scale:** FikoEksen ile aynı `scaleFactor = 0.01` kullanıldı
- **Aspect Ratio:** Orijinal en/boy oranı korunarak bozulma engellendi
- **Metin Kesme:** `truncateText()` metodu ile çok uzun yazılar otomatik kısaltılır

**Kod:**
```typescript
// Max boyutlar kısıtlaması - çok uzun yazılar için
const maxTextWidth = 200;
const actualTextWidth = Math.min(textWidth, maxTextWidth);

// Daha orantılı scale - FikoEksen gibi
const scaleFactor = 0.01; // FikoEksen ile aynı scaleFactor
sprite.scale.set(width * scaleFactor, height * scaleFactor, 1);

// Metni belirtilen genişliğe göre kesme
private truncateText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number, ellipsis: string): string {
    if (ctx.measureText(text).width <= maxWidth) {
        return text;
    }

    let truncated = text;
    while (ctx.measureText(truncated + ellipsis).width > maxWidth && truncated.length > 0) {
        truncated = truncated.slice(0, -1);
    }

    return truncated + ellipsis;
}
```

**Not:** Artık sprite label'lar her zaman orantılı görünür, yazılar uzamaz ve FikoEksen label'ları ile tutarlı boyutta olur.

### Ağırlık Merkezi Konumlandırma - Son Güncelleme: 2025-01-01

**Değişiklik:** İşaretleyiciler artık (0,0,0) yerine FikoCizimYalin'ın ağırlık merkezine ekleniyor

**Neden:** Kullanıcı isteği "güzel şimdide konumunu ayarlayalım eklerken fikocizimyalının ağırlık merkezine koysun"

**Özellikler:**
- **Ağırlık Merkezi Hesaplama:** `getAgirlikMerkezi()` metodu eklendi
- **Otomatik Konumlandırma:** İlk işaretleyici ve yeni işaretleyiciler otomatik olarak merkeze yerleştirilir
- **Vertex Ortalaması:** Tüm vertex koordinatlarının ortalaması alınarak merkez hesaplanır

**Kod:**
```typescript
// FikoCizimYalin'a eklendi
private getAgirlikMerkezi(): THREE.Vector3 {
    if (!this.meshGeom || !this.meshGeom.attributes['position']) {
        return new THREE.Vector3(0, 0, 0);
    }

    const positions = this.meshGeom.attributes['position'];
    const vertexCount = positions.count;
    let centerX = 0, centerY = 0, centerZ = 0;

    // Tüm vertex'lerin ortalamasını hesapla
    for (let i = 0; i < vertexCount; i++) {
        centerX += positions.getX(i);
        centerY += positions.getY(i);
        centerZ += positions.getZ(i);
    }

    return new THREE.Vector3(
        centerX / vertexCount,
        centerY / vertexCount,
        centerZ / vertexCount
    );
}

// Constructor'da ilk işaretleyici için
const ilkIsaretleyici = this.isaretleyiciler.ekleFikoIsaretci();
const merkez = this.getAgirlikMerkezi();
ilkIsaretleyici.position.copy(merkez);

// Sağ menüden yeni işaretleyici eklerken
tiklandi: () => {
    const yeniIsaretleyici = this.isaretleyiciler.ekleFikoIsaretci();
    const merkez = this.getAgirlikMerkezi();
    yeniIsaretleyici.position.copy(merkez);
}
```

**Kullanım:**
- **İşaretleyici Oluşturma:** Hem ilk işaretleyici hem de sağ menüden eklenenler otomatik olarak merkeze konumlanır
- **Hassas Konumlandırma:** Vertex koordinatlarının matematiiksel ortalaması kullanılır
- **Performans:** Tek seferde hesaplanan merkez değeri birden fazla işaretleyici için kullanılabilir

**Not:** Artık işaretleyiciler her zaman geometrinin ağırlık merkezinde görünür, bu da daha mantıklı bir konumlandırma sağlar.

### İşaretleyici Sağ Menü Sistemi - Son Güncelleme: 2025-01-01

**Değişiklik:** Fiko3DYaziIsaretleyici'ye özel sağ menü desteği eklendi

**Neden:** Kullanıcı isteği "güzel şimdi sağmenü ekleyelim sprite sağ tıklaynca menü açılsın sagmenüleri incele ve bu bileşeni sağ menü gösterecek şekilde uyarla"

**Özellikler:**
- **Sağ Tık Menüsü:** İşaretleyiciler üzerinde sağ tık ile menü açma
- **Pozisyon Bilgisi:** İşaretleyicinin 3D pozisyonunu gösterme ve clipboard'a kopyalama
- **Gizle/Göster:** İşaretleyici görünürlüğünü toggle etme
- **Yazı Düzenleme:** Mevcut çift tıklama dialog'u ile entegre
- **Kopyalama:** İşaretleyicinin kopyasını oluşturma (pozisyon, yazı ve tüm özellikler ile)
- **Silme:** İşaretleyiciyi tamamen silme

**Sağ Menü Yapısı:**
```typescript
// Fiko3DYaziIsaretleyici constructor'da
this.sagMenu = [
    {
        simge: null,
        baslik: "Gizle / Göster",
        tiklandi: () => {
            this.visible = !this.visible;
            this.sahne.anaGosterim.FikoYT.degisti.emit();
            this.sahne.anaGosterim.ciz();
        }
    },
    {
        simgeYazi: "📍",
        baslik: this.getPozisyonBilgisi(), // "Pozisyon: X:10.00 Y:20.00 Z:30.00"
        tiklandi: () => {
            this.copyTextToClipboard(this.getPozisyonBilgisi());
        }
    },
    {
        tipi: "Ayraç"
    },
    {
        simge: "edit",
        baslik: "Yazıyı Düzenle",
        tiklandi: () => {
            this.ciftTiklandi(); // Mevcut dialog'u aç
        }
    },
    {
        simge: "copy",
        baslik: "İşaretleyiciyi Kopyala",
        tiklandi: () => {
            this.kopyala(); // Tüm özellikleri ile kopyalar
        }
    },
    {
        simge: "trash",
        baslik: "İşaretleyiciyi Sil '" + this.name + "'",
        tiklandi: () => {
            this.sil(); // İşaretleyiciyi siler
        }
    }
];
```

**Yardımcı Metodlar:**
```typescript
/** Pozisyon bilgisi string olarak döndürür */
getPozisyonBilgisi(): string {
    return `Pozisyon: X:${this.position.x.toFixed(2)} Y:${this.position.y.toFixed(2)} Z:${this.position.z.toFixed(2)}`;
}

/** Yazıyı clipboard'a kopyalar */
copyTextToClipboard(text: string): void {
    if (navigator.clipboard) {
        navigator.clipboard.writeText(text).then(() => {
            console.log('Yazı kopyalandı:', text);
        }).catch(err => {
            console.error('Kopyalama hatası:', err);
        });
    } else {
        // Fallback
        const textArea = document.createElement("textarea");
        textArea.value = text;
        document.body.appendChild(textArea);
        textArea.select();
        document.execCommand('copy');
        document.body.removeChild(textArea);
    }
}

/** İşaretleyiciyi kopyalar */
kopyala(): void {
    const parentIsaretleyiciler = this.parent as any;
    if (this.parent && parentIsaretleyiciler.ekleFikoIsaretci) {
        const kopya = parentIsaretleyiciler.ekleFikoIsaretci(this.getYazi() + " Kopya");
        if (kopya) {
            kopya.position.copy(this.position);
            kopya.setYazi(this.getYazi() + " Kopya");

            // 3D yazı özelliklerini kopyala
            if (this.yaziTipi === '3D') {
                const yazi3d = this.yazi3d as Fiko3DYazi;
                const kopyaYazi3d = kopya.yazi3d as Fiko3DYazi;
                if (kopyaYazi3d) {
                    kopyaYazi3d.boyut = yazi3d.boyut;
                    kopyaYazi3d.renk = yazi3d.renk.clone();
                    kopyaYazi3d.sabitBoyut = yazi3d.sabitBoyut;
                    kopyaYazi3d.sabitYon = yazi3d.sabitYon;
                }
            }

            this.sahne.anaGosterim.ciz();
            this.sahne.anaGosterim.FikoYT.degisti.emit();
        }
    }
}

/** İşaretleyiciyi siler */
sil(): void {
    this.parent?.remove(this);
    this.sahne.anaGosterim.ciz();
    this.sahne.anaGosterim.FikoYT.degisti.emit();
}
```

**Kullanım:**
```typescript
// İşaretleyici üzerinde sağ tık → menü seçenekleri
const isaretleyici = cizim.isaretleyiciler.ekleFikoIsaretci("A");
isaretleyici.position.set(10, 20, 30);

// Sağ menü özellikleri:
// - Pozisyon bilgisi otomatik güncellenir
// - Yazı düzenleme için mevcut dialog kullanılır
// - Kopyalama tüm özellikleri ile yapılır
// - Silme işlemi tamamen kaldırır
```

**Entegrasyon:** FikoCizimYalin'ın sağ menüsü ile aynı sistem kullanılır (`FikoSecililer.contextMenu()`) ve tamamen uyumludur.

**Not:** Artık tüm işaretleyiciler sağ tık ile kolayca yönetilebilir, düzenlenebilir ve kopyalanabilir.

### Raycast Önceliklendirme Düzeltmesi - Son Güncelleme: 2025-01-01

**Değişiklik:** FikoCizimYalin'ın raycast'i sağ menü önceliği ile güncellendi

**Neden:** FikoCizimYalin'ın raycast'i child objelerin sağ menülerini engelliyordu

**Sorun:**
- FikoCizimYalin'ın raycast metodu tüm children'leri kontrol ediyordu
- Fiko3DYaziIsaretleyici'nin raycast'i çağrılmıyordu
- Sağ menü açılmıyordu (FikoCizimYalin'ın sagMenu property'si yok)

**Çözüm:**
FikoCizimYalin'ın raycast'i önceliklendirildi:
1. **Önce sagMenu'si olan child'leri kontrol et**
2. **SagMenu varsa** child objenin raycast'ini kullan
3. **SagMenu yoksa** normal FikoCizimYalin raycast'ini kullan

**Kod:**
```typescript
override raycast(raycaster: THREE.Raycaster, intersects: THREE.Intersection[]): void {
    if (!this.visible) return;
    if (this.children.length < 1) return;

    let kesisenler = raycaster.intersectObjects(this.children, true);
    if (kesinenler.length > 0) {
        // Önce sagMenu'si olan child objeleri kontrol et
        const sagMenuObje = kesisenler.find(kesisen =>
            kesisen.object && (kesinen.object as any).sagMenu && Array.isArray((kesinen.object as any).sagMenu)
        );

        if (sagMenuObje) {
            // SagMenu'si olan objenin raycast'ini kullan
            intersects.push({
                object: sagMenuObje.object,
                distance: sagMenuObje.distance,
                point: sagMenuObje.point,
                // ... diğer özellikler
                enUstte: true,
                secilebilir: true,
            });
        } else {
            // SagMenu'si yoksa normal FikoCizimYalin raycast'ini kullan
            intersects.push({
                object: this,
                // ... normal FikoCizimYalin özellikleri
            });
        }
    }
}
```

**Sonuç:**
- ✅ **İşaretleyici sağ menüsü çalışır** - Önceliklendirme sayesinde
- ✅ **FikoCizimYalin raycast'i korunur** - SagMenu'si olmayan objeler için
- ✅ **Hiyerarşik yapı korunur** - Child objeler parent'a göre önceliklidir
- ✅ **Tüm sağ menüler çalışır** - Hem FikoCizimYalin hem de işaretleyiciler

**Not:** Bu düzeltme sayesinde Fiko3DYaziIsaretleyici'lerin sağ menüleri artık düzgün çalışır ve FikoCizimYalin'ın mevcut işlevselliği korunur.

## Ölçü İste Component 3D Format Desteği (Son Güncelleme: 2025-01-03)

**Değişiklik:** Ölçü İste component'ine WRL format desteği ve 3D butonu koşullu gösterme özelliği eklendi

**Dosyalar:**
- [olcu-iste.component.html](../fikoyt-zorro.com/src/service/fikoyt-ui/src/lib/fiko-three-js/Fiko3DComponent/sahne-step-viewer/fikoyt-uyar-3d/components/olcu-iste/olcu-iste.component.html)
- [olcu-iste.component.ts](../fikoyt-zorro.com/src/service/fikoyt-ui/src/lib/fiko-three-js/Fiko3DComponent/sahne-step-viewer/fikoyt-uyar-3d/components/olcu-iste/olcu-iste.component.ts)

**Özellikler:**
- **Desteklenen Formatlar:** `.stp`, `.step`, `.wrl`, `.FikoWrl` için 3D gösterim butonu görünüyor
- **Diğer Formatlar:** `.rar`, `.catpart`, `.catproduct`, `.catdrawing`, `.dxf`, `.3dxml`, `.igs`, `.iges`, `.FikoCizim` sadece listeleniyor, 3D butonu yok
- **WRL Importer:** FikoCizimWRL ve FikoImporterWRL ile tam entegrasyon
- **Koşullu Buton:** `*ngIf="desteklenen3DFormati(item.dosya)"` ile buton gösterimi kontrol edilir

**Kod:**
```typescript
// Desteklenen format kontrolü
desteklenen3DFormati(dosya: File): boolean {
    if (!dosya) return false;
    const extension = dosya.name.toLowerCase();
    return extension.endsWith('.stp') ||
           extension.endsWith('.step') ||
           extension.endsWith('.wrl') ||
           extension.endsWith('.FikoWrl');
}

// 3D yükleme - STEP ve WRL destekli
async goster3D(item: SeciliDosya): Promise<void> {
    const dosyaUzantisi = item.dosya.name.toLowerCase();
    let cizim: any;

    if (dosyaUzantisi.endsWith('.stp') || dosyaUzantisi.endsWith('.step')) {
        // STEP dosyası - FikoImporterSTP
        const importer = new FikoImporterSTP(sahipWrapper as any);
        cizim = await importer.yukle(item.dosya);
    } else if (dosyaUzantisi.endsWith('.wrl') || dosyaUzantisi.endsWith('.fikowrl')) {
        // WRL dosyası - FikoCizimWRL
        const wrlCizim = new FikoCizimWRL(sahneNesnesi, item.dosya.name);
        wrlCizim.dosyadan(item.dosya);
        cizim = wrlCizim;
        await new Promise(resolve => setTimeout(resolve, 500));
    }

    // Sahneye ekle ve ekrana sığdır...
}
```

**HTML Buton Kontrolü:**
```html
<button class="btn-3d"
        *ngIf="desteklenen3DFormati(item.dosya)"
        (click)="goster3D(item)"
        [disabled]="item.yukleniyor"
        <!-- ... diğer özellikler -->
        title="3D çizimi göster">
```

**Sonuç:**
- ✅ Sadece 3D destekli formatlarda buton gösterilir
- ✅ WRL dosyaları başarıyla yüklenir ve görüntülenir
- ✅ Diğer formatlar listelenir ama 3D butonu gösterilmez
- ✅ Tüm mevcut formatlar `accept` attribute'ünde desteklenir

## Grup Seçim Sistemi - Highlight Düzeltmeleri (Son Güncelleme: 2025-11-05)

**Değişiklik:** Import edilen 3D objelerde kesişti yüzey/çizgi/nokta highlight sistemi tamamen düzeltildi

**Sorun:**
- FikoSecimler1 sahneye eklenmemişti → seçim sistemi çalışmıyordu
- Yüzey highlight material'ı çok şeffaf, görünmüyordu (opacity 0.4)
- Çizgi seçiminde renk tutarsızlığı vardı
- Nokta seçiminde boyut sabitlenemiyordu

**Çözümler:**

### 1. Seçim Sistemi Aktivasyonu (FikoSahne.ts:35-38)
```typescript
// ✅ DOĞRU: Seçim grubu oluştur, aktif et ve sahneye ekle
this.secimler = new FikoSecimler1(this, this.children, "*", true, true);
this.secimler.aktif = true;  // Seçim sistemi aktif edildi
this.ekle(this.secimler);    // Seçim grubu sahneye eklendi
```

**Neden:** `aktif = false` kaldı ve sahneye eklenmedi, raycast çalışmıyordu.

### 2. Yüzey Highlight Iyileştirmesi (FikoSecimler1.ts:359-373)
```typescript
const mesh = new THREE.Mesh(
    new THREE.BufferGeometry().setFromPoints(geom),
    new THREE.MeshPhongMaterial({
        color: 0xFF5C5C,
        side: THREE.DoubleSide,    // Her iki tarafı görünür
        transparent: true,
        opacity: 0.6,              // 0.4 → 0.6 (daha görünür)
        polygonOffset: true,
        polygonOffsetFactor: 3,
        polygonOffsetUnits: 3,
        depthTest: true,
        emissive: 0xFF7777         // Emission eklendi - parlak görünüm
    }));
mesh.renderOrder = 2;  // Render sırası - seçimleri önde göster
```

**Iyileştirmeler:**
- `side: THREE.DoubleSide` - Her iki tarafı görünür
- `opacity: 0.6` - Daha az şeffaf
- `emissive: 0xFF7777` - Kırmızı ışın kaynağı - parlak görünüm
- `renderOrder: 2` - Seçimler her zaman üstte

### 3. Çizgi Seçimi Renk Tutarlılığı (FikoSecimler1.ts:401-432)
```typescript
const material = new LineMaterial({
    color: 0xff6600,  // ✅ Renk standardize edildi (turuncu)
    linewidth: 8,     // 5 → 8 (daha kalın)
    vertexColors: true,
});

const mesh1 = new THREE.LineBasicMaterial({
    color: 0xff6600,  // ✅ Ana material ile aynı renk
    side: THREE.DoubleSide,
    transparent: true,
    opacity: 1,
    linewidth: 8,
    polygonOffset: true,
    polygonOffsetFactor: 5,
    polygonOffsetUnits: 5,
    depthTest: false
});
```

**Iyileştirmeler:**
- Renk tutarlılığı: `0xff6600` (turuncu) her ikisinde
- Kalınlık: 8 piksel (daha görünür)
- Vertex colors: RGB (1, 0.6, 0) = turuncu

### 4. Nokta Seçimi Görünürlüğü (FikoSecimler1.ts:449-460)
```typescript
const material = new THREE.PointsMaterial({
    color: 0xffff00,           // ✅ Renk sarı (daha görünür)
    size: 15,                  // 10 → 15 (daha büyük)
    depthTest: false,
    transparent: true,
    polygonOffset: true,
    polygonOffsetFactor: 3,
    polygonOffsetUnits: 3,
    sizeAttenuation: false,    // Boyut kamera yakınlığından etkilenmesin
});
mesh.renderOrder = 2;  // Render sırası
```

**Iyileştirmeler:**
- `color: 0xffff00` - Sarı (daha görünür)
- `size: 15` - Daha büyük
- `sizeAttenuation: false` - Sabit boyut (kameranın yakınlığından etkilenmesin)

### 5. Face Indeks Hatasının Düzeltilmesi (FikoSecimler1.ts:348-349)
```typescript
// ✅ FIX: Face indeksi kontrol et - face.a veya doğrudan faceIndex kullan
const faceIndex = kesisim.face?.a ?? kesisim.faceIndex ?? 0;

let strt = buffers.attributes["grup"].getX(faceIndex);
const syi = buffers.attributes["grup"].getY(faceIndex);
const grp = buffers.attributes["grup"].getZ(faceIndex);
```

**Neden:** `THREE.Intersection`'da `face` property'si `Face3` (eski) veya `undefined` olabilir. Modern THREE.js'te `faceIndex` kullanılır.

**Sonuç:**
- ✅ **Sahneye Eklenen Seçim Sistemi:** Raycast artık çalışıyor
- ✅ **Yüzey Highlight:** Daha parlak ve görünür
- ✅ **Çizgi Seçimi:** Tutarlı renk ve kalınlık
- ✅ **Nokta Seçimi:** Sarı, sabit boyut, görünür
- ✅ **Grup Seçim:** Face indeksi düşürüldü, robust hale getirildi

**Dosyalar:**
- [FikoSahne.ts:35-38](../fikoyt-zorro.com/src/service/fikoyt-ui/src/lib/fiko-three-js/IFikoThreeJS/FikoSahne.ts#L35-L38) - Seçim sistemi aktivasyonu
- [FikoSecimler1.ts:329-465](../fikoyt-zorro.com/src/service/fikoyt-ui/src/lib/fiko-three-js/IFikoThreeJS/FikoSecimler1.ts#L329-L465) - Highlight düzeltmeleri

**Not:** Artık STEP/WRL import edilen 3D modellerinde yüzey/çizgi/nokta seçimleri kusursuz çalışır ve görünürlüğü açık ve nettir.

## Önemli Notlar

⚠️ `matrixAutoUpdate = false` → Manuel `updateMatrix()` gerekli
⚠️ Vertex color + alpha attribute zorunlu
⚠️ NgZone dışında render et
⚠️ OnPush change detection kullan
⚠️ Dispose pattern uygula (memory leak)
⚠️ İşaretleyiciler otomatik olarak FikoCizimYalin'a eklenir (konumlar gibi)
⚠️ Ölçü İste'nde sadece STP ve WRL formatları 3D görüntüleme destekli
⚠️ **Seçim Sistemi:** FikoSecimler1 sahneye eklenmiş ve aktif olmalı (aktif = true)
