Calibration — ComMarker B4 60W MOPA

Galvo geometry calibration for MeerK40t 0.9.8100 (JCZ / BJJCZ board, 110 mm lens) and the MOPA colour calibration tool. Updated 2026-10-31. Status: kit ready; sheet 1 not burned yet, so there are no measured results yet.

MOPA colour calibration tool: download the colour grid for LightBurn, burn it on stainless, and upload a scan to get back a settings-to-colour library. Open /calibrate/ →
MeerK40t galvo calibration kit: download everything (.zip) · README.md · individual files
MeerK40t 100 mm calibration target: squares, diagonals, rulers, circles, orientation pips
Sheet target (100 mm)
Power/speed ladder for black construction paper: 3-20 % by 2000/1500/1000 mm/s
Sheet 1 power ladder
Synthetic self-test overlay showing residual distortion arrows
Analysis overlay (synthetic self test, arrows ×5)

MeerK40t galvo calibration on black construction paper

Folder: /home/hank/laser/projects_shared/calibration_meerk40t/ · prepared 2026-10-31 · MeerK40t 0.9.8100 · JCZ/BJJCZ board (USB 9588:9899) · 110 mm lens (F-theta)

Safety rule: these files only prepare jobs. Hank presses Start himself. Nothing here starts a burn, framing or the red dot. Never launch MeerK40t with -a, because it auto-starts the laser.

What this calibrates

MeerK40t has never been calibrated on this machine. Three sheets of 8.5×11 in black construction paper are used:

Sheet Burn Purpose
S1 power ladder (mk_cal_S1_ladder_L131.5.svg), then the target (mk_cal_S1_L131.5.svg) choose a clean line setting; baseline distortion at the current settings (lens 131.5, stock jcz110.cor)
S2 mk_cal_S2_L131.5.svg, using the new lens size + new .cor from the S1 analysis apply the correction; measure how the board responded (gamma)
S3 mk_cal_S3_L131.5.svg, using the S2 result verify, or apply a fine-tune

Target: worst point within the 100 mm square ≤ 0.2 mm, which matches LightBurn round 6 (0.186 mm).

Files

File What
jobs/mk_cal_S1_ladder_L131.5.svg MeerK40t project. 18 cells: 6 powers (3/5/8/12/16/20 %) × 3 speeds (2000/1500/1000 mm/s), 50 kHz, 200 ns, 1 pass. Each cell is a 6 mm square plus a diagonal. The ops are ordered from lowest to highest energy. Labels burn at 8 %/2000. The FRAME op is output off.
jobs/mk_cal_S{1,2,3}_L131.5.svg MeerK40t project for the 100 mm target. Ops: 1 GEOM (lines), 2 TEXT (outline labels), 3 FRAME (100 mm reference, output off). Default 8 %, 2000 mm/s, 50 kHz, 200 ns, ×1.
jobs/*_plain.svg, *_preview.png the same geometry as plain SVG, and a picture of it
jobs/jobs_L131.5.json every op setting in machine-readable form
mk_cal_analyze.py analyses a 600 dpi scan and writes the report, overlay, new lens size, new flags and a corrected .cor
mk_cal_design.py, mk_view_table.py shared geometry, and the MeerK40t view-flag matrices (generated from MeerK40t's own View class)
gen_mk_cal.py regenerates the jobs (--lens --power --speed --freq --pw). Needs matplotlib, so run it on the box, not in the laptop venv.
mk_cal_synth.py, selftest.sh synthetic burn/scan simulator plus an end-to-end self test
selftest/ box self-test evidence (reports + JSON)
ref/jcz110.cor read-only copy of the stock table, used for tests
scans/ put the scans here (sheet1_scan_600.png …)
results/ the analysis writes here
PROPOSED_CONFIG.md the settings that will change, and how to change them safely

Target geometry (mm, centred on the field, y down): outer square 100. Inner square 78.49 with both diagonals. Axis rulers ±46 with ticks at ±10/20/30/44. Circles: Ø70, Ø15, and four Ø8 corner circles at (±44.6, ±44.6). Orientation pips by the corner circles count 3 (top-left), 2 (top-right), 1 (bottom-left) and 0 (bottom-right). Text: "TOP-LEFT" with an arrow, "X+", "Y+", and the sheet label. The pips let the analysis detect a mirrored or rotated burn even without the paper snip.

Lens size: why 131.5 and not 110

MeerK40t 0.9.8100 knobs (Balor/JCZ device)

Knob GUI location Current What it does
Lens Size Device settings → General → Width 131.5 mm Global scale for both axes. It cannot fix X and Y separately.
Correction file General → Correction File → Enable + File on, /home/hank/laser/meerk40t/jcz110.cor A 65×65 table that the board applies. It is the only way to fix per-axis scale, skew, trapezoid, barrel/pincushion and off-centre error.
Flip X / Flip Y / Swap XY Parameters → Axis corrections F / T / T Mirrors or rotates the design-to-galvo mapping. These are right if the burn reads correctly (TOP-LEFT at the back-left, text not mirrored).
Rotate View Parameters → Axis corrections 0 Further 90° steps
Laserspot, Margins, redlight offsets General / Red Dot – Do not affect burn geometry (the redlight offset affects the red dot only)
Correction tab (Device window) – – A stub in 0.9.8100 that does nothing. Use a .cor file instead.

These are the settings the analysis prints and writes into new .cor files. Format: JCZ_COR_2_1, 65×65 signed int32 (dx, dy), step 1024 ticks. How the board applies the table (sign) is checked by the gamma test on sheet 2. New tables are clamped to |v| < 32768.

Before you start (once)

  1. Snip about 8 mm off one corner of each sheet with scissors, at 45°. That corner is the "back-left" reference, so the analysis knows how the paper sat.
  2. Put a flat non-flammable backing under the paper: aluminium sheet, steel plate or a ceramic tile. Tape all 4 edges flat with masking tape. Paper must lie flat, because a wave in the paper is a focus and scale error.
  3. Focus the head on the paper surface the usual way (focus gauge / measured height for the 110 mm lens).
  4. Load files with File → Open in the running MeerK40t. Do not use stand_center.bat, because it shifts everything by 38.25/23.25 mm. Do not use cal_run.bat, because it contains spool commands. After opening, press Ctrl+A then Ctrl+Shift+C to centre on the bed. The files are already centred at 65.75 mm for lens 131.5. Do this every time the lens size has changed.

Fire safety (every burn)

Sheet 1, step by step

  1. Prepare sheet 1 (snip at the back-left, tape it flat on the non-flammable backing). Slide it so its back third is under the centre of the lens. The ladder is about 66 × 37 mm.
  2. In MeerK40t: File → Open jobs/mk_cal_S1_ladder_L131.5.svg → Ctrl+A → Ctrl+Shift+C. Check the op list: 18 red ops 01 … 18 (low to high energy), 99 LABELS, and a FRAME op with output off. Device settings must be unchanged: Width 131.5 mm, jcz110.cor enabled, Flip X F, Flip Y T, Swap XY T, Rotate 0.
  3. Hank presses Start and watches the ladder burn. If a cell flames, stop. The settings after it are hotter.
  4. Let it cool, then look at the ladder. The rows are speed (top 2000, middle 1500, bottom 1000 mm/s) and the columns are power (3/5/8/12/16/20 %). Pick the lowest-energy cell whose square and diagonal are continuous, crisp and clearly lighter or darker than the paper, without burn-through, charring or flame. That is the line setting. Rule from the laser lessons: stay at ≤12 % if a cell there is clean.
  5. Open jobs/mk_cal_S1_L131.5.svg (Ctrl+A, Ctrl+Shift+C). Double-click op 1 GEOM and op 2 TEXT and set the chosen power and speed (keep 50 kHz, 200 ns, 1 pass). 3 FRAME stays output off.
  6. Untape and slide the paper without rotating it (snip still at the back-left) so a clean area at least 110 mm square is centred under the lens and is clear of the ladder. Tape it flat again.
  7. Hank presses Start. The target takes well under a minute. Watch it, then let it cool and check for embers.
  8. Scan (next section) and run the analysis.

Scan (HP PhotoSmart 2600 on the laptop's USB)

Place the sheet face down, with the snipped corner fully on the glass (portrait, against the top-left of the glass). Then run on the laptop:

pkill -x scanimage; mkdir -p /home/hank/laser/projects_shared/calibration_meerk40t/scans && nohup scanimage -d 'hpaio:/usb/Photosmart_2600_series?serial=YOUR_SERIAL' --mode Color --resolution 600 --compression None -x 215.9 -y 297 --format=png -o /home/hank/laser/projects_shared/calibration_meerk40t/scans/sheet1_scan_600.png > /tmp/scan.log 2>&1 < /dev/null &

For later sheets, change sheet1 to sheet2 / sheet3. A scan takes about 2.5 min and is about 53 MB. Check it with cat /tmp/scan.log; ls -la …/scans/. The scanner's measured scale is 600.0 dpi in X and 599.70 dpi in Y, and the analysis uses those values by default. A face-down flatbed scan is not mirrored.

Analysis

cd /home/hank/laser/projects_shared/calibration_meerk40t
PY=/home/hank/.venvs/mk40t/bin/python
# sheet 1 (baseline: lens 131.5 + stock cor)
$PY mk_cal_analyze.py scans/sheet1_scan_600.png --lens 131.5 --cor-in /home/hank/laser/meerk40t/jcz110.cor --tag sheet1 --out results
# sheet 2 (use the lens and cor that sheet 1 produced; --prev measures how the board responded)
$PY mk_cal_analyze.py scans/sheet2_scan_600.png --lens <S1 lens> --cor-in results/sheet1_corrected_L<S1 lens>.cor --prev results/sheet1_result.json --tag sheet2 --out results
# sheet 3: same pattern with sheet2 files

If the flags were changed after a sheet, add --flip-x 0/1 --flip-y 0/1 --swap-xy 0/1 --rotate 0..3 with the flags that were in use for that burn. The defaults are the current 0/1/1/0.

Each run takes a few seconds and writes to results/:

Response check on sheet 2+: gamma ≈ 1 means the correction acted as predicted. If gamma < 0, the sign is inverted, and the script automatically writes a sign-flipped table. If gamma is near 0, the table was ignored or transposed, and no automatic .cor is written. Stop and check that the cor was enabled and the lens was re-typed.

Applying the sheet-1 result (before sheet 2)

See PROPOSED_CONFIG.md. In short:

  1. Back up the config.
  2. Device settings → Correction File → File → choose results/sheet1_corrected_L<lens>.cor. Keep Enable ticked.
  3. Type the new Width (lens size) from the report back in, because selecting the cor reset it to 125.002.
  4. Change Flip/Swap/Rotate only if the report says the burn was mirrored or rotated.
  5. Open mk_cal_S2_L131.5.svg with the same op power and speed as S1, then Ctrl+A, Ctrl+Shift+C.

If the S1 worst point was very large (> 3 mm) or the overlay looks wrong, use the _half.cor for S2.

Self test (no laser, no scanner)

./selftest.sh /home/hank/.venvs/mk40t/bin/python (the laptop venv has no matplotlib; the simulator then skips the text labels and prints harmless numpy/matplotlib import warnings) simulates a distorted machine, then runs a sheet-1 scan, the correction, a sheet-2 scan and the analysis. Box result (2026-10-31):

Earlier box tests also passed: a mirrored machine (the flags were detected), and a sign-inverted board (gamma −1.02, auto-flipped, sheet 3 at 0.061 mm).

Downloads

Measured results (scans, reports, final lens size and .cor) will be added here after sheets 1–3 are burned. The stock vendor table jcz110.cor is not redistributed.