io.github.emircbngl/blender-optics-simulator
Build, align & render a real optical bench in Blender; drivable by an AI agent over MCP.
Versions
0.24.2latestTools 79
capabilities READ ME FIRST. Self-describing manifest for an agent that just connected: the scope, how the bench works, the tools grouped by purpose (the READ/inspect tools are 'your eyes' — call them, never guess), the common workflows, the example library, and the gotchas that bite. Nothing is mutated. Pair with get_state() (the live scene) and diagnose() (advisory corrections you weigh against user intent before acting).
get_state Full optical state: every element's world center, ports (world position + normal), mount/DOFs, mechanics, params, misalignment, plus the traced beam path.
build_example Build a canonical setup: mach_zehnder | michelson | hong_ou_mandel | bell | adaptive_optics | newton_rings.
trace_beam Re-trace the beam path. mode: '' = current, or AUTO / ORDER.
path_statistics Read cumulative source-to-detector route lengths for each arrival. Returns geometric_length_mm and the tracer's phase_opl_mm separately. Phase OPL is not group delay: group index and GDD are not modeled. Empty detector = all terminals.
diagnose Run the bench-intelligence error-detection gates over the current trace: beam_clipped (hard miss), vignetting (Gaussian wing clip), dark_detector / orphan_source, energy_violation (per-node + global power budget), and mount_limit (DOF range exhausted). READ-ONLY -- the beam trace is unaffected. Returns the diagnostics list ({kind, element, detail, severity}) + BAD/WARN counts.
propose_corrections ADVISORY correction proposals: diagnose() that also suggests a FIX for each issue, but applies NOTHING. Each proposal carries {issue, element, detail, severity, suggested_fix, tool, maybe_intentional_if, fault_confidence, advisory}. These are FEEDBACK, not commands -- weigh each against USER INTENT (did they ask for this on purpose? -- see 'maybe_intentional_if') and choose REFUSE / PARTIAL / ACCEPT. fault_confidence is how likely a genuine fault vs a design choice (crossed_polarizer ~0.3 = usually an intentional extinction; energy_violation ~0.9 = config bug). The honest default is to SURFACE, not silently fix. READ-ONLY -- the trace is byte-identical.
inspect_beam The full OPTICAL STATE of the beam where it reaches `element` (or the most-lit element if blank) -- the AI's numeric eyes on the beam, so you READ the physics instead of eyeballing a render: power, wavelength, Gaussian radius w + wavefront curvature R(z) (collimated/diverging/converging), beam quality M^2, far-field divergence + reconstructed waist, polarization (Stokes -> kind / azimuth / ellipticity / DOP), coherence length, and how many beams arrive. READ-ONLY -- byte-identical.
inspect_element What an element DOES to the beam + what it is DOING right now: its optical role, the type-relevant params (focal_length, retardance, split_ratio, reflectivity, coating, nl_process, ...), and the LIVE trace -- incoming power and the OUTGOING children by kind (TRANSMIT / REFLECT / SPLIT_R / SHG / ...) with power + wavelength + throughput, so you SEE the actual effect (split 50/50, reflected 98%, converted 532 nm, clipped to Y%). READ-ONLY -- the trace is byte-identical.
inspect_all The whole-bench INSPECTION DASHBOARD -- ONE call that chains inspect_beam + inspect_element for EVERY optical element instead of N separate calls. Each row: {name, type, role, in_power, throughput, w_mm, R_mm, curvature, m2, divergence_mrad, polarization, n_beams, outputs}, plus a bench summary {n_elements, worst_diagnostic, issues}. The single-glance numeric overview of the entire scene. READ-ONLY; byte-identical.
detect_phenomena ADVISORY: the recognized optical PHENOMENA whose conditions the current trace MEETS -- two-beam interference, off-axis hologram recording (carrier fringe spacing Lambda = lambda/(2 sin(theta/2))), and more as added. READ-ONLY: the sim FLAGS that the geometry/coherence conditions are satisfied (e.g. "a reference + object beam cross at 8 deg on the camera -> off-axis hologram"); it does not auto-produce anything (the same surface-don't-act stance as diagnose). {ok, phenomena, count}.
produce_phenomenon ADVISORY emergence -- PRODUCE a phenomenon detect_phenomena flagged (the interferogram, or the recorded + reconstructed off-axis hologram), off-trace + byte-identical. Two-stage like propose_corrections: accept=False (default) is a DRY-RUN returning {would_produce, what_it_would_compute, maybe_not_wanted_if} and produces NOTHING; weigh the user's intent (refuse / partial / accept) and only then re-call with accept=True for the actual {produced:{...metrics, oracle...}, png}. png=True writes a PNG. The owner principle: conditions met -> the phenomenon CAN be produced, but never silently -- it's an intent-judged act. (phenomenon/where empty -> the first detected phenomenon.) Never mutates the scene/trace.
design_telescope Design an afocal two-lens telescope / beam-expander (PURE -- no scene mutation). Given objective focal f1 and eyepiece/relay focal f2, returns the afocal lens separation sep=f1+f2, the transverse magnification -f2/f1, the angular magnification -f1/f2, the beam expansion |f2/f1|, the type (keplerian if both focals positive, else galilean), and the composed afocal ABCD matrix [[-f2/f1, f1+f2], [0, -f1/f2]] (C=0 -> a collimated input exits collimated when the lenses are sep apart). Verified against the physics oracle.
optics_calc Pure optics formula calculator -- no scene needed. Covers Brewster / critical angle, Sellmeier n(lambda[,T]), thin- & thick-lens, cavity finesse / FSR / stability, grating angle / resolving power, AR quarter-wave coating, fiber NA / V / mode-count, AOM deflection, Pockels Vpi, photon energy, coherence length, Gaussian divergence. Call with quantity="" to LIST every calculator and its arguments; otherwise pass the quantity plus its parameters in `params`, e.g. optics_calc("brewster_angle", {"n1": 1.0, "n2": 1.5}) or optics_calc("sellmeier_n", {"wl_nm": 633, "glass": "N-SF11"}).
import_glass Add a glass to the user catalog from refractiveindex.info Sellmeier coefficients. Fetch the RII YAML's `coefficients` ([c0, B1, C1, B2, C2, B3, C3]) and `type` (formula 1 or 2), then call this: formula 1 -> the C entries are resonance wavelengths (squared automatically); formula 2 -> already squared. c0 must be 0; up to 3 poles. Pass ref_n (the published index at ref_wl_nm) to validate the conversion before it is accepted. After import, sellmeier_n('<name>', ...) and the glass dropdowns see it. E.g. import_glass("MY-N-SF6", [0, 1.77931763, 0.0133714182, 0.338149866, 0.0617533621, 2.08734474, 174.01759], 2, 587.56, 1.80518).
wave_psf Real diffraction PSF of a circular aperture via Fourier optics (PSF = |FFT(pupil*exp(i*2*pi*W))|^2) -- the OPT-IN wave layer. The live ray + Gaussian trace cannot diffract; this can. Returns Strehl, Airy radius (1.22*lambda*F#), first dark ring, FWHM, MTF cutoff (1/(lambda*F#)), and the encircled energy in the first ring (83.8% when ideal). `defocus_waves` adds RMS Noll-Z4 defocus (Strehl then matches the Marechal exp(-(2*pi*rms)^2)). png=True saves a log-scaled PSF image. E.g. wave_psf(550, 8, 25.4) -> Strehl 1, Airy 5.37 um; wave_psf(550, 8, 25.4, defocus_waves=0.0714) -> Strehl ~0.82.
aberrated_psf Diffraction PSF aberrated by a ZERNIKE wavefront -- the general (any-mode) form of wave_psf's defocus knob. Either a single named ``mode`` in {defocus, astigmatism, coma, trefoil, spherical} at amplitude_waves RMS, OR a full Noll-indexed ``zernike_waves`` list (j=1..15, in waves; index 0 = piston). Returns {strehl, rms_wavefront_waves, airy_radius_um, ...}; for a small aberration the Strehl follows Marechal exp(-(2*pi*rms)^2). png=True saves the PSF. Off-trace, byte-identical. E.g. aberrated_psf('astigmatism', 0.05) -> strehl ~ 0.906; aberrated_psf('coma', 0.1) -> strehl ~ 0.67.
propagate_field Free-space ANGULAR-SPECTRUM propagation of a sampled scalar field over dz_mm -- the OPT-IN field layer, separate from the live geometric+Gaussian trace (which cannot diffract BETWEEN planes). Source: a Gaussian waist `w0_mm` OR a clear circular `aperture_mm`. Returns the propagated beam metrics (w_2sigma_mm, peak, total_power, centroid, fresnel_number, w_analytic_mm); png=True saves an intensity image. Reproduces the Gaussian w(z) closed form and is reversible (+dz then -dz = digital-hologram back-propagation). The primitive behind multi-plane Fresnel; FDTD/NLSE stay out of scope (see capabilities()['scope_map']). E.g. propagate_field(632.8, w0_mm=0.3, dz_mm=300) -> w_2sigma ~ w_analytic ~ 0.361 mm.
propagate_chain March a sampled field through a SEQUENCE of planes -- the POPPY-style multi-plane OpticalSystem that CHAINS the single-step angular-spectrum propagator (propagate_field runs it once). `steps` is a list of [kind, value]: ["prop", dz_mm] free-space propagation, ["aperture", D_mm] hard circular aperture, ["lens", f_mm] thin lens. Source: a Gaussian w0_mm OR a clear aperture_mm. Returns {final beam metrics, z_total_mm, per-step trace}; png=True saves the last plane. E.g. propagate_chain([["aperture",3],["lens",200], ["prop",200]], aperture_mm=3) focuses at z=f=200mm. Off-trace / byte-identical. NEAR-field only -- a tight focus or Fraunhofer far field is better via direct FFT (wave_psf / slit_diffraction).
gerchberg_saxton Gerchberg-Saxton phase retrieval / CGH design -- the iterative Fourier-transform algorithm that finds a source-plane PHASE whose far-field |FFT| matches a target pattern. Alternates amplitude constraints in the source + far-field planes; the far-field error is MONOTONE NON-INCREASING (the GS guarantee). target in {ring, double, tophat, spot} (canonical beam-shaping / computer-generated-hologram patterns). Returns {ok, correlation (achieved-vs-target 0..1), final_error, initial_error, monotone, n_iter}; png=True saves the achieved far-field + the recovered phase mask. Off-trace; live trace byte-identical.
fienup_phase_retrieval Fienup HIO phase retrieval -- recover a hidden REAL, non-negative object from its diffraction INTENSITY (|FFT|^2) ALONE plus a real-space SUPPORT mask: the genuine 'phase problem' of coherent diffractive imaging / crystallography. Where Gerchberg-Saxton knows the amplitude in BOTH planes (CGH design), here the object is UNKNOWN -- only its support is. The Hybrid-Input-Output feedback (beta ~ 0.9) escapes the stagnation / twin-image traps that pure error-reduction falls into. obj in {dots, ell, tri} (asymmetric objects in an off-centre support that breaks the conjugate twin). Returns {ok, correlation (recovered-vs-truth, invariant to the inherent translation + twin ambiguities, 0..1), final_error, initial_error, n_iter, support_frac}; png=True saves truth / recovery / Fourier-error curve. Off-trace; live trace byte-identical.
tem_mode Laser cavity TRANSVERSE mode pattern -- the TEM_mn / donut shapes a resonator supports. family in {HG (Hermite-Gaussian TEM_ij, rectangular: (i+1)(j+1) bright lobes), LG (Laguerre-Gaussian LG_{p=i, l=j}, cylindrical: a DONUT with an on-axis null + an exp(i l phi) phase vortex carrying orbital angular momentum l*hbar when l!=0)}. Returns {ok, family, indices, n_lobes, gouy_order (i+j+1 for HG, 2p+|l|+1 for LG), on_axis_intensity_frac, x2_over_w2 (= (2i+1)/4 for HG_i0), oam_winding_turns (LG)}. png=True saves the intensity (+ the phase vortex for LG). Off-trace; live trace byte-identical.
newton_rings The 2-D NEWTON'S-RINGS reflected pattern of a plano-convex surface (radius of curvature R) resting on a flat: the quadratic air gap makes the two reflections interfere as I(r)=sin^2(pi r^2/(lambda R)) -- a central DARK spot + dark rings at r_m = sqrt(m lambda R). Returns {ok, radius_of_curvature_mm, wavelength_nm, n_rings, field_mm, dark_ring_radii_mm, central_intensity (~0), oracle}; png=True saves the ring image. Off-trace; live trace byte-identical.
speckle_pattern Fully-developed LASER SPECKLE from a diffuse scatterer: a coherent beam illuminating a rough surface (diameter `diam_mm`) picks up a uniform random phase, and after `dz_mm` of propagation the intensity is a grainy speckle pattern. Returns {ok, contrast (sigma_I/<I> -> 1 for one frame), predicted_contrast (1/sqrt(n_avg)), var_over_mean2 (-> 1, the exponential-PDF signature), frac_above_mean (-> e^-1=0.368), speckle_size_mm, predicted_speckle_size_mm (lambda*z/D), n_avg, oracle}. n_avg>1 averages independent frames -> contrast falls as 1/sqrt(N) (the speckle-suppression law). png=True saves the pattern + its intensity histogram vs the exponential PDF. Off-trace; live trace byte-identical.
caustic_pattern The coffee-cup CAUSTIC: parallel rays reflecting off the concave inner wall of a circular mirror (radius `mirror_radius_mm`) pile up on a NEPHROID -- the bright cusped curve seen in a coffee mug. Traced from first principles (reflect a fan of rays, accumulate the ray-density) and reported against the exact analytic envelope. Returns {ok, caustic_type ('nephroid'), cusp_distance_mm (= R/2, the mirror paraxial focus), mirror_focal_mm, density_on_envelope_ratio (caustic brightness vs background, >>1), brightest_point_mm (~the cusp), n_rays, oracle}. png=True saves the ray-density image with the analytic nephroid + mirror circle overlaid. Off-trace; live trace byte-identical.
reset_mount RE-HOME a mount: zero every adjustment DOF back to its home position WITHOUT touching the stored base pose -- the lab move when a knob is wound so far off that the beam is lost and align_element is blind (a fully-dropped beam gives it no gradient). Dark-port recovery: diagnose() names the dead mount -> reset_mount(it) -> align_element(it). Returns {ok, name, zeroed, segments}.
set_dof TURN ONE MOUNT KNOB explicitly: set an adjustment DOF of element `name` to `value`, or move it by `steps` of its own step size. `dof` is the DOF index or kind ('TIP', 'TILT', 'ROT', 'TRANS_X/Y/Z'); read the element's mount.dofs in get_state() first. Units: degrees for rotation, mm for translation. Pass exactly one of value / steps. Clamped to the DOF's [min, max] (`clamped` reports it); the pose is recomposed (a hand move is kept) and the bench re-traced. Returns {ok, name, dof, index, unit, requested, current, clamped, min, max, step, segments}.
build_bench Compile a DECLARATIVE bench spec into a full built + traced + diagnosed bench in ONE call -- the alternative to hand-sequencing many add_component/place_relative calls. spec = {name, elements: [{name, type, at:[x,y,z] | after:{of, along?, distance}, direction:[..], out?:[..] (dual-port mirror/beamsplitter/grating), params:{...}}], mounts?:{name: preset}}. 18 element types (source, mirror, beamsplitter, grating, lens, window, waveplate, aperture, objective, prism, polarizer, pinhole, slit, knife_edge, isolator, crystal, fiber_collimator, detector). Compilation is ALL-OR-NOTHING: types/placement-references/params are validated against the real builder signatures BEFORE anything is built (unknown entries fail with the valid list), so a bad spec never half-mutates the scene. Returns {ok, built, collection, elements, segments, diagnostics_counts}.
material_tables Machine-readable MATERIAL/REFERENCE tables (also the optics://tables/materials resource): glasses {n_d, dndt, range_um}, NL crystals {deff, dk/dT, pm_temp, has_sellmeier_oe}, biaxial/IR/metal/detector-QE lists, and the Noll Zernike j->name map. Computed LIVE from the add-on's sourced physics data -- use this instead of guessing which materials exist. READ-ONLY.
gpu_status Report (or set) the OPT-IN GPU backend for the off-trace FFT field engine. enable=None reports; 'auto'/'cupy'/'mlx' turns it on (needs the library + hardware -- owner-run); 'off' reverts to NumPy. Returns {ok, active_backend, available:{cupy,mlx}, default_dtype, note}. NumPy default is byte-identical to the CPU path; complex128 on GPU matches the oracle exactly, complex64 is the fast path (~1e-6 deviation -- the angular-spectrum H phase is kept in float64). Off-trace; the live ray trace is unaffected.
quantum_stats QUANTUM photon-statistics observables (off-trace; verified analytic core + a QuTiP scaffold). observable: 'g2' (g^(2)(0) of state in {coherent=1, thermal=2, single_photon=0, fock n=1-1/n}), 'hom' (Hong-Ou-Mandel two-photon dip at indistinguishability -- coincidence (1-V)/2, 0 for perfect overlap), 'squeezing' (squeezed-quadrature variance at squeeze_db, sub-shot-noise), 'spdc' (the chi2 down-conversion g2: heralded -> 0, single arm -> 2), 'full_state' (QuTiP-backed; closed-form fallback if qutip absent). Off-trace; live trace byte-identical.
spatial_filter 4f FOURIER-PLANE spatial filtering (the Abbe-Porter experiment / coherent optical image processing): FFT a canonical object, apply a Fourier-plane mask, IFFT back. obj in {grating, edge, phase}; kind in {lowpass (smooths), highpass (edge enhancement / removes the DC background), phase_contrast (Zernike: a phase dot on the zero order makes a PURE-PHASE object visible in intensity)}. Returns {ok, input/output intensity std, output_mean_amp, contrast_ratio}; png=True saves input vs filtered. Off-trace, byte-identical.
slit_diffraction Single / double / N-slit FRAUNHOFER diffraction by FFT, validated against the textbook closed forms -- the OPT-IN field layer (the geometric trace only CLIPS a beam at a slit; it cannot diffract). n_slits=1 -> single slit (sinc^2, first min at sin(theta)=lambda/width); 2 -> Young's double slit (sinc^2 envelope x cos^2, fringes spaced lambda/sep); N -> a coarse grating (orders at sin(theta)=m*lambda/sep). Returns {rms_vs_analytic (~1e-3), first_min_sin_theta/theory (single), fringe_spacing_sin_theta/theory (multi)}. Off-trace; live trace byte-identical. E.g. slit_diffraction(100, 2, sep_um=500) -> Young's fringes.
talbot_effect TALBOT self-imaging of a periodic grating via angular-spectrum propagation -- the OPT-IN field layer. A grating reproduces ITSELF at the Talbot distance z_T = 2*period^2/lambda, a half-period-shifted copy at z_T/2, and no image at z_T/4. Returns {talbot_distance_mm, self_image_corr (~1 at z_T), half_talbot_shift_corr (~1 at z_T/2 vs a d/2 shift), quarter_corr (~0)}. png=True saves the Talbot carpet. Off-trace; live trace byte-identical. E.g. talbot_effect(100, 632.8) -> z_T ~ 31.6 mm, self_image_corr ~ 0.93.
turbulence_screen Generate a DENSE 2D Kolmogorov/von-Karman atmospheric PHASE SCREEN [radians] (FT method + sub-harmonics) and validate it -- the off-trace turbulence layer the MODAL AO channel (ao_kolmogorov, a 15-Zernike caricature) cannot represent. r0_mm = Fried parameter; L0_mm/l0_mm = von-Karman outer/inner scales (default = pure Kolmogorov). Returns {rms_rad, r_mm, D_meas, D_theory, ratio_mid, r0_fit_mm}: the measured structure function tracks 6.88 (r/r0)^(5/3) (Schmidt's validation; subharmonics restore the low-frequency tail). Seed-pinned RNG; live trace byte-identical. Feed to propagate_field for a single-screen seeing PSF; the full multi-screen split-step is out of scope. E.g. turbulence_screen(256, 4.0, r0_mm=100, png=True).
propagate_turbulent Image a plane wave through atmospheric turbulence and average the PSF -- the long-exposure 'seeing' analysis (composes turbulence_screen + propagate_field). n_screens=1 = pupil-phase model; n_screens>1 with spacing_m>0 = the MULTI-SCREEN SPLIT-STEP (angular-spectrum between Kolmogorov screens -> scintillation). Returns {strehl_long, fwhm_long_rad, fwhm_diffraction_rad, broadening, seeing_ratio, energy_ratio}: the PSF is seeing-broadened toward lambda/r0 (>> diffraction) and propagation conserves energy. Seed-pinned RNG; live trace byte-identical. (Finite FFT grid -> FWHM ~0.7x ideal seeing.) E.g. propagate_turbulent(60, 10, 500, n_screens=5, spacing_m=2.0) -> seeing-limited PSF through 5 screens.
propagate_pulse Propagate an optical PULSE down a dispersive + Kerr-nonlinear fiber by the split-step NLSE -- the OPT-IN temporal-field layer (the live steady-state trace has no time axis). shape='sech' (soliton) or 'gaussian'. The DEFAULTS give the fundamental soliton (N=1: T0=1ps, beta2=-0.02 ps^2/m, gamma=0.002/W/m, P0=10W); length_m defaults to one soliton period z0=(pi/2)L_D. Returns {peak_power_W, energy_pJ, fwhm_ps, rms_bandwidth_THz, shape_invariance_err (~0 for a soliton), soliton_order_N, L_D_m}. Live trace byte-identical. Full supercontinuum (higher-order dispersion + Raman) is out of scope (scope_map). E.g. propagate_pulse() -> a shape-invariant N=1 soliton; propagate_pulse(shape='gaussian', gamma_per_W_per_m=0) -> pure dispersive broadening.
monte_carlo_tissue Monte-Carlo photon transport (MCML-style) through a homogeneous turbid tissue SLAB -- the OPT-IN biomedical-optics layer (stochastic radiative transport, separate from the coherent ray/field trace). Returns the energy budget {reflectance, transmittance, absorbed, energy_sum} (R+T+A=1 at a matched boundary), the unscattered ballistic_T (-> Beer-Lambert exp(-mu_t L)), and the fluence(depth) whose far-field log-slope gives penetration_depth_mm (-> diffusion mu_eff = sqrt(3 mu_a (mu_a+mu_s')), mu_s'=mu_s(1-g)). Seed-pinned RNG; live trace byte-identical. The one genuinely GPU-friendly category (CPU here for moderate n_photons). E.g. monte_carlo_tissue(0.1, 10.0, 0.9, 10.0, n_photons=20000) -> delta ~ 1.74 mm.
tolerance_scan Monte-Carlo ALIGNMENT-tolerance sweep: perturb the pose (position+orientation) of `elements` by Gaussian setup errors (sigma_pos_mm / sigma_ang_deg), re-trace `n` times, and report how far the beam walks at `target` -- the pointing-stability statistics (pointing_rms_mm / p95 / max). POSE-ONLY (kinematic DOFs); NOT glass/coating/figure tolerancing. Seed-pinned local RNG; restores every pose + the nominal trace afterwards (off-trace, byte-identical). `tol_mm` adds a yield (fraction of samples landing within tol). E.g. tolerance_scan(['M1'], 'Detector', sigma_pos_mm=0.1, sigma_ang_deg=0.05, n=200, tol_mm=0.5).
fdtd_derive_property ORCHESTRATE a full-wave sub-sim (Meep; Tidy3D as a cloud alt) to DERIVE a rigorous effective property for ONE element, then CACHE it as an ID-prop so the lumped trace reads it (live trace byte-identical). kind in {'grating_efficiency','stack_reflectance','metaatom_phase'}. If the backend is absent, returns the closed-form FALLBACK with backend='fallback-closedform' (grating direction == physics.grating_angle; stack == exact Abeles TMM, == ar_quarter_wave_reflectance for a quarter-wave; metaatom == low-confidence EMT) -- never fakes a Meep result (the 'backend' field tells the truth). This is tier (c) 'orchestrate Meep/Tidy3D' in capabilities()['scope_map']. E.g. fdtd_derive_property('Grating','grating_efficiency', wavelength_nm=633, orders=[-1,0,1]).
design_4f Design a full 4f relay (PURE -- no scene mutation). Object at the front focal plane of L1, lenses f1+f2 apart, image at the back focal plane of L2. Returns the object->L1->L2->image spacings seps=[f1, f1+f2, f2], the total length 2*(f1+f2), the transverse magnification -f2/f1, the beam expansion |f2/f1|, and the L1->L2 afocal ABCD matrix. Verified against the physics oracle.
mode_match Solve the single thin lens that mode-matches a Gaussian beam into a target mode -- the lens-to-cavity/fiber design solve (B3, PURE -- no scene mutation). Inputs: input waist w0_in (mm) located s_in mm before the lens, target waist w0_t (mm) at distance z_t mm past the lens, wavelength_nm, and optional beam quality m2. Returns the solved focal f (and the conjugate-plane second root f_alt), the REQUIRED lens position s_lens, the waist magnification m, the input Rayleigh range zR, the achieved_w0 / achieved_z obtained by actually propagating the input q through the solved lens (self-checking against the target), and the power-coupling efficiency `coupling` into the target mode (== 1 on a clean solve). Returns {ok:False, error:...} for a non-physical input or an UNREACHABLE target (no real focal -- e.g. demagnifying too close to the lens); no focal is fabricated. Verified by forward q-propagation against the physics oracle.
coupling_efficiency Power-coupling efficiency eta of a Gaussian mode (waist w_in, mm) into a target Gaussian mode (waist w_t, mm) transversely offset by `offset` mm -- the fiber/cavity coupling metric (B3, PURE). eta = [2 w_in w_t/(w_in^2+w_t^2)]^2 * exp(-2 offset^2/ (w_in^2+w_t^2)): symmetric, dimensionless, bounded (0,1], and == 1 only when w_in==w_t and offset==0. Returns the scalar eta, or {ok:False, error:...} for a non-physical waist. The coupling formula is verified against the physics oracle (pass_rate 1.0).
align_all Auto-align every element's kinematic knobs toward its target detector.
set_mount Apply a kinematic-mount preset (e.g. KM100CP/M) to element `name`.
set_param Set an optical parameter on element `name`. Accepts numbers (reflectivity, wavelength, pol_angle, ...), strings (element_type, analyzer, pol_type, coating, ...), or booleans (is_pbs, ...) -- optics_api.set_param takes any scalar; a float-only type blocked the rest.
add_component Spawn a catalog component by key (or its generic mesh-free fallback).
swap_part Replace element `name`'s mesh from an STL/OBJ (or STEP/IGES) file, keeping its optical slot (ports / pose / mount / beam role).
place_relative Place element `name` a distance (mm) from `reference` along an axis (BEAM / +X / -X / +Y / -Y / +Z / -Z); link=True makes it follow the reference live; align_rotation=False keeps the element's current rotation instead of snapping it to the reference.
scan Sweep a parameter (STAGE OPD / WAVEPLATE angle / WAVELENGTH) and write a plot PNG + CSV; `element` names the swept part (e.g. the OPD-stage mirror).
beam_profile Gaussian spot radius w(z) along the beam path source -> detector: waist position/size, element positions + clear apertures, plus a plot PNG + CSV.
render Configure or render the scene. preset: preview | final; camera: HERO/TOP/FRONT/SIDE. Pass filepath to write a still.
render_sequence Render a camera-orbit PNG sequence of the current setup (headless animation pipeline) and, if ffmpeg is present, encode an mp4 (best-effort; the PNG sequence is always produced). engine: EEVEE (fast) | CYCLES (realistic). Returns {frames, dir, pattern, ffmpeg, video}.
tag_element Mark object `name` as an optical element (optionally set its type) and auto-detect ports.
align_element Auto-align one element's kinematic knobs toward its target, then re-trace.
auto_align On-demand auto-aligner: the closed-loop influence-matrix corrector (the same AI/auto-align the promo teased). Drives steering knobs until the beam is centered on the reference apertures, by calibrating dy/du (poke each DOF, re-trace) and iterating u <- u - gain*A_pinv(y - y_target) to eps. GENERIC: with no args it auto-picks every kinematic (tip/tilt) element and the iris/pinhole/detector planes downstream of them. Or name what to steer: * actuators: list of element names (uses their tip/tilt DOFs), or [name, kind] / [name, [kinds]] pairs to pick specific DOFs. * targets: list of reference-aperture element names (irises / detectors). Returns {ok, residual_before, residual_after, iterations, converged, history}. This MOVES DOFs - only call it when you actually want to align (it is never run during a normal trace).
tilt_null Interferometer tilt-null solver (B4): automate the benchtop "spread the fringes to a single null". It reads the 2-D fringe pattern at the recombination detector, recovers the relative wavefront TILT between the two interfering arms (the fringe spatial frequency fx = tilt/lambda, cycles/mm), drives the two steering mirror tip/tilt DOFs until the fringe frequency -> 0 (dense tilt-fringes collapse to one broad fringe), then runs a 1-DOF piston (OPD) search to peak the fringe visibility. GENERIC: with no args it auto-picks the detector (the lit terminal with the most interfering beams) and the recombining-arm mirror's tip+tilt. Or name them: * detector: the recombination detector element name. * mirrors: steering actuators -- element names, or [name, kind] / [name, [kinds]] pairs. A single intensity frame cannot tell +tilt from -tilt (cos is even), so the solver descends the (V-shaped) fringe frequency to its ~1-fringe optical floor -- below one fringe across the aperture the tilt is unmeasurable, which IS a single broad fringe. This MOVES the steering + piston DOFs (only when called; a normal trace never enters it). Returns {ok, detector, tilt_before_deg/after_deg, fringe_freq_before/after, fringe_count_before/after, visibility_before/after, iterations, converged, history, controls, piston}.
check_mechanics Report the worst opto-mechanical limit (post pull-out, cage-rod travel, ...).
bake_beams Bake the traced beam path into emission-cylinder meshes (for rendering). `scale` multiplies the tube width (0 = use the scene's Beam width scale); the tube still follows the real Gaussian w(z), so 1.0 is physical size.
convert_scene_to_mm Bring the scene onto the add-on's millimetre convention, keeping physical sizes. The tracer reads world coordinates as mm and cannot be told otherwise, so this scales the USER's objects by the same factor it changes Unit Scale by; add-on geometry is already mm-authored and untouched.
clear_beams Remove all baked beam geometry.
ao_measure Read the residual Zernike wavefront error (waves) at a wavefront sensor. {zernike, rms}.
get_wavefront Alias of ao_measure: a wavefront sensor's reconstructed wavefront (Zernike + RMS).
zonal_render Dense ZONAL surface-figure 'sensor render': the raw wavefront map a WAVEFRONT SENSOR reads from the reflective element whose reflected beam reaches it (pass `sensor` -- requires the beam to land on the WFS), or a named reflective `element` directly. Bypasses the 15-mode modal low-pass; writes a PNG and publishes the map to the sensor. Use the 'surface_figure' example for a ready bench (oblique laser -> figured reflector -> WFS); swap_part any mesh onto the reflector to read its figure.
pyramid_wfs Read a wavefront sensor as a PYRAMID WFS: instead of the modal Zernike vector (ao_measure / Shack-Hartmann), report the local wavefront SLOPE -- the 4-pupil intensity differences a pyramid sensor encodes as Sx = dW/dx, Sy = dW/dy. Reads the same wavefront (modal aberr + the beam's own curvature defocus), writes a slope-FIELD PNG (hue = slope direction, value = magnitude) + publishes it to the sensor. A pure defocus reads a RADIAL slope. Tier-1 GEOMETRIC (the gradient a pyramid integrates, not a diffractive 4-pupil image). {sensor, wavefront_rms, slope_x_rms, slope_y_rms, slope_rms, ...}.
sensor_capture What a wavefront SENSOR actually CAPTURES of the beam reaching it (it does NOT swallow the whole beam -- a beam wider than the sensor is truncated at its aperture). Returns the beam radius at the sensor, the clear aperture, the captured POWER fraction, the figure-footprint fraction captured (rho_max), and the captured zonal figure RMS / hit_frac. A collimated beam that fits reads the whole figure; a diverging beam that overfills reads only its centre — the difference is produced by the simulation.
ao_command Set a deformable mirror's command (Zernike coefficients, in waves).
ao_close_loop Close the modal adaptive-optics loop (wavefront sensor -> deformable-mirror integrator) until the residual wavefront RMS flattens. Returns the RMS history (open-loop first, corrected last).
ao_close_loop_recon B5: close the AO loop with the full reconstructor control structure -- an interaction matrix B (poke each DM mode, record the wavefront-sensor response), a reconstructor R=B+ (method='TSVD' truncated-SVD pseudoinverse, or 'DAMPED_TRANSPOSE' noise-tolerant c*B^T), and the leaky integrator x_{k+1}=leak*x_k - gain*R*w_k. Returns {rms_before, rms_after, reduction, history, singular spectrum}. TSVD converges fast; damped-transpose is slower but never amplifies ill-conditioned/noise modes.
ao_kolmogorov B5: drive an ABERRATOR element's injected wavefront from a PHYSICAL Fried parameter r0 (Kolmogorov/Noll turbulence statistics, sigma^2=1.0299*(D/r0)^(5/3) rad^2). Smaller r0 = stronger turbulence. r0_mm / D_mm default to the scene AO props. {ok, r0_mm, D_mm, rms, modes}.
export_svg Export a top-view 2-D vector (SVG) schematic of the optical layout + beam path to filepath (element glyphs, port ticks, wavelength-coloured beams) -- a dependency-free publication figure.
export_report Bundle the WHOLE-BENCH analysis into ONE self-contained HTML spec sheet -- the 'show me everything' command. Binds get_state + inspect_all (per-element dashboard) + diagnose + beam_profile (plot embedded) + an optional render (with_render=True), images inlined as base64 so the file is portable. Returns {ok, path, n_elements, n_issues}. READ-ONLY; byte-identical.
dress_bench Spawn (enable=True) or remove (enable=False) the procedural opto-mechanics: a hole-grid breadboard, a post + pedestal under each optic, and a mount ring framing it. Optics are NOT moved, so the trace is unchanged. After dressing, get_state()['bench'] reports the grid (pitch, origin, extent) and the occupied holes -- read that to know where parts can seat.
set_grid Set the breadboard hole-grid standard. standard in {METRIC (25 mm/M6), IMPERIAL (1"/1/4-20), CUSTOM}; pitch_mm sets a custom pitch in mm (implies CUSTOM). Default is metric. Re-dresses the bench if dressed. Returns the active grid (also visible in get_state()['bench']).
place_on_grid Move optical element `name` over breadboard hole (col, row), keeping its height and orientation -- grid-aware placement for building a layout. The bench must be dressed first (call dress_bench). Read get_state()['bench'] for grid extent (cols x rows) and occupied holes. Unlike dress_bench this DOES move the part, so the trace updates.
make_cage Group collinear optical elements into a cage assembly: they share 4 rods (e.g. Ø6 mm on a 30 mm square for SM1/Ø1") and one cage post instead of an individual post each. `members` is a list of element names; `size_mm` in {16, 30, 60} (companion optic Ø1/2"/Ø1"/Ø2"). The cage is reported in get_state()['cages']. Optics are not moved, so the trace is unchanged.
make_tube Stack collinear in-line optics into one SM lens-tube barrel (they share one barrel + one post instead of an individual post each). `members` is a list of element names; `thread` in {SM05, SM1, SM2} (Ø1/2", Ø1", Ø2"). Reported in get_state()['tubes']. Optics are not moved, so the trace is unchanged.
make_rail Put collinear elements on one dovetail rail: each rides a carrier on the shared rail instead of a bare post, so they translate along one straight track. `members` is a list of element names. Reported in get_state()['rails'] (carrier s_mm). Optics are not moved -> trace unchanged.
place_on_rail Slide rail-mounted element `name` to position s_mm along its rail (s=0 at the rail start). Moves the optic along the rail axis only, so the trace updates. The element must be on a rail (call make_rail first).
Permissions 4
network medium filesystem low shell high env_vars low