import { ChangeDetectorRef, OnDestroy, Input, OnInit } from '@angular/core'; import { Component } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { Subscription } from 'rxjs'; import * as THREE from 'three'; import { FikoYTService } from '@/src/service/fikoyt.service'; import { FikoSecililer } from '../../FikoSecililer'; declare var $: any; @Component({ selector: 'fikoyt-dialog-ters-aci-analizi', standalone: true, imports: [CommonModule, FormsModule], templateUrl: './dialog-ters-aci-analizi.component.html', styleUrls: ['./dialog-ters-aci-analizi.component.scss'] }) export class DialogTersAciAnaliziComponent implements OnInit, OnDestroy { @Input() anaGosterim: any = null; private _pencere: any; @Input() public get pencere(): any { return this._pencere; } public set pencere(v: any) { // Wrapper nesnesinden dialog core instance'ını çıkar (backward compatibility için kontrol) this._pencere = v?.instance || v; // Mevcut subscription'ı temizle (dialog yeniden açıldığında tekrar subscription kurulmasını sağlar) if (this.dialogCloseSub) { this.dialogCloseSub.unsubscribe(); this.dialogCloseSub = null; } // Pencere set edilince event listener'ını kur if (this._pencere && this._pencere.onDialogClose) { this.dialogCloseSub = this._pencere.onDialogClose.subscribe(() => { // Change detection hatasını önlemek için setTimeout ile async yap setTimeout(() => { this.temizle(); }, 0); }); } } id_dialog = "dialogTersAciAnalizi" + this.FikoYT.fikoIdUret(); subsList: Subscription[] = []; haritaOlaylar: Map = new Map(); // Dialog close event subscription private dialogCloseSub: Subscription | null = null; // Ters açı analizi özellikleri shaderMaterial: THREE.ShaderMaterial | null = null; orijinalMaterial: THREE.Material | null = null; aktifMesh: THREE.Mesh | null = null; moldDirection: THREE.Vector3 = new THREE.Vector3(0, 0, 1); // Varsayılan Z ekseni aciEsinigi: number = 3.0; // Kritik açı eşiği (derece) analizAktif: boolean = false; // Yüzey seçimi (FikoKutuk benzeri) yonSecimAktif: boolean = false; fikoSecimYuzey: any = null; secilenFaceNormal: THREE.Vector3 | null = null; // Seçilen yüzeyin normal'ı // Mesh listesi - birden fazla mesh için aktifMeshler: THREE.Mesh[] = []; orijinalMaterials: Map = new Map(); // Açı aralıkları listesi (kullanıcı ekleyip çıkarabilir) // Not: -180° ile +180° arası desteklenir aciAraliklarListesi: { min: number; // Minimum açı (derece) -180 ile +180 max: number; // Maximum açı (derece) -180 ile +180 renk: string; // Hex renk isim: string; // Aralık ismi }[] = [ { min: -180, max: -135, renk: '#ff3333', isim: 'Ters Açı (Negatif)' }, { min: -135, max: -45, renk: '#ffff00', isim: 'Nötr (Negatif)' }, { min: -45, max: -3, renk: '#00ff00', isim: 'İyi (Negatif)' }, { min: -3, max: 0, renk: '#ff0000', isim: 'Kritik (Negatif)' }, { min: 0, max: 3, renk: '#ff0000', isim: 'Kritik (Pozitif)' }, { min: 3, max: 45, renk: '#00ff00', isim: 'İyi (Pozitif)' }, { min: 45, max: 135, renk: '#ffff00', isim: 'Nötr (Pozitif)' }, { min: 135, max: 180, renk: '#ff3333', isim: 'Ters Açı (Pozitif)' } ]; // Gelişmiş ayarlar gelismisAyarlarAcik: boolean = false; yeniAralik = { min: 0, max: 10, renk: '#ffffff', isim: 'Yeni Aralık' }; // Reflect line analizi reflectLineAktif: boolean = false; reflectLinePozisyon: number = 0; // Mold yönü boyunca pozisyon // Undercut tespiti (CNC'de ters kalan alanlar) undercutTespitiAktif: boolean = false; private raycaster: THREE.Raycaster = new THREE.Raycaster(); constructor(public readonly FikoYT:FikoYTService, public readonly CD: ChangeDetectorRef) { } ngOnInit(): void { // Component ilk yüklendiğinde shader'ı oluştur this.shaderOlustur(); } public ac(anaGosterim?: any, pencere?: any) { this.anaGosterim = anaGosterim; this.pencere = pencere; // Shader henüz oluşturulmadıysa oluştur if (!this.shaderMaterial) { this.shaderOlustur(); } // FikoOlcumler (ölçümler) mesh'lerini raycast'ten kapat if (this.anaGosterim?.sahneler?.[0]) { this.anaGosterim.sahneler[0].traverse((child: any) => { if (child instanceof THREE.Mesh && (child.userData?.type === 'FikoOlcumler' || child.parent?.name === 'Ölçümler')) { child.userData.raycastable = false; } }); } var _this = this; $("#" + this.id_dialog).modal({ closable: false, allowMultiple: true, onShow: function () { $("#" + _this.id_dialog).parent().removeClass('dimmer'); $("#" + _this.id_dialog).draggable({ handle: ".baslikMove" }); } }); $("#" + this.id_dialog).modal('show'); this.CD.detectChanges(); } private shaderOlustur() { // Açı aralıklarını sırala ve shader'a gönder const sortedRanges = [...this.aciAraliklarListesi].sort((a, b) => a.min - b.min); // Maximum 10 aralık destekle const maxRanges = 10; const ranges = new Float32Array(maxRanges * 2); // min, max const colors = new Float32Array(maxRanges * 3); // r, g, b for (let i = 0; i < maxRanges; i++) { if (i < sortedRanges.length) { const range = sortedRanges[i]; ranges[i * 2] = range.min * Math.PI / 180; // min (radyan) ranges[i * 2 + 1] = range.max * Math.PI / 180; // max (radyan) const color = new THREE.Color(range.renk); colors[i * 3] = color.r; colors[i * 3 + 1] = color.g; colors[i * 3 + 2] = color.b; } else { // Kullanılmayan aralıklar için geçersiz değer ranges[i * 2] = 999.0; ranges[i * 2 + 1] = 999.0; colors[i * 3] = 0.0; colors[i * 3 + 1] = 0.0; colors[i * 3 + 2] = 0.0; } } this.shaderMaterial = new THREE.ShaderMaterial({ uniforms: { moldDirection: { value: this.moldDirection.clone() }, aciAraliklari: { value: ranges }, renkler: { value: colors }, aralikSayisi: { value: Math.min(sortedRanges.length, maxRanges) }, reflectLineAktif: { value: this.reflectLineAktif }, reflectLinePozisyon: { value: this.reflectLinePozisyon }, undercutTespitiAktif: { value: this.undercutTespitiAktif } }, vertexColors: true, vertexShader: ` varying vec3 vNormal; varying vec3 vPosition; varying vec3 vWorldNormal; varying vec3 vWorldPosition; varying vec3 vVertexColor; attribute vec3 color; void main(){ vNormal = normalize(normalMatrix * normal); vPosition = position; // World normal ve pozisyonunu hesapla vec4 worldPosition = modelMatrix * vec4(position, 1.0); vWorldNormal = normalize(mat3(modelMatrix) * normal); vWorldPosition = worldPosition.xyz; // Vertex color'ı fragment shader'a aktar (undercut tespiti için) vVertexColor = color; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); } `, fragmentShader: ` uniform vec3 moldDirection; uniform float aciAraliklari[20]; // max 10 range * 2 (min, max) uniform float renkler[30]; // max 10 range * 3 (r, g, b) uniform int aralikSayisi; uniform bool reflectLineAktif; uniform float reflectLinePozisyon; uniform bool undercutTespitiAktif; varying vec3 vNormal; varying vec3 vPosition; varying vec3 vWorldNormal; varying vec3 vWorldPosition; varying vec3 vVertexColor; void main(){ vec3 worldNormal = normalize(vWorldNormal); vec3 moldDir = normalize(moldDirection); // Undercut tespiti aktifse vertex color kullan if(undercutTespitiAktif && vVertexColor.r > 0.9) { // Kırmızı vertex color = undercut (CNC'de ters kalan alan) gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); return; } // Reflect line kontrolü - sadece altındaki yüzeyleri boya if(reflectLineAktif) { // Pozisyonun mold yönündeki projeksiyonunu hesapla float positionProjection = dot(vWorldPosition, moldDir); // Reflect line pozisyonunun altında mı kontrol et if(positionProjection > reflectLinePozisyon) { // Reflect line'ın üstündeyse gri renk göster gl_FragColor = vec4(0.5, 0.5, 0.5, 1.0); return; } } // Yüzey normali ile mold yönü arasındaki açı (radyan) float dotProduct = dot(worldNormal, moldDir); float aciRad = acos(clamp(dotProduct, -1.0, 1.0)); // Açıyı -180° ile +180° arasına dönüştür (signed angle) // Cross product ile yönü belirle vec3 crossProd = cross(moldDir, worldNormal); float crossLength = length(crossProd); // Signed açı: dotProduct pozitifse pozitif, negatifse negatif float signedAci = aciRad; if(dotProduct < 0.0) { signedAci = -aciRad; } vec3 color = vec3(0.5, 0.5, 0.5); // Default gri float alpha = 1.0; bool found = false; // Dinamik aralıklardan uygun olanı bul (signed angle kullan) for(int i = 0; i < 10; i++) { if(i >= aralikSayisi) break; float minAci = aciAraliklari[i * 2]; float maxAci = aciAraliklari[i * 2 + 1]; if(signedAci >= minAci && signedAci <= maxAci) { // Bu aralığın rengini al color = vec3( renkler[i * 3], renkler[i * 3 + 1], renkler[i * 3 + 2] ); found = true; break; } } // Aralık bulunamadıysa gradient uygula (komşu aralıklar arası) if(!found && aralikSayisi > 1) { for(int i = 0; i < 9; i++) { if(i >= aralikSayisi - 1) break; float max1 = aciAraliklari[i * 2 + 1]; float min2 = aciAraliklari[(i + 1) * 2]; if(signedAci > max1 && signedAci < min2) { // İki aralık arasında - gradient float t = (signedAci - max1) / (min2 - max1); vec3 color1 = vec3(renkler[i * 3], renkler[i * 3 + 1], renkler[i * 3 + 2]); vec3 color2 = vec3(renkler[(i + 1) * 3], renkler[(i + 1) * 3 + 1], renkler[(i + 1) * 3 + 2]); color = mix(color1, color2, t); break; } } } // Kenar vurgulama için edge detection vec3 dx = dFdx(vNormal); vec3 dy = dFdy(vNormal); float edge = length(dx) + length(dy); edge = clamp(edge * 2.0, 0.0, 1.0); // Kenarları daha koyu yap color = mix(color, color * 0.7, edge * 0.5); gl_FragColor = vec4(color, alpha); } `, side: THREE.DoubleSide, transparent: true }); } public analiziBaslat() { const seciliMeshler = this.seciliMeshleriAl(); if (!this.shaderMaterial) { return; } if (seciliMeshler.length === 0) { return; } // Önceki analizi temizle this.analiziDurdur(); // Her seçili mesh için shader'ı uygula seciliMeshler.forEach((mesh: THREE.Mesh) => { if (mesh instanceof THREE.Mesh && mesh.material) { // Orijinal material'ı sakla this.orijinalMaterials.set(mesh, mesh.material as THREE.Material); // Geometry'de color attribute yoksa default ekle (siyah) const geometry = mesh.geometry as THREE.BufferGeometry; if (!geometry.hasAttribute('color')) { const positionAttr = geometry.getAttribute('position'); if (positionAttr) { const vertexCount = positionAttr.count; const colors = new Float32Array(vertexCount * 3); // Hepsi 0 (siyah) geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); } } // Shader material'ı uygula mesh.material = this.shaderMaterial; // Parent'a analizMeshParent flag'i ekle (ama raycastable bırak) if (mesh.parent) { mesh.parent.userData.analizMeshParent = true; } this.aktifMeshler.push(mesh); } }); this.analizAktif = true; this.CD.detectChanges(); } public analiziDurdur() { // Tüm mesh'lerin orijinal material'larını geri yükle this.aktifMeshler.forEach(mesh => { const orijinalMaterial = this.orijinalMaterials.get(mesh); if (orijinalMaterial) { mesh.material = orijinalMaterial; } // Parent flag'lerini temizle if (mesh.parent && mesh.parent.userData?.analizMeshParent) { delete mesh.parent.userData.analizMeshParent; } }); // Temizle this.aktifMeshler = []; this.orijinalMaterials.clear(); this.analizAktif = false; this.CD.detectChanges(); } private seciliMeshleriAl(): THREE.Mesh[] { const meshler: THREE.Mesh[] = []; if (!this.anaGosterim) { return meshler; } // Seçili mesh'leri al const seciliGruplar = []; // Seçim gruplarını kontrol et if (this.anaGosterim.secililer?.grupSecimlerUzerinde) { seciliGruplar.push(this.anaGosterim.secililer.grupSecimlerUzerinde); } if (this.anaGosterim.secililer?.grupSecimler2Uzerinde) { seciliGruplar.push(this.anaGosterim.secililer.grupSecimler2Uzerinde); } // Seçili gruplardaki mesh'leri topla seciliGruplar.forEach((grup: any) => { if (grup && grup.children) { grup.traverse((child: any) => { if (child instanceof THREE.Mesh && child.material) { meshler.push(child); } }); } }); // Eğer hiç seçim yoksa, sahnedeki tüm mesh'leri al (ölçümler hariç) if (meshler.length === 0 && this.anaGosterim.sahneler?.[0]) { this.anaGosterim.sahneler[0].traverse((child: any) => { if (child instanceof THREE.Mesh && child.visible && child.name !== 'Ölçümler' && child.userData.type !== 'FikoOlcumler') { meshler.push(child); } }); } return meshler; } private seciliMeshAl(): THREE.Mesh | null { const meshler = this.seciliMeshleriAl(); return meshler.length > 0 ? meshler[0] : null; } public moldYonunuGuncelle(eksen: 'x' | 'y' | 'z') { // Eksen seçildiğinde yüzey seçimini temizle this.secilenFaceNormal = null; switch(eksen) { case 'x': this.moldDirection = new THREE.Vector3(1, 0, 0); break; case 'y': this.moldDirection = new THREE.Vector3(0, 1, 0); break; case 'z': this.moldDirection = new THREE.Vector3(0, 0, 1); break; } if (this.shaderMaterial) { this.shaderMaterial.uniforms.moldDirection.value = this.moldDirection.clone(); } // Undercut aktifse yeniden hesapla if (this.undercutTespitiAktif) { this.undercutHesapla(); } this.CD.detectChanges(); } /** * Yön seç butonuna tıklandığında - yüzey seçimini başlat */ public yonSec() { this.yuzeySecimiBaslat(); } /** * Yüzey seçimini sıfırla */ public resetYuzeySecimi() { this.secilenFaceNormal = null; this.moldDirection = new THREE.Vector3(0, 0, 1); // Varsayılan Z if (this.shaderMaterial) { this.shaderMaterial.uniforms.moldDirection.value = this.moldDirection.clone(); } // Yüzey seçimini tekrar başlat this.yuzeySecimiBaslat(); this.CD.detectChanges(); } /** * Yeni açı aralığı ekle */ public aralikEkle() { this.aciAraliklarListesi.push({ min: this.yeniAralik.min, max: this.yeniAralik.max, renk: this.yeniAralik.renk, isim: this.yeniAralik.isim }); // Yeni aralık formunu sıfırla this.yeniAralik = { min: 0, max: 10, renk: '#ffffff', isim: 'Yeni Aralık' }; this.shaderiGuncelle(); } /** * Açı aralığı sil */ public aralikSil(index: number) { this.aciAraliklarListesi.splice(index, 1); this.shaderiGuncelle(); } /** * Aralık değiştiğinde shader'ı güncelle */ public aralikDegisti() { this.shaderiGuncelle(); } /** * Shader uniforms'ları güncelle (analiz aktifken) */ private shaderiGuncelle() { if (!this.shaderMaterial) return; const sortedRanges = [...this.aciAraliklarListesi].sort((a, b) => a.min - b.min); const maxRanges = 10; const ranges = new Float32Array(maxRanges * 2); const colors = new Float32Array(maxRanges * 3); for (let i = 0; i < maxRanges; i++) { if (i < sortedRanges.length) { const range = sortedRanges[i]; ranges[i * 2] = range.min * Math.PI / 180; ranges[i * 2 + 1] = range.max * Math.PI / 180; const color = new THREE.Color(range.renk); colors[i * 3] = color.r; colors[i * 3 + 1] = color.g; colors[i * 3 + 2] = color.b; } else { ranges[i * 2] = 999.0; ranges[i * 2 + 1] = 999.0; colors[i * 3] = 0.0; colors[i * 3 + 1] = 0.0; colors[i * 3 + 2] = 0.0; } } this.shaderMaterial.uniforms.aciAraliklari.value = ranges; this.shaderMaterial.uniforms.renkler.value = colors; this.shaderMaterial.uniforms.aralikSayisi.value = Math.min(sortedRanges.length, maxRanges); this.shaderMaterial.uniforms.reflectLineAktif.value = this.reflectLineAktif; this.shaderMaterial.uniforms.reflectLinePozisyon.value = this.reflectLinePozisyon; this.shaderMaterial.uniforms.undercutTespitiAktif.value = this.undercutTespitiAktif; this.CD.detectChanges(); } /** * Reflect line aktif/pasif toggle */ public reflectLineToggle() { this.reflectLineAktif = !this.reflectLineAktif; // İlk açıldığında mesh'lerin bounding box'ına göre merkez pozisyon hesapla if (this.reflectLineAktif && this.reflectLinePozisyon === 0) { this.reflectLinePozisyonuHesapla(); } this.shaderiGuncelle(); } /** * Reflect line pozisyonunu değiştir */ public reflectLinePozisyonDegisti() { this.shaderiGuncelle(); } /** * Seçili mesh'lerin bounding box'ına göre reflect line pozisyonunu hesapla */ private reflectLinePozisyonuHesapla() { const seciliMeshler = this.seciliMeshleriAl(); if (seciliMeshler.length === 0) return; // Tüm mesh'lerin bounding box'larını birleştir const boundingBox = new THREE.Box3(); seciliMeshler.forEach(mesh => { if (mesh.geometry) { const meshBox = new THREE.Box3().setFromObject(mesh); boundingBox.union(meshBox); } }); // Bounding box merkezinin mold yönündeki projeksiyonunu hesapla const center = new THREE.Vector3(); boundingBox.getCenter(center); this.reflectLinePozisyon = center.dot(this.moldDirection); } /** * Undercut tespiti toggle */ public undercutTespitiToggle() { this.undercutTespitiAktif = !this.undercutTespitiAktif; if (this.undercutTespitiAktif) { // Undercut hesaplama başlat this.undercutHesapla(); } else { // Vertex color'ları temizle this.vertexColorTemizle(); } this.shaderiGuncelle(); } /** * Mesh'lerdeki undercut (ters kalan) alanları tespit et * Her vertex için mold yönünde raycast atarak önünde başka yüzey olup olmadığını kontrol eder */ private undercutHesapla() { const seciliMeshler = this.seciliMeshleriAl(); if (seciliMeshler.length === 0) return; // Her mesh için undercut hesapla seciliMeshler.forEach(mesh => { if (!mesh.geometry) return; const geometry = mesh.geometry as THREE.BufferGeometry; const positionAttr = geometry.getAttribute('position') as THREE.BufferAttribute; const normalAttr = geometry.getAttribute('normal') as THREE.BufferAttribute; if (!positionAttr || !normalAttr) return; const vertexCount = positionAttr.count; const colors = new Float32Array(vertexCount * 3); // Her vertex için kontrol et for (let i = 0; i < vertexCount; i++) { const localPos = new THREE.Vector3( positionAttr.getX(i), positionAttr.getY(i), positionAttr.getZ(i) ); const localNormal = new THREE.Vector3( normalAttr.getX(i), normalAttr.getY(i), normalAttr.getZ(i) ); // World space'e dönüştür const worldPos = localPos.clone().applyMatrix4(mesh.matrixWorld); const worldNormal = localNormal.clone().applyMatrix3( new THREE.Matrix3().getNormalMatrix(mesh.matrixWorld) ).normalize(); // Vertex normal'ı ile mold yönü arasındaki açı const dotProduct = worldNormal.dot(this.moldDirection); // Ters açılı yüzeyler (undercut potansiyeli) if (dotProduct < 0) { // Mold yönünde raycast at - biraz offset ekle const rayOrigin = worldPos.clone().add( this.moldDirection.clone().multiplyScalar(0.001) ); this.raycaster.set(rayOrigin, this.moldDirection.clone().normalize()); this.raycaster.far = 10000; // Maksimum mesafe // Diğer mesh'lere raycast at const intersects = this.raycaster.intersectObjects(seciliMeshler, false); // Eğer önünde başka bir yüzey varsa -> undercut if (intersects.length > 0) { // İlk kesişim kendi mesh'i olabilir, kontrol et const validIntersect = intersects.find(hit => hit.distance > 0.01); if (validIntersect) { // Undercut tespit edildi - kırmızı işaretle colors[i * 3] = 1.0; // R colors[i * 3 + 1] = 0.0; // G colors[i * 3 + 2] = 0.0; // B } else { // Normal vertex - beyaz colors[i * 3] = 0.0; colors[i * 3 + 1] = 0.0; colors[i * 3 + 2] = 0.0; } } else { // Önünde yüzey yok - normal colors[i * 3] = 0.0; colors[i * 3 + 1] = 0.0; colors[i * 3 + 2] = 0.0; } } else { // Pozitif açılı yüzey - normal colors[i * 3] = 0.0; colors[i * 3 + 1] = 0.0; colors[i * 3 + 2] = 0.0; } } // Vertex color attribute ekle (THREE.js standardı 'color') geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); geometry.attributes.color.needsUpdate = true; }); this.CD.detectChanges(); } /** * Vertex color'ları temizle (undercut kapatıldığında) */ private vertexColorTemizle() { const seciliMeshler = this.seciliMeshleriAl(); seciliMeshler.forEach(mesh => { if (mesh.geometry) { const geometry = mesh.geometry as THREE.BufferGeometry; if (geometry.hasAttribute('color')) { geometry.deleteAttribute('color'); } } }); } /** * Yüzey seçimini başlat - Kullanıcı mesh'e tıklayınca o yüzeyin normal'ını mold yönü olarak ayarla */ private yuzeySecimiBaslat() { if (!this.anaGosterim) { return; } // Zaten aktifse tekrar başlatma if (this.fikoSecimYuzey?.aktif) { return; } const seciliMeshler = this.seciliMeshleriAl(); if (seciliMeshler.length === 0) { return; } // Önceki FikoSecililer varsa temizle if (this.fikoSecimYuzey) { this.fikoSecimYuzey.bitir(); this.fikoSecimYuzey = null; } // FikoSecililer oluştur this.fikoSecimYuzey = new FikoSecililer(this.anaGosterim); // Seçili mesh'leri rayObje olarak ayarla this.fikoSecimYuzey.rayObje = seciliMeshler; this.fikoSecimYuzey.filitreler = ["Mesh"]; // Raycast params ayarla - face-level raycast için this.fikoSecimYuzey.raycaster.params.Mesh = { threshold: 0 }; // Tıklama event'ini dinle const sub = this.fikoSecimYuzey.tiklandi.subscribe((kesisim: any) => { // Kesişim noktasındaki face normal'ı al let faceNormal: THREE.Vector3 | null = null; const actualMesh = kesisim?.altObje || kesisim?.object; const mesh = actualMesh as THREE.Mesh; if (kesisim?.face?.normal) { // Eski API: face.normal (deprecated) const localNormal = kesisim.face.normal.clone(); faceNormal = localNormal.applyMatrix3( new THREE.Matrix3().getNormalMatrix(mesh.matrixWorld) ).normalize(); } else if (mesh.geometry && kesisim.faceIndex !== undefined) { // Yeni API: geometry normal'ından hesapla const geometry = mesh.geometry as THREE.BufferGeometry; const normalAttr = geometry.getAttribute('normal') as THREE.BufferAttribute; if (normalAttr) { const faceIndex = kesisim.faceIndex; const normalArray = new Float32Array(3); normalArray[0] = normalAttr.getX(faceIndex); normalArray[1] = normalAttr.getY(faceIndex); normalArray[2] = normalAttr.getZ(faceIndex); const localNormal = new THREE.Vector3(normalArray[0], normalArray[1], normalArray[2]); faceNormal = localNormal.applyMatrix3( new THREE.Matrix3().getNormalMatrix(mesh.matrixWorld) ).normalize(); } } if (faceNormal) { // Seçilen yüzeyi kaydet (FikoKutuk benzeri) this.secilenFaceNormal = faceNormal.clone(); // Mold yönünü güncelle this.moldDirection = faceNormal.clone(); if (this.shaderMaterial) { this.shaderMaterial.uniforms.moldDirection.value = this.moldDirection.clone(); } // Undercut aktifse yeniden hesapla if (this.undercutTespitiAktif) { this.undercutHesapla(); } // Yüzey seçimini durdur this.yuzeySecimiDurdur(); this.CD.detectChanges(); } }); this.subsList.push(sub); // Aktifleştir (FikoKutuk'taki gibi) this.fikoSecimYuzey.baslat(); this.fikoSecimYuzey.aktif = true; this.yonSecimAktif = true; this.CD.detectChanges(); } /** * Yüzey seçimini durdur */ private yuzeySecimiDurdur() { if (this.fikoSecimYuzey) { this.fikoSecimYuzey.aktif = false; // FikoKutuk'taki gibi this.fikoSecimYuzey.bitir(); this.fikoSecimYuzey = null; } this.yonSecimAktif = false; this.CD.detectChanges(); } /** * Dialog kapatıldığında analizi ve event'leri temizler */ private temizle() { // Analizi durdur this.analiziDurdur(); // Yüzey seçimini durdur this.yuzeySecimiDurdur(); // FikoOlcumler'leri geri raycast'e aç (dialog kapalı olunca ölçümler tıklanabilir) if (this.anaGosterim?.sahneler?.[0]) { this.anaGosterim.sahneler[0].traverse((child: any) => { if (child instanceof THREE.Mesh && (child.userData?.type === 'FikoOlcumler' || child.parent?.name === 'Ölçümler')) { child.userData.raycastable = true; } }); } } public kapa() { this.subsList.forEach(element => { if (element && element.unsubscribe) { element.unsubscribe(); } }); // Dialog close subscription'ı temizle if (this.dialogCloseSub) { this.dialogCloseSub.unsubscribe(); this.dialogCloseSub = null; } this.haritaOlaylar.forEach((value: any, key: string) => { if (value && typeof value === 'function') { value(); } }); // Analizi temizle this.temizle(); // FikoOlcumler'leri güvenli olması için geri aç if (this.anaGosterim?.sahneler?.[0]) { this.anaGosterim.sahneler[0].traverse((child: any) => { if (child instanceof THREE.Mesh && (child.userData?.type === 'FikoOlcumler' || child.parent?.name === 'Ölçümler')) { child.userData.raycastable = true; } }); } // Dialog core'ın kapa metodunu çağır (pencere zaten instance'dır) if (this.pencere && typeof this.pencere.kapa === 'function') { try { this.pencere.kapa(); } catch (error) { // Error handling } } // Her durumda modal hide'ı çağır (fallback) try { $("#" + this.id_dialog).modal('hide'); } catch (error) { // Error handling } } ngOnDestroy(): void { this.analiziDurdur(); // Dialog close subscription'ı temizle if (this.dialogCloseSub) { this.dialogCloseSub.unsubscribe(); this.dialogCloseSub = null; } // Shader material'ı temizle if (this.shaderMaterial) { this.shaderMaterial.dispose(); this.shaderMaterial = null; } // Orijinal materials'ı temizle this.orijinalMaterials.clear(); this.aktifMeshler = []; $("#" + this.id_dialog).remove(); this.subsList.forEach(element => { if (element && element.unsubscribe) { element.unsubscribe(); } }); this.haritaOlaylar.forEach((value: any, key: string) => { if (value && typeof value === 'function') { value(); } }); this.haritaOlaylar.clear(); // Özellikleri temizle this.anaGosterim = null; this.pencere = null; this.secilenFaceNormal = null; } }