Skip to main content

Mach Vision System

Introduction

IntroductionOverview

Overview

The Keyence Vision integrationsystem lets a Mach-controlled CNC router use a smart camera to find printed fiducial marks and either (a) report their offset for downstream G-code or (b) automatically align an entire program to a part by capturing a few marks and applying a modal coordinate transform.

The implementation is driver-based. VisionModule owns all calibration math, fit solving, G-code emission, and OEM-parameter wiring; individual driver classes (currently only Keyence, for VS-series smart cameracameras (such as the VS-L320MX)L320MX) to locatehandle the centerwire ofprotocol. New cameras are added by writing a printednew fiducialdriver, marknot by changing the M-code surface.

All pixel-to-machine-units math, fit solving, and reportmodal theG-code X/Y(G51 offset/ fromG68 / G52) emission live in VisionModule. Drivers are dumb — they trigger the camera centerand toreturn thepixel mark center.deviations.

Typical workflow: the machine jogs or moves under G-code so the camera is positioned within roughly two inches of the printed mark, then an M280 call triggers the camera to measure and stores the offset values in OEM parameter registers where the operator screen and downstream G-code can read them.

Communication uses the Keyence Non-Procedural command interface over TCP/IP. The control sends TRG on the data output port and the camera replies with the configured Data Output (Non-Procedural) tool values.

Components

(
File Purpose
Modules/Addons/KeyenceVisionModule.Vision/VisionModule.lua Top-level orchestrator. Calibration (K matrix), alignment-run state, fit solving, modal G-code emission, OEM-parameter wiring, pound-var output.
Modules/Addons/Vision/VisionMath.luaPure-math helpers: pixel → machine-unit transform (ApplyK), 3-point similarity fit, 4-point rectangle fit, Procrustes / least-squares solvers.
Modules/Addons/Vision/Drivers/KeyenceVisionDriver.luaKeyence VS-series Non-Procedural TCP non-procedural client. Opens the socket, sendsImplements TRGTrigger, parsesSelectJob, theGrabImage, response, and writes results to OEM parameters.Capabilities.
Modules/CommonMCodeModule.lua Hosts the _m280)M-code implementation that calls, KeyenceVisionModule.ReadOffset()_m281., _m282, _m283, _m284 implementations.
Profiles/Mill/Macros/m280.mcs … m284.mcs G-code macro wrapperwrappers that invokesforward into the implementations.
_m280Profiles/Mill/Settings/Standard/Common/settings.Vision.csvOEM parameter definitions (Connection, Calibration, Output sections).

M-Code Summary

M-codePurpose
M280Begin an alignment run. Clears the dot list and cancels any active G51 / G68 / G52. Optional P selects fit mode (4 = rectangle, 3 = similarity).
M281Capture one mark. Triggers the camera, applies the K matrix, stores the observed point in pound vars and the dot list. Optional D1 persists the FTP-pushed inspection image.
M282Solve and apply the fit as modal G51 / G68 / G52. Optional P selects which piece of the fit to emit (1 = shift, 2 = rotation, 3 = scale, omitted = all).
M283Calibrate the K matrix. Q = quick diameter calibration (no motion). C = full cal-sheet walk (recommended).
M284Select the active camera job/program (capability-gated — only drivers that advertise select_job support this).

System Architecture

Data Flow

  1. Driver Trigger. The active driver fires the camera and returns a pixel deviation (dx_px, dy_px) in its native frame (typically top-left origin).
  2. Image-center shift. VisionModule.CenterRawPixels() subtracts (resX/2, resY/2) using the OEM-configured camera resolution so all internal pixel coordinates are signed offsets from image center.
  3. Camera-vs-machine axis remap. Three OEM toggles — VisionCameraSwapXY, VisionCameraInvertX, VisionCameraInvertY — normalize the camera frame to the machine frame. M283 auto-detects and writes these the first time you calibrate.
  4. K matrix. VisionModule.PxToMachine() applies the calibrated 2x2 K matrix to convert pixels to machine units (in or mm). K captures combined scale, axis-swap residual, and small rotation between the camera and the machine.
  5. Observed point. observed = commanded + K*(px - px_center_offset). The commanded XY is read from mcAxisGetPos at trigger time.
  6. Fit. When M282 runs, VisionMath.RectangleFit (4-point) or VisionMath.SimilarityFit (3-point) solves a rigid-body transform from commanded → observed.
  7. Apply. M282 emits the solved transform as modal G52 (shift), G68 (rotation), and G51 (scale) via mcCntlGcodeExecuteWait. The rest of the program runs in the corrected frame.

Tool-Table Convention

The camera is treated as a tool. M283 C writes the camera-vs-spindle X/Y offsets directly into the tool table for the camera tool number, so the machine knows where the camera is relative to the spindle. The convention is:

  1. Activate the spindle tool. Jog over the cal-sheet center and zero work-coord X and Y.
  2. Tool-change (Tn M6) to the camera tool. Jog so the start dot is in the FOV.
  3. Run M283 C1. The routine walks the sheet, solves K, and writes the camera tool's X/Y offsets so that with the camera tool active a commanded XY puts the camera — not the spindle — at that XY.

VisionCameraToolNumber must match the tool number you use for the camera. When set, every M280 / M281 / M282 / M283 verifies that this tool is the active spindle tool and aborts otherwise — this stops captures from running with the wrong tool offset in effect. Set it to 0 to disable the check.

Camera Setup

(Keyence)

Network Configuration

  • Default camera IP expected by Mach:IP: 192.168.208.80 — configurable via OEM parameter KeyenceVisionIP. The camera must be set to this address (or the OEM parameter must be changed to match the camera)VisionIP.
  • TCP port for command + data output:port: 8500 (default)Non-Procedural data output) — configurable via KeyenceVisionPortVisionPort.
  • Socket I/O timeout: 3 seconds (default) — configurable via KeyenceVisionTimeoutVisionTimeout.
  • End delimiter: CR (0x0D)0x0D). (camera default; the moduleModule assumes this).this.

VS Creator Job Requirements

The job loaded on the camera must be configured to emit the mark-center offset in response to a TRG command:

  1. The cameraCamera must be in Run Mode. Data Output (Non-Procedural) only fires in Run Mode.
  2. Configure the triggerTrigger source as= Communication / Command so TRG initiates a measurement.
  3. AddConfigure a Data Output (Non-Procedural) tool that emitsemitting comma-separated valuesfields. in oneEither of thethese followingformats formats:is accepted:
    • dx,dy — the X and Y offset from the camera center to the mark center.
    • judgment,dx,dy
    • judgment,count,dx,dy,width,height,angle,score (full formjudgmentrecommended (1for =cal OK, 0 = NG) followed by the offsets.sheet)
  4. ConfirmFor M283 Q and image saving via M281 D1, the endcamera must be configured to FTP-push inspection images to a folder reachable from Mach.
  5. End delimiter is= CR.

Image Folder (optional)

TheSet offsetVisionImageWatchDir valuesto arethe returned in whatever unitsfolder the camera jobFTP-pushes isimages configuredinto. toM281 output.D1 Makelooks surefor an image newer than the trigger timestamp and copies it into Modules/Addons/Vision/Images/ with a timestamped filename. VisionImageGrabTimeout (default 2 s) controls how long the watch loop waits.

The 8500 protocol cannot toggle FTP push from the controller, so leave camera FTP push enabled at the camera unitsside matchand let the unitsD expectedflag byon theM281 programdecide readingwhether theeach OEMframe parameters.is kept.

OEM Parameters

Connection

Configuration Parameters

successwithout
Name Type Default Description
KeyenceVisionEnabledVisionEnabled numberyesno 0No 0Master enable. No = disabledM-codes (module not loaded, M280 islog a no-op).warning 1and =return enabled.
KeyenceVisionIPstring192.168.208.80IP address ofcontacting the camera.
KeyenceVisionPortVisionDriver numberchoiceKeyenceDriver name. Selects which class under Vision/Drivers/ is loaded.
VisionIPtext192.168.208.80Camera IP address.
VisionPortinteger 8500 TCP port for the non-procedural interface.port.
KeyenceVisionTimeoutVisionTimeout numberinteger 3 Socket I/O timeout (seconds).
VisionActiveJobinteger0Camera program/job id. Persistent; written by M284. 0 = leave camera on whatever job it has loaded.
VisionImageWatchDirtext(empty)Folder the camera FTP-pushes images into. Empty disables image grab.
VisionImageGrabTimeoutfloat2Seconds to wait for a fresh image to appear in seconds.the watch dir.
VisionCameraSwapXYyesnoNoSwap camera X and Y axes. Auto-set by M283.
VisionCameraInvertXyesnoNoNegate camera X. Auto-set by M283.
VisionCameraInvertYyesnoNoNegate camera Y. Auto-set by M283.
VisionTriggerSettleSecfloat0.25Pre-trigger dwell used by M283 only. Lets servo motion settle before capture.
VisionCameraToolNumberinteger0Camera tool number. M280 / M281 / M282 / M283 require this tool be active. 0 disables the check.

Result

Calibration

Parameters
NameTypeDefaultDescription
VisionCal_K11float0K matrix row 1 col 1 (px → machine units).
VisionCal_K12float0K matrix row 1 col 2.
VisionCal_K21float0K matrix row 2 col 1.
VisionCal_K22float0K matrix row 2 col 2.
VisionCal_Datetext(empty)ISO timestamp of the last successful calibration.
VisionCal_UnitschoiceinUnits the K matrix is in (in or mm).
VisionCameraResXinteger1776Camera image width in pixels.
VisionCameraResYinteger1776Camera image height in pixels.
VisionPxCenterOffsetXfloat0Optional sub-pixel correction applied after image-center shift, before K.
VisionPxCenterOffsetYfloat0Same, Y.

Output

NameTypeDefaultDescription
VisionPoundVarBaseinteger500Base address of the 5-slot, 8-stride pound-var output block. See Pound Variables.

Runtime / Last Trigger (read-only)

AfterThese everyare M280written call,by the module writesafter every trigger and run. Surface them as DROs on the camera result to the following OEM parameters. G-code, scripts, and screen DROsfor can read them at any time.diagnostics.

bythecamera.0= refused","ER,TRG,07")
Name Type Description
KeyenceVisionLastJudgmentVisionLastJudgment numberfloat 1 = OK, 0 = NG, -1 = unknown / not reportedreported.
VisionLastOffsetPxX float Last NGcentered pixel X offset.
VisionLastOffsetPxYfloatLast centered pixel Y offset.
VisionLastOffsetXfloatLast machine-unit X offset (fail).K 1* = OK (pass)px).
KeyenceVisionLastOffsetXVisionLastOffsetY numberfloat XLast offsetmachine-unit fromY camera center to mark center, in camera units. Signed.offset.
KeyenceVisionLastOffsetYVisionLastWidth numberfloat Y offset from camera center toDetected mark center,width in cameracurrent units.machine Signed.units (in or mm).
KeyenceVisionLastStatusVisionLastHeight stringfloatDetected mark height in current machine units.
VisionLastAnglefloatDetected mark angle (deg, 0 if N/A).
VisionLastScorefloatDriver-reported correlation/score.
VisionLastStatustext "OK"OK on success, otherwise aor human-readable error messagetext.
VisionRun_Activefloat1 while a run is in progress, 0 otherwise.
VisionRun_Modeinteger4 = rectangle, 3 = similarity.
VisionRun_DotCountintegerCaptures taken so far in this run.
VisionRun_Tx / _TyfloatSolved shift (e.g.G52).
VisionRun_ThetafloatSolved rotation (radians, G68 R).
VisionRun_Sx / "timeout"_Sy,floatSolved scale (G51).
VisionRun_CenterX / "connection_CenterY float Rotation center (G68 X / Y).

Module

Pound-Variable Loading

Output

Every M281 writes the captured point into a fixed pound-variable block based at VisionPoundVarBase (default 500). The block is 5 slots of 8 vars each (40 vars total). Slot 0 is always the most recent capture (mirrored from whichever indexed slot just got written). Slots 1–4 hold the four M281 captures in the order they were taken during the current run.

For a non-default VisionPoundVarBase — call it B — replace 500 below with B. The address of slot s field f is #(B + s*8 + f).

AddressSlotFieldDescription
#5000 (latest)XObserved X of the most recent capture, in machine units (commanded_x + dx_units). Mirrored from the slot that was just written.
#5010 (latest)YObserved Y of the most recent capture, in machine units.
#5020 (latest)WidthDetected feature width of the most recent capture, in current machine units (driver pixels × X-axis K column norm).
#5030 (latest)HeightDetected feature height of the most recent capture, in current machine units.
#5040 (latest)AngleDetected angle of the most recent capture, in degrees. 0 if the camera job does not report angle.
#5050 (latest)ScoreDriver-reported correlation / match score for the most recent capture.
#5060 (latest)reservedReserved for future use (planned: Z height).
#5070 (latest)reservedReserved for future use (planned: detected area).
#5081 (point 1)XObserved X of the first M281 capture in this run, machine units.
#5091 (point 1)YObserved Y of the first M281 capture, machine units.
#5101 (point 1)WidthDetected width at point 1, machine units.
#5111 (point 1)HeightDetected height at point 1, machine units.
#5121 (point 1)AngleDetected angle at point 1, degrees.
#5131 (point 1)ScoreDriver score at point 1.
#5141 (point 1)reservedReserved (planned: Z).
#5151 (point 1)reservedReserved (planned: area).
#5162 (point 2)XObserved X of the second M281 capture, machine units.
#5172 (point 2)YObserved Y of the second M281 capture, machine units.
#5182 (point 2)WidthDetected width at point 2, machine units.
#5192 (point 2)HeightDetected height at point 2, machine units.
#5202 (point 2)AngleDetected angle at point 2, degrees.
#5212 (point 2)ScoreDriver score at point 2.
#5222 (point 2)reservedReserved (planned: Z).
#5232 (point 2)reservedReserved (planned: area).
#5243 (point 3)XObserved X of the third M281 capture, machine units.
#5253 (point 3)YObserved Y of the third M281 capture, machine units.
#5263 (point 3)WidthDetected width at point 3, machine units.
#5273 (point 3)HeightDetected height at point 3, machine units.
#5283 (point 3)AngleDetected angle at point 3, degrees.
#5293 (point 3)ScoreDriver score at point 3.
#5303 (point 3)reservedReserved (planned: Z).
#5313 (point 3)reservedReserved (planned: area).
#5324 (point 4)XObserved X of the fourth M281 capture, machine units. Used as the fourth fiducial in a 4-point rectangle fit.
#5334 (point 4)YObserved Y of the fourth M281 capture, machine units.
#5344 (point 4)WidthDetected width at point 4, machine units.
#5354 (point 4)HeightDetected height at point 4, machine units.
#5364 (point 4)AngleDetected angle at point 4, degrees.
#5374 (point 4)ScoreDriver score at point 4.
#5384 (point 4)reservedReserved (planned: Z).
#5394 (point 4)reservedReserved (planned: area).

VisionPoundVarBase must be ≥ 100 and must not overlap Mach's reserved ranges (5061..5081 probe results, 5201..5500 fixture offsets). The module isvalidates loadedthis automaticallyon by both the screen environment and the M-code environment whenever KeyenceVisionEnabled equals 1. Loading happens in:

  • CommonGUIModule.LoadAllModules() — exposed as global kv for screen scripts.
  • ModuleLoader.RequireCommonMCodeModulesAndGlobals() — exposed as global kv for M-code scripts.

When KeyenceVisionEnabled is 0, the module is not loaded and no TCP code runs.every M280 stilland existsaborts but logswith a messageclear error if misconfigured.

In a 3-point similarity run (M280 P3), only slots 1, 2, and returns3 successare withoutpopulated contacting theslot camera.4 is left at its previous-run value. Slot 0 still mirrors whichever capture was most recent.

M280 — TriggerBegin CameraAlignment ReadRun

Overview

Overview

TriggersStarts a singlenew measurementalignment onrun. Clears the in-memory dot list, validates the pound-var base, verifies the camera and stores the result in the KeyenceVisionLast* OEM parameters. The G-code programtool is responsibleactive, forand firstemits positioningG69 theG50 cameraG52 overX0 theY0 mark.to cancel any active rotation/scale/shift modal that a previous alignment may have left set.

Parameters

Letter Description
P Error-handlingFit mode (optional).
4 or omitted = 4-point rectangle fit (recovers shift, rotation, X/Y scale).
3 = 3-point similarity fit (recovers shift, rotation, uniform scale).

Examples

M280       ( default 4-point rectangle fit )
M280 P3    ( 3-point similarity fit )

M281 — Capture One Mark

Overview

Triggers the camera at the current commanded position, applies the K matrix, and stores the observed point in the dot list and pound vars. The G-code program is responsible for first positioning the camera over the mark.

M281 aborts the program if the camera reports NG / no mark, if the K matrix is zero (no calibration), or if the camera tool is not active.

Parameters

LetterDescription
DImage-save flag.
0 or omitted = discard the FTP-pushed image.
1 = persist the image into Modules/Addons/Vision/Images/ with a timestamped filename. Requires VisionImageWatchDir to be set.

Examples

G0 X1.0 Y1.0
M281            ( capture, do not save image )
G0 X1.0 Y1.0
M281 D1         ( capture and save image )

M282 — Solve and Apply Fit

Overview

Solves a fit from the captured dot list and emits the result as modal G52 (shift), G68 (rotation), and G51 (scale). The rest of the program runs in the corrected frame until the modals are cancelled.

G51, G68, and G52 are modal in Mach 4. Repeated M282 calls overwrite the previous values rather than compounding.

Parameters

LetterDescription
PPiece selector (optional).
0 or omitted = soft-fail:apply logall error,(cancel continue+ program.G68 + G51 + G52) (default).
Non-zero1 = halt:shift stoponly the(G52).
2 program= onrotation cameraonly error(G68 orabout NGcentroid).
3 judgment.= scale only (G51 Sx, Sy).

Examples

Emitted G-code

The default (P omitted) emits, in order:

G69 G50 G52 X0 Y0                  ( cancel prior rot/scale/shift )
G52 X{tx} Y{ty}                    ( shift  )
G68 X{cx} Y{cy} R{theta_deg}       ( rotate about centroid )
G51 X{sx} Y{sy}                    ( scale  )

To cancel everything when the alignment job is finished, run:

G69 G50 G52 X0 Y0

Calibration Sheet

Overview

The MachMotion Vision Calibration Sheet is a printed US-Letter-portrait sheet with five black-and-white target marks arranged on a 5.00″ × 8.00″ rectangle plus a center mark. It is the reference geometry used by M283 C1 to solve the full 2×2 K matrix and write the camera-vs-spindle tool offset.

Sheet Layout

DotPosition (in)Notes
1 — Start(-2.5, -4.0)Lower-left. Operator parks the camera over this dot before issuing M283 C1.
2(-2.5, +4.0)Upper-left.
3(+2.5, +4.0)Upper-right.
4(+2.5, -4.0)Lower-right.
5 — End(0, 0)Sheet center. Used to write the camera tool's X/Y offset and to verify the residual at the end of the cal walk.
  • Outer rectangle: 5.00″ wide (X) × 8.00″ tall (Y), centered on dot 5.
  • Mark style: 0.5″ diameter quartered black/white target (high-contrast, symmetric — works well with the Keyence circle / blob detector).
  • Walk order: 1 (Start) → 2 → 3 → 4 → 5 (End / Center).
  • Trusted axis: X (8.5″ page width prints more accurately than the 11″ page length on most consumer printers).

Printing

  1. Print the supplied PDF on US Letter (8.5″ × 11″) at 100% scale / Actual Size. Do not use "Fit to Page" or "Shrink to Fit" — the cal math depends on the printed dimensions matching the table above.
  2. After printing, measure the X distance between dot 1 and dot 4 (or dot 2 and dot 3) with a ruler or caliper. It should read 5.00″ ± 0.01″. If your printer is off by more, scale-correct the PDF before reprinting.
  3. Use clean, matte paper. Glossy finishes can produce specular reflections that confuse the camera.

Mounting on the Machine

  1. Tape the sheet flat on the table or fixture. Any wrinkles, bubbles, or curls translate directly into apparent dot-position error.
  2. Orient the sheet so its X+ arrow points in the same direction as machine X+ and its Y+ arrow points the same direction as machine Y+. Small rotation is fine — the cal routine recovers it — but the sheet's X+ should not be pointing at machine Y+.
  3. Pick a working area where the spindle and camera both reach all five dots without colliding with fixturing.

A wavy or wrinkled sheet is the single biggest source of calibration error. Tape down all four corners and at least the center of each long edge. Heavier card-stock prints are noticeably better than 20-lb copy paper.

Calibration Procedure

  1. Mount the sheet as described above.
  2. Anchor the sheet to the spindle. Activate the spindle tool. Jog so the spindle tip is centered over dot 5 (the middle mark). Zero work-coord X and Y. The cal routine assumes the active WCS reads (0, 0) at dot 5 with the spindle.
  3. Tool-change to the camera. Issue Tn M6 for the camera tool number (matches VisionCameraToolNumber). The camera will land somewhere offset from dot 5 because the tool-table X/Y offsets are not calibrated yet — that is normal.
  4. Park over the start dot. Jog the camera in X/Y until dot 1 (lower-left, X-2.5 Y-4.0) is visible in the FOV. Centering is not required; the cal routine accepts any offset that keeps the dot in frame and that survives a 0.1″ bump in X and Y for the axis-detect step.
  5. Run the cal. Issue M283 C1. The routine:
    1. Auto-detects camera axis orientation (bumps +0.1″ X then +0.1″ Y, writes VisionCameraSwapXY / InvertX / InvertY).
    2. Walks dots 1 → 2 → 3 → 4 capturing pixel offset and machine position at each.
    3. Solves the rigid Procrustes fit and the per-axis K scale.
    4. Writes K to VisionCal_K11..K22.
    5. Moves to dot 5 (sheet center) and writes the camera tool's X/Y offsets so the camera lands on (0, 0) when commanded there.
    6. Re-walks all 5 dots in absolute G90 to report verify residuals.
  6. Inspect the result popup. Look for:
    • Scale X ≈ Scale Y — should be within ~1% (e.g. 0.00203 / 0.00203). Wildly different values mean the sheet is rotated 90°, the camera is mounted askew, or the print scale is bad.
    • Sheet rotation — small (< 1°). Larger means the sheet was taped down rotated, which is OK as long as it is consistent and small.
    • Verify RMS — typically ≤ 0.020″ on a printed sheet. Higher means the sheet is wavy, the lens is dirty, or the lighting is changing during the walk.

Example Cal G-Code

( Vision K-matrix calibration - cal sheet 1 )
( Spindle tool active, X/Y already zeroed over sheet center, )
( camera tool then activated and jogged over dot 1.          )

G90 G20 G17       ( absolute, inches, XY plane )
G54
T98 M6            ( camera tool - change number to match your setup )

M283 C1           ( walk sheet 1, solve K, write camera tool offset )

M30

Verifying the Calibration

After M283 C1 finishes, the camera tool's X/Y tool-table offset should put the camera over the commanded XY. Run the verify program in Example 2 to walk all five dots in absolute G90 with M280 / M281 / M282 active — the residual at the center capture should be < 0.005″ on a flat, well-printed sheet.

Re-run M283 C1 any time the camera is removed and remounted, the lens is touched, the working distance changes (different fixture height), or the verify residual drifts beyond your tolerance.

Camera Z Height & Working Distance

Why Z Height Matters

A smart camera does not measure distance — it measures pixels. The number of pixels per inch (or per mm) is set by the lens focal length and the working distance from the lens to the surface being imaged. The K matrix calibrated by M283 bakes that pixels-per-unit relationship in at the working distance present at calibration time. Move the camera closer or farther from the work surface and every pixel now represents a different real-world distance, so every M281 capture will report the wrong offset.

The Z distance from the camera lens to the top of the material being inspected MUST match the Z distance that was present when M283 C1 was run. Any change in standoff — thicker stock, different fixture, raised/lowered camera bracket, refocused lens — invalidates the calibration and requires a re-cal.

How Much Does It Matter?

For a typical machine-vision lens at a working distance WD, the pixel-to-unit scale changes roughly linearly with WD. A 10% change in standoff produces a ~10% change in measured offsets — on a 1.000″ commanded distance, that is a 0.100″ error. Even a 1% standoff change (about 0.060″ on a typical 6″ working distance) produces a 0.010″ scale error, which is enough to put the verify residual out of spec.

Best Practices

  • Rigid camera mount. The camera bracket must not deflect. A loose or springy mount lets vibration change the working distance and degrades repeatability.
  • Calibrate at the same Z you will inspect at. If the camera is on the spindle and is moved up/down with the Z axis, calibrate with the camera tool's Z offset and material thickness configured the way they will be at runtime. The cal sheet must be at the same height as the part top.
  • Match material thickness. If your production stock is 0.250″ thick, the cal sheet should sit on a 0.250″ spacer (or directly on the part fixture) so its top surface is at the same Z as the parts you will inspect.
  • Don't refocus after calibrating. Twisting the lens focus ring changes the optical magnification and silently invalidates K. If you must refocus, re-run M283 C1 immediately after.
  • Lock down the lens. Most VS-series lenses have a focus lock screw — tighten it after focusing.
  • Fixed-Z cameras are easiest. A camera mounted to the gantry or a column at a fixed Z (not the spindle) keeps standoff constant for all parts of a given thickness and is the most robust setup.

When to Re-Calibrate

Re-run M283 C1 whenever any of the following change:

  • The camera bracket is loosened, removed, or shimmed.
  • The lens is rotated, refocused, swapped, or replaced.
  • The aperture is opened/closed enough to noticeably change depth of field.
  • Material thickness changes by more than ~5% of the working distance.
  • The camera tool's Z offset is changed.
  • A different fixture, sub-plate, or vise raises/lowers the part top.
  • The verify-walk residual at the center dot starts drifting beyond your tolerance.

Symptom of a wrong Z: calibration converges with a small RMS residual but the production verify dot reports an offset that scales with distance from the work zero (e.g. small error near origin, large error at the far corners). That is a pure scale error and almost always means the working distance changed between cal and run.

M283 — Calibrate K Matrix

Overview

Calibrates the pixel-to-machine-units transform (the K matrix) and the camera-vs-spindle tool offsets. Two modes — exactly one of Q or C must be provided.

Both modes start by running an automatic axis-orientation check: the machine bumps +0.1 in X then +0.1 in Y, captures the resulting raw pixel deltas, and writes VisionCameraSwapXY / VisionCameraInvertX / VisionCameraInvertY so that subsequent triggers report machine-frame pixels. A mark must be in the FOV at the start, and the mark must remain in the FOV after a 0.1-unit bump in X and Y.

Parameters

LetterDescription
QQuick diameter calibration. Qd sets K = diag(d/width_px, d/height_px) using a single trigger over a printed dot of known physical diameter d. No motion (besides the axis-detect bump). Useful for first setup or quick checks — assumes the camera is roughly axis-aligned, no off-diagonal terms.
CCal-sheet walk. Cn selects sheet id n from VisionModule.CAL_SHEETS. The routine commands incremental moves between dots on the printed sheet, captures pixel offset at each, and solves the full 2x2 K by least-squares plus a rigid Procrustes fit. Also writes the camera tool's X/Y tool-table offsets so the camera can be commanded directly in work coords. Recommended.

M283 Q — Quick Diameter Calibration

  1. Activate the camera tool (Tn M6).
  2. Jog the camera so a printed dot is roughly centered in the FOV.
  3. Issue M283 Qd, where d is the dot's physical diameter (in current units).
  4. The routine runs the axis-detect bump, triggers the camera, computes K from the reported width/height, and re-triggers to confirm the residual.
( Quick cal: dot in FOV, half-inch printed diameter )
T98 M6
M283 Q0.5

M283 C — Cal-Sheet Walk (Recommended)

  1. Tape the printed cal sheet flat on the table, oriented as printed (X+ right, Y+ up).
  2. Activate the SPINDLE tool. Jog over the sheet center (dot 5) and zero work-coord X and Y. This anchors the sheet origin to the spindle.
  3. Activate the CAMERA tool (Tn M6). Jog so the camera FOV is over the start dot (dot 1, lower-left). The dot must be visible in frame — perfect centering is not required.
  4. Issue M283 C1 for sheet 1 (US Letter portrait, ±2.5 x ±4.0 in).

On success, a popup reports the per-axis scale, rotation, K matrix, sheet rotation, RMS residual, the tool-offset shift written to the camera tool, and the verify-walk residuals at each dot.

( Cal sheet walk - sheet center is current XY zero, camera tool active )
T98 M6
M283 C1

M284 — Select Camera Job

Overview

Switches the camera to a different program/job. Capability-gated — drivers that do not advertise select_job raise a clean error.

Parameters

LetterDescription
PJob id to switch to. If omitted (or P0), the persistent OEM VisionActiveJob is applied. On success, VisionActiveJob is updated.

Examples

M284 P3        ( switch to camera job 3 and remember it )
M284           ( apply VisionActiveJob )

Example G-Code Programs

Example 1 — Calibration (cal-sheet walk)

( Vision K-matrix calibration - cal sheet 1 )
( 1. Tape sheet flat, X+ right / Y+ up                                  )
( 2. Spindle tool active, jog over sheet CENTER, zero X and Y           )
( 3. Camera tool active, jog over START dot (lower-left -2.5,-4.0)      )

G90 G20 G17       ( absolute, inches, XY plane )
G54
T98 M6            ( camera tool - change number to match your setup )

M283 C1           ( walk sheet 1, solve K, write camera tool offset )

M30

Example 2 — Verify Calibration (5-dot walk in absolute G90)

( Vision alignment test - walks the 5-dot calibration sheet pattern )
( Sheet center = 0,0 in current work coords                         )
( Corners at (+/- 2.5, +/- 4.0) inch                                )
( Walk order: 1 -> 2 -> 3 -> 4 (4-point fit) -> 5 (center verify)   )

G90 G20 G17       ( absolute, inches, XY plane )
G54               ( use whatever WCS is zeroed at sheet center )
T98 M6            ( camera tool )

M280              ( begin 4-point alignment run )

G0 X-2.5 Y-4.0    ( dot 1 - lower left  )
M281

G0 X-2.5 Y4.0     ( dot 2 - upper left  )
M281

G0 X2.5 Y4.0      ( dot 3 - upper right )
M281

G0 X2.5 Y-4.0     ( dot 4 - lower right )
M281

M282              ( solve + apply modal G51 / G68 / G52 )

G0 X0 Y0          ( dot 5 - center, post-fit verify )
M281              ( capture; pound vars #500..#507 show residual )

( Inspect #500 (X), #501 (Y) - both should be ~0 if fit is good. )
( To clear the transform when done:                              )
( G69 G50 G52 X0 Y0                                              )

M30

Example 3 — Quick Diameter Cal

( Quick K calibration from a single dot of known diameter )
( Dot must be in the FOV when M283 Q runs.                 )

G90 G20
T98 M6
M283 Q0.5         ( half-inch printed dot )

M30

Example 4 — 4-Point Rectangle Fit on a Production Part

( Locate a 6 x 4 in part by its 4 corner fiducials, then run the cut )
( Operator drops the part on the fixture roughly square; fiducials   )
( are at the nominal corners (0,0), (6,0), (6,4), (0,4).             )

G90 G20 G17
G54
T98 M6                   ( camera tool )

M280                     ( default 4-point rectangle fit )

G0 X0    Y0
M281                     ( fid 1 )

G0 X6.0  Y0
M281                     ( fid 2 )

G0 X6.0  Y4.0
M281                     ( fid 3 )

G0 X0    Y4.0
M281                     ( fid 4 )

M282                     ( solve and apply modal transform )

T1 M6                    ( swap to cutter )
( ...your normal cutting program in part-design coords...        )
( ...G51/G68/G52 modally re-map every move to the actual part... )

( At end of program, cancel the transform: )
G69 G50 G52 X0 Y0
M30

Example 5 — 3-Point Similarity Fit

( Locate a part by 3 fiducials (similarity fit - shift, rotate, uniform scale) )

G90 G20 G17
G54
T98 M6

M280 P3                  ( 3-point similarity fit )

G0 X0    Y0
M281

G0 X8.0  Y0
M281

G0 X4.0  Y6.0
M281

M282                     ( apply )

T1 M6
( ...cutting program... )

G69 G50 G52 X0 Y0
M30

Example 6 — Reading the Result with Pound Variables

( Single-mark inspection: trigger and use the result in G-code math )
( Requires VisionPoundVarBase = 500 (default).                      )

G90 G20
T98 M6
M280                     ( begin run so M281 has somewhere to store the dot )

G0 X10.0 Y8.0
M280M281                     (read offset,capture; soft-fail#500=obs X, #501=obs Y, #502=W_px, #503=H_px )

( Apply the deviation as a fixture offset on cameraG55: error))
G10 L2 P2 X[#500] Y[#501]

M30

Example 7 — Switching Camera Jobs Mid-Program

(Halt Use a coarse fiducial finder for the programrough ifalignment, then switch  )
( to a precise circle-fit job for the final inspection step.         )

G90 G20
T98 M6

M284 P1                  ( camera failsjob or1 returns= NG)coarse finder )
M280
G0 X0 Y0
M281
G0 X10.0 Y8.0Y0
M280M281
P1

Behavior

  1. If KeyenceVisionEnabled is 0, the call is logged and returns success immediately.
  2. Opens a TCP socket to KeyenceVisionIP:KeyenceVisionPort with timeout KeyenceVisionTimeout.
  3. Sends TRG\r.
  4. Reads response lines, skipping the TRG echo and any empty lines, up to 8 lines total.
  5. Parses the first data line as either dx,dy (judgment defaults to -1) or judgment,dx,dy.
  6. Writes KeyenceVisionLastJudgment, KeyenceVisionLastOffsetX, KeyenceVisionLastOffsetY, and KeyenceVisionLastStatus.
  7. If P is non-zero and the camera errored or returned NG (judgment = 0), halts the program with w.Error().

If the configured Data Output emits only dx,dy (no judgment field), KeyenceVisionLastJudgment is set to -1. In that case M280 P1 will halt only on transport errors (timeout, refused, malformed response), not on a missing judgment field.

Reading the Result from G-code

After M280 completes, the operator screen and any G-code or macro script can read the result from the OEM parameters listed above. From a G-code subroutine or another macro, the values can be retrieved with the standard wrapper helpers:

local dx     = w.GetOEMParamValue("KeyenceVisionLastOffsetX")
local dy     = w.GetOEMParamValue("KeyenceVisionLastOffsetY")
local judge  = w.GetOEMParamValue("KeyenceVisionLastJudgment")
local status = w.GetOEMParamValueString("KeyenceVisionLastStatus")

Reading into Pound Variables with M242

M242 is the general MachMotion macro for reading an OEM parameter and writing the value into a pound variable. Use it after M280 to expose the Keyence result to standard G-code math.

Parameters:

  • V — the pound variable that will receive the value read from the OEM parameter.
  • (Data:"<ParamName>") — a G-code comment on the same line that names the OEM parameter to read.

Example — trigger the camera, then load each result into a pound variable:

G0 X10.0 Y8.Y6.0
M280M281
G0 X0    Y6.0
M281
M282                     (read offset)apply M242alignment V550using job 1 captures )

M284 P2                  (Data:"KeyenceVisionLastOffsetX") (#550camera job 2 = Xprecise offset)circle M242fit V551)
G0 X5.0 Y3.0
M281                     (Data:"KeyenceVisionLastOffsetY") (#551fine-grained =measurement Yat offset)part M242 V552 (Data:"KeyenceVisionLastJudgment")  (#552 = judgment)

(Now #550 and #551 can be usedcenter, in G-codealigned math,frame e.g.)

applyG69 correction)G50 G10G52 L2X0 P1Y0
X[#550] Y[#551]M30

M242 reads numeric OEM parameters. KeyenceVisionLastStatus is a string and should be read with w.GetOEMParamValueString() from a Lua script rather than with M242.

Error Handling and Diagnostics

Error Types

Symptom Likely Cause / Fix
timeout Camera unreachable, wrong IP/port, or no dataData Output (Non-Procedural) tool configured to emit on TRG.
connection refused Camera not in Run Mode, or another client already holds the socket.
NG / no mark foundThe configured camera job did not find a fiducial in the FOV. Re-jog over the mark, check lighting, verify the camera job is locating the right feature.
K matrix is zero -- run M283 to calibrate firstVisionCal_K11..K22 are all zero. Run M283 C1 (or M283 Q).
camera tool Tn is not the active toolVisionCameraToolNumber is set, but a different tool is active. Issue Tn M6 for the camera tool first, or clear the OEM to disable the check.
VisionPoundVarBase is in reserved rangeMove the base out of 5061..5081 or 5201..5500; pick a value ≥ 100.
ER,TRG,01 Unknown command — the cameraCamera does not accept TRG in its current mode.mode — not in Run Mode.
ER,TRG,06 Command not executable — the camera is not in Run Mode or the trigger source is not set to Communication.
ER,TRG,07 Camera-side timeout — measurement didtimed not completeout within the camera'sits configured window.
Malformed response The Data Output (Non-Procedural) tool is missing, disabled, or configuredemitting to emit something other than the expectedunexpected fields.

Status Storage

On every call (success or fail),trigger, KeyenceVisionLastStatusVisionLastStatus is updated.updated On failureOK iton containssuccess, otherwise the error texttext. returned from the parse or socket layer; on successSurface it contains "OK". Operators can surface this on the screen as a diagnosticscreen DRO.DRO for live diagnostics.

Axis Orientation Mismatch

TheIf the camera reportsis anmounted offsetrotated 90° or with one or both axes flipped relative to itsthe ownmachine, center,every nottrigger toreports pixels in the spindlewrong frame and downstream cal / fit residuals will be large or tool.non-converging. AnySymptoms mechanicalinclude offseta betweenresidual that grows with the camerabump distance instead of shrinking, or a sheet-rotation result around 90°.

Run M283 C1 (or M283 Q) with a mark in the FOV. The pre-flight axis-detect bump test automatically writes the correct values for VisionCameraSwapXY, VisionCameraInvertX, and the working tool must be accounted for separately (for example, by applying a head-shift or fixture-offset adjustment in G-code after reading KeyenceVisionLastOffsetXVisionCameraInvertY / KeyenceVisionLastOffsetY).

Protocol Reference

 (Keyence Driver)

The moduleKeyence driver implements a minimal subset of the Keyence VS Non-Procedural protocol. See the Keyence VS series user manual for the full command set.

connectivity
Command Description
TRG Trigger a single measurement. Camera echoes TRG\r and then emits the configured Data Output (Non-Procedural) line(s).
FVR Firmware version read. Used by the GUI Test Connection button.
KeyenceVisionModule.TestConnection()PR as/ aPW Program check.(job) read / write. Used by M284 when the driver advertises select_job.
  • Each command is terminated with CR (0x0D).
  • Maximum command size: 500 bytes.
  • Camera-side per-command timeout: 3 seconds.
  • Error responses follow ER,<cmd>,<nn>\r, where nn is a two-digit error code (01 unknown, 02 args, 03 range, 04 type, 05 not acceptable, 06 not executable, 07 timeout).

Camera vs. Tool Position

The camera reports an offset relative to its own optical center, not to the spindle or working tool. The cal-sheet walk (M283 C) handles this correctly by writing the camera-vs-spindle offset directly into the camera's tool-table entry. After cal, with the camera tool active, a commanded XY positions the camera at that XY. After tool-changing back to the cutter, the same XY positions the cutter at that XY — the alignment transform from M282 applies to both.

Appendix A — OEM Parameter Reference

Every OEM parameter the Vision system reads or writes, grouped by section as it appears in the Configuration → Vision screen. Parameters in the Last Result and Run State sections are runtime telemetry — they are written by the module on every capture / fit and are not intended to be edited by the operator. All names are prefixed with Vision and stored in the profile-level OEM database.

Connection

NameTypeDefaultDescription
VisionEnabledyesnoNoMaster enable. When No, every Vision M-code returns a clean "Vision is not enabled" error and no driver is loaded.
VisionDriverchoiceKeyenceDriver selector. Currently only the Keyence VS-series driver is shipped; new cameras are added by writing additional drivers under Modules/Addons/Vision/Drivers.
VisionIPtext192.168.208.80Camera IP address. The PC and the camera must be on the same subnet.
VisionPortinteger8500TCP port for the camera's data-output channel. Keyence VS default is 8500.
VisionTimeoutinteger (s)3Per-command socket I/O timeout. Increase only if the network is unreliable; longer timeouts mask real failures.
VisionActiveJobinteger0Persistent "current job" selector. Updated by M284 Pn. M284 with no argument re-applies whatever value is stored here.
VisionImageWatchDirtext(empty)Local folder the camera FTP-pushes inspection images to. Used by M281 D1 to grab and rename the most recent image.
VisionImageGrabTimeoutfloat (s)2.0How long M281 D1 waits for a new image to appear in the watch folder before giving up.
VisionCameraResXinteger (px)1776Camera image width in pixels. Used to convert from native top-left pixel coords to signed image-center offsets.
VisionCameraResYinteger (px)1776Camera image height in pixels. Same role as VisionCameraResX on the Y axis.
VisionTriggerSettleSecfloat (s)0.25Dwell M283 inserts after each commanded move and before triggering the camera. Lets motion settle so the captured pixel reading is stable. Production captures (M281) do not auto-dwell — precede them with a programmed G4 P if needed.
VisionCameraSwapXYyesnoNoCamera-to-machine axis remap: swap raw pixel X and Y. Auto-set by the M283 axis-detect pre-flight bump.
VisionCameraInvertXyesnoNoCamera-to-machine axis remap: negate raw pixel X (after any swap). Auto-set by M283.
VisionCameraInvertYyesnoNoCamera-to-machine axis remap: negate raw pixel Y (after any swap). Auto-set by M283.

Calibration

NameTypeDefaultDescription
VisionCal_K11float0Row 1, column 1 of the 2×2 K matrix. Pixels → machine-units in the X direction (plus any small camera/machine rotation cross-term goes into K12).
VisionCal_K12float0Row 1, column 2 of K. Captures small Y-pixel-into-X-units cross-coupling from camera/machine rotation.
VisionCal_K21float0Row 2, column 1 of K. Captures X-pixel-into-Y-units cross-coupling.
VisionCal_K22float0Row 2, column 2 of K. Pixels → machine-units in the Y direction.
VisionCal_Datetext (ISO)(empty)Local-time ISO timestamp written by M283 on a successful calibration. Useful as a screen DRO so an operator can see when the camera was last calibrated.
VisionCal_UnitschoiceinUnits the K matrix was calibrated in (in or mm). The Vision module checks this against the current G20/G21 mode at trigger time.
VisionPxCenterOffsetXfloat (px)0Optional bias added to the centered pixel X before applying K. Lets you correct for a camera whose optical center is not exactly the image-array center.
VisionPxCenterOffsetYfloat (px)0Same as VisionPxCenterOffsetX, on the Y axis.

Do not edit VisionCal_K11..K22 by hand. They are written atomically by M283; partial edits leave the camera mis-calibrated. Re-run M283 C1 instead.

Output

NameTypeDefaultDescription
VisionPoundVarBaseinteger500Base pound-variable index for M281 output. M281 writes 8 consecutive pound vars starting at this base (see Pound-Variable Output). Must be ≥ 100 and outside the reserved Mach ranges 5061..5081 and 5201..5500.
VisionCameraToolNumberinteger0Camera tool number used in the tool table. M280 / M281 / M282 / M283 verify this tool is the active spindle tool and abort otherwise. Set to 0 to disable the check (not recommended for production).

Last Result (telemetry — read-only)

NameUnitsDescription
VisionLastJudgment1=OK / 0=NGPass/fail flag from the most recent camera trigger.
VisionLastOffsetPxXpxRaw centered pixel offset X reported by the driver, after image-center shift but before swap/invert and K.
VisionLastOffsetPxYpxRaw centered pixel offset Y, same conventions as VisionLastOffsetPxX.
VisionLastOffsetXunitsPixel offset converted to machine units via the K matrix. This is the value M281 stores in pound var #base+0.
VisionLastOffsetYunitsPixel offset converted to machine units, Y axis.
VisionLastWidthunitsDetected feature width from the camera (pixels × X-axis K column norm), in current machine units.
VisionLastHeightunitsDetected feature height (pixels × Y-axis K column norm), in current machine units.
VisionLastAngledegDetected feature angle, if the camera job emits it.
VisionLastScoreCamera-side match score (driver-specific). Useful as a quality gate.
VisionLastStatustextOK on success, otherwise the error string. Surface as a screen DRO for live diagnostics.

Run State (telemetry — read-only)

NameUnitsDescription
VisionRun_Active0/11 between M280 and M282; 0 otherwise.
VisionRun_Mode4 / 34 = rectangle (4-point) fit, 3 = similarity (3-point) fit. Set by M280 P.
VisionRun_DotCountintegerHow many M281 captures have been collected so far.
VisionRun_Tx / VisionRun_TyunitsSolved shift, emitted as G52.
VisionRun_ThetaradiansSolved rotation, emitted as G68 R (converted to degrees on output).
VisionRun_Sx / VisionRun_SyscaleSolved per-axis scale (rectangle fit only), emitted as G51 X Y.
VisionRun_CenterX / VisionRun_CenterYunitsRotation/scale center, emitted as G68 X Y / G51.

Appendix B — Pixel-to-Machine-Units Math

This appendix walks the full transform chain a single capture goes through, from the camera's wire-format pixel reading to the observed point that M281 stores. Every step is implemented in VisionModule.lua / VisionMath.lua; the field references in parentheses point at the OEM parameters that drive each step.

Step 1 — Camera Native Pixels

The Keyence VS-series camera reports a hit as two floating-point pixel values: (px_native_x, px_native_y). The native frame is camera-defined — for the VS series, origin is the top-left of the image, X grows to the right, Y grows downward. Image dimensions are VisionCameraResX × VisionCameraResY pixels. The driver hands these values to the module unchanged.

Step 2 — Image-Center Shift

VisionModule.CenterRawPixels() converts the top-left-origin native pixel to a signed offset from the image center, and optionally applies a per-camera optical-center bias (VisionPxCenterOffsetX, VisionPxCenterOffsetY):

px_centered_x = px_native_x - (VisionCameraResX / 2) - VisionPxCenterOffsetX
px_centered_y = px_native_y - (VisionCameraResY / 2) - VisionPxCenterOffsetY

After this step, (0, 0) means the feature is exactly under the optical axis. The PxCenterOffset terms are normally zero — only set them if you have evidence the camera's optical center is biased away from the array center.

Step 3 — Camera-to-Machine Axis Remap

The camera can be physically mounted rotated 90° or with one axis flipped relative to the machine. The three boolean OEMs VisionCameraSwapXY, VisionCameraInvertX, VisionCameraInvertY normalize the centered pixel into the machine frame. They are applied in this order:

if VisionCameraSwapXY then
    swap(px_centered_x, px_centered_y)
end
if VisionCameraInvertX then px_centered_x = -px_centered_x end
if VisionCameraInvertY then px_centered_y = -px_centered_y end

After this step, "positive pixel X" means the same direction as "positive machine X." M283's pre-flight bump test (a small +X then +Y move) sets these three flags automatically by checking which raw pixel axis moved and which sign.

Step 4 — K Matrix (Pixels → Machine Units)

VisionMath.ApplyK() applies the calibrated 2×2 K matrix to convert from machine-frame pixels to machine units (inches or millimeters, per VisionCal_Units):

| dx_units |   | K11  K12 |   | px_centered_x |
|          | = |          | * |               |
| dy_units |   | K21  K22 |   | px_centered_y |

In an ideal install where the camera is square to the machine and the same scale on both axes, K = diag(s, s) where s is units-per-pixel. The off-diagonal terms K12 and K21 capture residual rotation between the camera array and the machine that the Step 3 boolean remap can't represent (because rotation is continuous, not 90°-discrete). They are normally small but nonzero after a real M283 C1 walk.

dx_units / dy_units is the offset from the optical axis to the detected feature, expressed in current G-code units. It is not a machine coordinate — it is a delta. Step 5 converts the delta into an absolute observed point.

Step 5 — Observed Point in Machine Coordinates

At the moment of the trigger, VisionModule reads the commanded XY from mc.mcAxisGetPos for the active WCS and adds the units-frame delta to get the observed point of the feature:

observed_x = commanded_x + dx_units
observed_y = commanded_y + dy_units

This is the value M281 stores into pound vars #base+0 / #base+1 and into the dot list used by M282. The fit step (M282) compares this observed point against the commanded point that was active when the trigger fired and solves a rigid-body transform from the commanded grid to the observed grid.

Full Transform — One Equation

Combining Steps 1–5, the observed point as a function of the camera's wire-format reading is:

observed = commanded + K * R * (px_native - res/2 - PxCenterOffset)

where R is the discrete ±1 / swap remap from Step 3, K is the continuous 2×2 calibration matrix from Step 4, and res/2 is the image-center vector. The cal-sheet walk in M283 C solves for both R (axis-detect bump) and K (Procrustes fit over the dot grid).

Where Each Step Lives in Code

StepFunctionFile
1. Native pxKeyenceVisionDriver:Trigger()Modules/Addons/Vision/Drivers/KeyenceVisionDriver.lua
2. Center shiftVisionModule.CenterRawPixels()Modules/Addons/Vision/VisionModule.lua
3. Swap / invertVisionModule.RemapToMachineFrame()Modules/Addons/Vision/VisionModule.lua
4. K matrixVisionMath.ApplyK()Modules/Addons/Vision/VisionMath.lua
5. Observed pointVisionModule.PxToMachine() + _m281VisionModule.lua / CommonMCodeModule.lua