1// ========================================== 2// ðªµ ÙØØ¯Ø© Ø§ÙØ®Ø´Ø¨ (Wood Module) 3// Ø§ÙØ¥ØµØ¯Ø§Ø±: 53.2 - Ù ØªÙØ§ÙÙ 100% ٠ع Ø¨ÙØ¦Ø© Ø§ÙØ§Ø³ØªÙدÙÙ (Studio Ready) 4// ========================================== 5 6console.log("ð¥ [Laser App]: ت٠تØÙ ÙÙ ÙØØ¯Ø© Ø§ÙØ®Ø´Ø¨ Ø§ÙØ¥ØµØ¯Ø§Ø± V53.2 (Studio Ready)"); 7 8document.addEventListener('DOMContentLoaded', function() { 9 10 // 1. تجÙÙØ² اÙÙØ§ÙÙØ§Ø³ (Ø³ÙØ¹Ù ٠داخ٠اÙÙ Ø³Ø±Ø Ø§ÙÙ Ø±ÙØ²Ù تÙÙØ§Ø¦ÙاÙ) 11 const overlayCanvas = document.getElementById('woodOverlayCanvas'); 12 let wCtx = null; 13 if (overlayCanvas) wCtx = overlayCanvas.getContext('2d'); 14 15 const mockupCanvas = document.getElementById('mockupCanvas'); 16 let mCtx = null; 17 if (mockupCanvas) mCtx = mockupCanvas.getContext('2d'); 18 19 let currentWoodCroppedCanvas = null; 20 21 // 2. Ø¯Ø§ÙØ© استخراج اÙÙ Ø±ÙØ² 22 function getCentroid(pts) { 23 if (!pts || pts.length === 0) return { x: 0, y: 0 }; 24 let sumX = 0, sumY = 0; 25 pts.forEach(p => { sumX += p.x; sumY += p.y; }); 26 return { x: sumX / pts.length, y: sumY / pts.length }; 27 } 28 29 // ========================================== 30 // ð Ø§ÙØ¯Ø§ÙØ© Ø§ÙØ±Ø¦ÙØ³ÙØ© ÙÙ Ø¹Ø§ÙØ¬Ø© Ø§ÙØ®Ø´Ø¨ (اÙÙØµ Ø§ÙØ¯Ø§Ø¦Ø±Ù) 31 // ========================================== 32 function processWoodEngine() { 33 const state = window.LaserApp.state; 34 const imgElement = window.LaserApp.elements.imgElement; 35 const previewBtn = document.getElementById('previewWoodBtn'); 36 37 if (state.currentModule !== 'woodModule') return; 38 39 if (!imgElement || imgElement.width === 0 || !wCtx) { 40 if (previewBtn) previewBtn.disabled = true; 41 return; 42 } 43 44 overlayCanvas.width = imgElement.width; 45 overlayCanvas.height = imgElement.height; 46 47 let frameCenterX = imgElement.width / 2; 48 let frameCenterY = imgElement.height / 2; 49 let baseFaceScalePx = imgElement.width * 0.3; // Ù ÙÙØ§Ø³ Ø§ÙØªØ±Ø§Ø¶Ù 50 51 // إذا ØªÙ Ø§ÙØªØ´Ø§Ù ÙØ¬ÙØ ÙØ±Ùز اÙÙØµ عÙÙÙ 52 if (state.facesCount > 0 && state.leftEye && state.rightEye) { 53 const eyeA = getCentroid(state.leftEye); 54 const eyeB = getCentroid(state.rightEye); 55 const dx = eyeB.x - eyeA.x; 56 const dy = eyeB.y - eyeA.y; 57 baseFaceScalePx = Math.sqrt(dx*dx + dy*dy); 58 frameCenterX = (eyeA.x + eyeB.x) / 2; 59 frameCenterY = (eyeA.y + eyeB.y) / 2; 60 } 61 62 // Ø¬ÙØ¨ إعدادات اÙ٠ستخد٠63 const zoomEl = document.getElementById('woodZoomSlider'); 64 const zoom = zoomEl ? parseFloat(zoomEl.value) || 1.0 : 1.0; 65 66 const pxEl = document.getElementById('woodPanXSlider'); 67 const pyEl = document.getElementById('woodPanYSlider'); 68 const pXPercent = pxEl ? (parseFloat(pxEl.value) || 0) / 100 : 0; 69 const pYPercent = pyEl ? (parseFloat(pyEl.value) || 0) / 100 : 0; 70 71 // تØÙÙ٠اÙÙØ³Ø¨ Ø¥ÙÙ Ø¨ÙØ³Ùات 72 const panX = imgElement.width * pXPercent; 73 const panY = imgElement.height * pYPercent; 74 75 const diaEl = document.getElementById('woodDiameterInput'); 76 const diameterMM = diaEl ? parseFloat(diaEl.value) || 100 : 100; 77 78 // ð Ø§ÙØªØµØÙØ Ø§Ù٠ع٠ار٠ÙÙØ§: ÙØµÙ Ø§ÙØ²ÙÙ Ø¹Ù ØØ¬Ù Ø§ÙØ¥Ø·Ø§Ø± ð 79 // ØØ³Ø§Ø¨ ÙØµÙ اÙÙØ·Ø± Ø¨Ø§ÙØ¨ÙØ³Ù Ø¨ÙØ§Ø¡Ù عÙÙ Ø§ÙØ°Ùاء Ø§ÙØ§ØµØ·Ùاع٠ÙÙØ·Ø± Ø§ÙØ®Ø´Ø¨ ÙÙØ· (بدÙ٠زÙÙ ) 80 const pixelsPerMM = baseFaceScalePx / 25; 81 const radiusPx = (diameterMM / 2) * pixelsPerMM; 82 83 // Ø±Ø³Ù Ø§ÙØ®ÙÙÙØ© اÙ٠عت٠ة 84 wCtx.drawImage(imgElement, 0, 0); 85 wCtx.fillStyle = 'rgba(15, 23, 42, 0.85)'; 86 wCtx.fillRect(0, 0, overlayCanvas.width, overlayCanvas.height); 87 88 // ð Ø±Ø³Ù Ø§ÙÙØµ Ø§ÙØ¯Ø§Ø¦Ø±Ù (دائرة Ø§ÙØ®Ø´Ø¨) ٠ع تطبÙÙ Ø§ÙØ²ÙÙ ð 89 wCtx.save(); 90 wCtx.beginPath(); 91 wCtx.arc(frameCenterX, frameCenterY, radiusPx, 0, Math.PI * 2); 92 wCtx.clip(); // ØªØ«Ø¨ÙØª Ø§ÙØ¥Ø·Ø§Ø± 93 94 // تطبÙÙ Ø§ÙØ²ÙÙ ÙØ§ÙØªØØ±Ù٠عÙÙ Ø§ÙØµÙرة Ø¯Ø§Ø®Ù Ø§ÙØ¥Ø·Ø§Ø± 95 wCtx.translate(frameCenterX + panX, frameCenterY + panY); 96 wCtx.scale(zoom, zoom); 97 wCtx.translate(-frameCenterX, -frameCenterY); 98 99 wCtx.drawImage(imgElement, 0, 0);
100 wCtx.restore(); 101 102 // رس٠إطار ØªÙØ¬ÙÙÙ Ø£ØµÙØ± ثابت 103 wCtx.save(); 104 wCtx.beginPath(); 105 wCtx.arc(frameCenterX, frameCenterY, radiusPx, 0, Math.PI * 2); 106 wCtx.strokeStyle = '#FFD700'; 107 wCtx.lineWidth = 3; 108 wCtx.stroke(); 109 wCtx.restore(); 110 111 // ð ØªØ¬ÙÙØ² Ø§ÙØµÙرة اÙÙÙØ§Ø¦ÙØ© اÙÙ ÙØ·Ùعة (بدÙÙ Ø®ÙÙÙØ©) ÙÙÙÙØ§Ø¹Ø© ð 112 currentWoodCroppedCanvas = document.createElement('canvas'); 113 currentWoodCroppedCanvas.width = radiusPx * 2; 114 currentWoodCroppedCanvas.height = radiusPx * 2; 115 const tCtx = currentWoodCroppedCanvas.getContext('2d'); 116 117 tCtx.beginPath(); 118 tCtx.arc(radiusPx, radiusPx, radiusPx, 0, Math.PI * 2); 119 tCtx.clip(); 120 121 // تطبÙÙ ÙÙØ³ تØÙÙÙØ§Øª اÙ٠صÙÙÙØ© ÙÙØ·Ø§Ø¨Ù Ù Ø³Ø§ØØ© Ø§ÙØ¹Ù Ù 122 tCtx.translate(radiusPx + panX, radiusPx + panY); 123 tCtx.scale(zoom, zoom); 124 tCtx.translate(-frameCenterX, -frameCenterY); 125 126 tCtx.drawImage(imgElement, 0, 0); 127 128 if (previewBtn) previewBtn.disabled = false; 129 130 // ØªÙ Ø±ÙØ± Ø§ÙØµÙرة ÙÙØ¹Ù٠اÙÙ Ø±ÙØ²Ù ÙÙØªØµØ¯Ùر 131 window.LaserApp.state.exportCanvasRaw = currentWoodCroppedCanvas; 132 window.LaserApp.state.exportFilename = 'laser-wood-engraving.png'; 133 134 drawWoodMockup(); 135 } 136 137 // ========================================== 138 // ð Ø±Ø³Ù Ø§ÙÙ Ù٠آب داخ٠اÙÙÙØ§Ø¹Ø© اÙÙ Ø±ÙØ²ÙØ© 139 // ========================================== 140 function drawWoodMockup() { 141 if (window.LaserApp.state.currentModule !== 'woodModule' || !mCtx) return; 142 143 // ð¯ ÙØ±Ø§Ø¡Ø© Ø§ÙØÙ٠اÙÙ Ø®ÙÙ Ø§ÙØ°Ù Ø²Ø±Ø¹ÙØ§Ù ÙÙ Ù ÙÙ wood-ui.php 144 const bgInput = document.getElementById('woodMockupBgImage'); 145 const bgName = bgInput ? bgInput.value : 'wood_slice.png'; // Fallback 146 147 const img = new Image(); 148 const imagesPath = typeof LaserToolConfig !== 'undefined' ? LaserToolConfig.imagesUrl : ''; 149 img.src = imagesPath + bgName; 150 151 img.onload = function() { 152 // Ø±Ø³Ù Ø´Ø±ÙØØ© Ø§ÙØ®Ø´Ø¨ 153 mockupCanvas.width = img.width; 154 mockupCanvas.height = img.height; 155 mCtx.drawImage(img, 0, 0); 156 157 if (!currentWoodCroppedCanvas) return; 158 159 // ضبط ØØ¬Ù ØµÙØ±Ø© اÙÙ Ø³ØªØ®Ø¯Ù ÙØªØªÙاسب ٠ع ÙÙØ¨ Ø§ÙØ´Ø±ÙØØ© Ø§ÙØ®Ø´Ø¨ÙØ© (ØÙاÙÙ 75% Ù Ù Ù Ø³Ø§ØØªÙا) 160 const drawSize = Math.min(img.width, img.height) * 0.75; 161 const destX = (img.width - drawSize) / 2; 162 const destY = (img.height - drawSize) / 2; 163 164 mCtx.save(); 165 // ØªØ£Ø«ÙØ± Ø§ÙØØ±Ù Ø¨Ø§ÙÙÙØ²Ø± (Multiply ÙØ¯Ù ج Ø§ÙØµÙرة ٠ع عرÙÙ Ø§ÙØ®Ø´Ø¨) 166 mCtx.globalCompositeOperation = 'multiply'; 167 mCtx.globalAlpha = 0.85; 168 169 // تطبÙ٠اÙÙÙØ§ØªØ± Ø§ÙØÙØ© ÙÙØ¹Ù٠اÙÙ Ø±ÙØ²Ù 170 if (window.LaserApp.utils && typeof window.LaserApp.utils.getLiveFilter === 'function') { 171 mCtx.filter = window.LaserApp.utils.getLiveFilter(); 172 } 173 174 mCtx.drawImage(currentWoodCroppedCanvas, destX, destY, drawSize, drawSize); 175 mCtx.restore(); 176 }; 177 178 img.onerror = function() { 179 console.error("ð´ [Wood Module]: ÙØ´Ù Ù٠تØÙ ÙÙ ØµÙØ±Ø© Ø§ÙØ®ÙÙÙØ© ٠٠اÙ٠سار: " + img.src); 180 // alert("â ï¸ ÙØ§ ÙÙ ÙÙ Ø§ÙØ¹Ø«Ùر عÙÙ ØµÙØ±Ø© Ø§ÙØ®ÙÙÙØ© (" + bgName + "). ØªØ£ÙØ¯ Ù Ù ÙØ¬ÙØ¯ÙØ§ ÙÙ Ù Ø¬ÙØ¯ images."); 181 }; 182 } 183 184 // ========================================== 185 // ð¡ Ø§Ùاست٠اع ÙÙØ¯Ø§Ø¡Ø§Øª Ø§ÙØ¹Ù٠اÙÙ Ø±ÙØ²Ù 186 // ========================================== 187 document.addEventListener('LaserApp:ImageReady', processWoodEngine); 188 189 document.addEventListener('LaserApp:ModuleChanged', function() { 190 if(window.LaserApp.elements && window.LaserApp.elements.imgElement && window.LaserApp.elements.imgElement.src) { 191 processWoodEngine(); 192 } 193 });
194 195 document.addEventListener('LaserApp:LiveFilterChanged', function() { 196 drawWoodMockup(); 197 }); 198 199 // ð Ø§ÙØ³Ø± ÙÙØ§: Ø§ÙØ§Ø³ØªÙ اع ÙÙØªØ اÙÙÙØ§Ø¹Ø© ÙØ¥Ø¸Ùار اÙÙØ§ÙÙØ§Ø³ ð 200 document.addEventListener('LaserApp:PrepareModal', function() { 201 if (window.LaserApp.state.currentModule === 'woodModule') { 202 const cont = document.getElementById('mockupContainer'); 203 // ÙØ³Ø± Ø§ÙØ¥Ø®Ùاء ÙØ¥Ø¸Ùار اÙÙØ§ÙÙØ§Ø³ 204 if (cont) cont.style.display = 'inline-block'; 205 206 const title = document.getElementById('modalDynamicTitle'); 207 if (title) title.innerText = '٠عاÙÙØ© اÙÙÙØ´ عÙÙ Ø§ÙØ®Ø´Ø¨ ðªµ'; 208 209 drawWoodMockup(); 210 } 211 }); 212 213 // ========================================== 214 // ðï¸ Ù Ø±Ø§ÙØ¨Ø© Ø§ÙØªØºÙÙØ±Ø§Øª Ø§ÙØÙØ© (UI Listeners) 215 // ========================================== 216 const previewBtn = document.getElementById('previewWoodBtn'); 217 if(previewBtn) previewBtn.addEventListener('click', function() { 218 if(window.LaserApp.openModal) window.LaserApp.openModal(); 219 }); 220 221 const inputs = ['woodZoomSlider', 'woodPanXSlider', 'woodPanYSlider', 'woodDiameterInput']; 222 inputs.forEach(id => { 223 const el = document.getElementById(id); 224 if (el) el.addEventListener('input', function(e) { 225 const valSpan = document.getElementById(id.replace('Slider', 'Val')); 226 if (valSpan) { 227 let suf = ''; 228 if (id.includes('Zoom')) suf = 'x'; 229 if (id.includes('Pan')) suf = '%'; 230 valSpan.innerText = parseFloat(e.target.value).toFixed(1).replace('.0', '') + suf; 231 } 232 processWoodEngine(); 233 }); 234 }); 235});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.