PageSourceSearch

https://cncpromax.com/wp-content/plugins/eye-engraving-tool/assets/js/wood-module.js?ver=1790260826

js cncpromax.com collected 2026-09-24 14:41:22 UTC 10,610 bytes, 235 lines download raw bytes

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.