import sys, os src = os.path.join(os.path.dirname(__file__), '../../frontend/js/labs/opticsbench.js') with open(src, 'r', encoding='utf-8') as f: content = f.read() # ───────────────────────────────────────────────────────────────────────────── # PATCH: Insert new glue functions after lensPreset, before _lensUpdateUI # ───────────────────────────────────────────────────────────────────────────── MARKER = 'function _lensUpdateUI(info) {' idx = content.find(MARKER) if idx == -1: print('ERROR: _lensUpdateUI not found'); sys.exit(1) NEW_FUNCS = ( '/* ── Lens animated ray + LM controls (Feature 1 & 3) ── */\n' 'function lensToggleLM(on) {\n' ' const sliders = document.getElementById(\'ob-lm-sliders\');\n' ' const fRow = document.querySelector(\'#ob-ctrl-lens .proj-slider-row\');\n' ' if (sliders) sliders.style.display = on ? \'\' : \'none\';\n' ' // hide/show simple f slider\n' ' const fSlRow = document.getElementById(\'sl-lens-f\');\n' ' if (fSlRow && fSlRow.parentElement) fSlRow.parentElement.style.display = on ? \'none\' : \'\';\n' ' if (lensSim) lensSim.setLensMode(!on);\n' ' if (on && lensSim) {\n' ' // sync sliders to current LM params\n' ' const r1 = lensSim._lmR1, r2 = lensSim._lmR2, n = lensSim._lmN;\n' ' const s1 = document.getElementById(\'sl-lm-r1\'), l1 = document.getElementById(\'lm-r1-val\');\n' ' const s2 = document.getElementById(\'sl-lm-r2\'), l2 = document.getElementById(\'lm-r2-val\');\n' ' const sn = document.getElementById(\'sl-lm-n\'), ln = document.getElementById(\'lm-n-val\');\n' ' if (s1) s1.value = r1; if (l1) l1.textContent = r1.toFixed(0);\n' ' if (s2) s2.value = r2; if (l2) l2.textContent = r2.toFixed(0);\n' ' if (sn) sn.value = n; if (ln) ln.textContent = n.toFixed(2);\n' ' }\n' '}\n' '\n' 'function lensLMParam(name, val) {\n' ' const v = parseFloat(val);\n' ' const lblMap = { R1: \'lm-r1-val\', R2: \'lm-r2-val\', n: \'lm-n-val\' };\n' ' const el = document.getElementById(lblMap[name]);\n' ' if (el) el.textContent = name === \'n\' ? v.toFixed(2) : v.toFixed(0);\n' ' if (lensSim) {\n' ' lensSim.setLMParam(name, v);\n' ' // update f display\n' ' const fl = document.getElementById(\'lens-f-val\');\n' ' if (fl) fl.textContent = lensSim.f.toFixed(0);\n' ' }\n' '}\n' '\n' '/* ── Mirror R-slider + parabolic controls (Feature 2) ── */\n' 'function mirrorToggleR(on) {\n' ' const rRow = document.getElementById(\'ob-mirror-R-row\');\n' ' if (rRow) rRow.style.display = on ? \'\' : \'none\';\n' ' const pbtn = document.getElementById(\'mirror-parab-btn\');\n' ' if (pbtn) pbtn.style.display = on ? \'\' : \'none\';\n' ' if (mirrorSim) mirrorSim._useR = !!on;\n' ' if (on && mirrorSim) {\n' ' const sv = document.getElementById(\'sl-mirror-R\');\n' ' const lv = document.getElementById(\'mirror-R-val\');\n' ' if (sv) sv.value = mirrorSim._R;\n' ' if (lv) lv.textContent = mirrorSim._R;\n' ' mirrorSim.setMirrorR(mirrorSim._R);\n' ' } else if (mirrorSim) { mirrorSim.draw(); }\n' '}\n' '\n' 'function mirrorRParam(val) {\n' ' const v = parseFloat(val);\n' ' const el = document.getElementById(\'mirror-R-val\');\n' ' if (el) el.textContent = v;\n' ' if (mirrorSim) mirrorSim.setMirrorR(v);\n' '}\n' '\n' 'function mirrorToggleParabolic(btn) {\n' ' if (!mirrorSim) return;\n' ' mirrorSim._parabolic = !mirrorSim._parabolic;\n' ' if (btn) btn.textContent = mirrorSim._parabolic ? \'Параболическое\' : \'Сферическое\';\n' ' if (btn) btn.style.color = mirrorSim._parabolic ? \'#7BF5A4\' : \'#888\';\n' ' mirrorSim.draw();\n' '}\n' '\n' ) new_content = content[:idx] + NEW_FUNCS + content[idx:] with open(src, 'w', encoding='utf-8') as f: f.write(new_content) print('OK lines:', new_content.count('\n'))