Lua Module - Postprocessor
This chapter is about customising and creating new postprocessors for send2cnc.
Postprocessor script
A postprocessor is basically a Lua script file that contains special commands and functions to convert the toolpaths calculated by send2cnc into a suitable format for the CNC machine.
By default, the scripts are stored in the "postprocessor" subfolder of the Program Data folder (see chapter "Installation"). New scripts can be added and managed in this folder.
Before creating or modifying a postprocessor, make sure that it is saved under a unique name in the "postprocessor/user" subfolder of the above-mentioned directory. Otherwise, modified postprocessors could be overwritten when updating or installing a new postprocessor package.
Script structure
The functions described in this section are called up by the system for each post-processing run and should be defined in each post-processor.
Overview
ppStart
Function | function ppStart( [clsobj] prj, [clsppdata] job, [clsppdata] jobnext) |
---|---|
Arguments |
[clsobj] prj: The project containing the jobs to be processed. [clsppdata] job: The first job item to be processed. [clsppdata] jobnext: The next job item to be processed. |
Return Value | nil |
Description | When a post-processor run is started with s2.pp.run(objlist), this function is called by the system with the system-provided arguments. |
Example |
|
ppJobStart
Function | function ppJobStart(prj, joblast, job, jobnext) |
---|---|
Arguments |
[clsobj] prj The project containing the jobs to be processed. [clsppdata] joblast The previously processed job item or "nil" if not available. [clsppdata] job The job item to be processed. [clsppdata] jobnext The next job item to be processed or "nil" if there is none. |
Return Value | nil |
Description | This function is called by the system every time a new job is started. |
Example |
|
ppMove
This function is called for each individual toolpath point. Since this could mean millions of calls in extreme cases, computationally intensive tasks should be avoided. User messages within this function should be avoided entirely.
Function | function ppMove(job,x,y,z,gflag,mflag) |
---|---|
Arguments |
[clsppdata] job The job item to be processed. [number] x,y,z The coordinates of the next toolpath point as floating point numbers. [integer] gflag Bit mask to determine the feed mode [integer] mflag Bit mask for determining the spindle |
Return Value | nil |
Description | This function is called by the system every time a new job is processed. |
Example |
|
gflag, mflag
These arguments contain additional information about the toolpath point. They are bit masks in integer format, which can be checked for certain states using the bit operator "&". Global variables provided by send2cnc can be used for checking. The following is a listing of possible queries:
Check | Description |
---|---|
if(gflag&FLAG_G0) | The toolpath point is part of a positioning move. The regular rapid feedrate should be used. |
if(gflag&FLAG_G1) | The toolpath point is part of a regular milling move. The regular milling feedrate should be used. |
if(gflag&FLAG_G1_RAMP) | The toolpath point is part of a ramped plunge move. The plunge feedrate should be used. |
if(gflag&FLAG_G1_PLUNGE) | The toolpath point is part of a perpendicular plunge move. The drill feed should be used. |
if(mflag&FLAG_M_SPINDLE_START) | The spindle should be started before moving to the toolpath point. |
ppJobEnd
Function | function ppJobEnd(prj, joblast, job, jobnext) |
---|---|
Arguments |
[clsobj] prj The project containing the jobs to be processed. [clsppdata] joblast The previously processed job item or "nil" if not available. [clsppdata] job The job item to be processed. [clsppdata] jobnext The next job item to be processed or "nil" if there is none. |
Return value | nil |
Description | Called by the system at the end of each job. |
Example |
|
ppEnd
Function | function ppEnd(prj, joblast, job) |
---|---|
Arguments |
[clsobj] prj The project containing the jobs to be processed. [clsppdata] joblast The previously processed job item or "nil" if not available. [clsppdata] job The job item to be processed. |
Return value | nil |
Description | This function is called by the system at the end of each postprocessor run (after ppJobEnd). |
Example |
|
ppMain
function | function ppMain(objlist, prj) |
---|---|
Arguments |
[clsobjlist] objlistAn object list with all the job objects to be processed [clsobj] prj The project containing the jobs to be processed. |
Return value | nil |
Description | This is the entry point for the post-processor script. The object list to be processed is passed to this function. In general, this function starts the actual post-processing run (see example). |
Example |
|
Script process
The process of a script in send2cnc is divided into several phases.
- Initialisation: At the beginning, the system records all the functions defined in the script, but does not execute them yet.
- Main function (ppMain): The system calls the function ppMain() and passes the object list to be processed as an argument.
- Post-processing run: Within ppMain() the function s2.pp.run(objlist) is typically called. This starts the actual post-processing run and ensures that all relevant functions are executed by the system in a loop until all objects have been processed.
Example
An example with two jobs to be processed:
- ppMain(jobliste) --is called by the system
- s2.pp.run(jobliste) --must be called by the post-processor script within ppMain()
- (the following functions are all called by the system)
- ppStart()
- ppJobStart("job1")
- ppMove()
- ppMove()
- ppMove()
- ...
- ppJobEnd("job1")
- ppJobStart("job2")
- ppMove()
- ppMove()
- ppMove()
- ...
- ppJobEnd("job2")
- ppEnd()
- s2.pp.run(jobliste) --must be called by the post-processor script within ppMain()
Further API functions
All further functions and methods of the send2cnc API are listed in the chapter Lua script, in particular the methods for a job object (clsppdata).
Examples
This simple example illustrates the script process. The output of the movements is limited to 3 lines to keep the structure display in the console clear.

local limitmove = 0
function ppStart( prj, job, jobnext) --executed within s2.pp.run(objlist) -> see ppMain()
s2.printnl("start pp run in project <", prj:get("nameProject"), ">")
end
function ppJobStart(prj, joblast, job, jobnext) --executed every time a new job starts
s2.printnl("\tstart of ", job:get("nameJob"))
limitmove = 0
end
function ppMove(job,x,y,z,gflag,mflag) --executed for each toolpath point
if limitmove > 2 then return end
s2.print("\t\t")
if x ~= nil then s2.print("X ", x," ") end
if y ~= nil then s2.print("Y ", y," ") end
if z ~= nil then s2.print("Z ", z," ") end
s2.nl()
limitmove = limitmove + 1
end
function ppJobEnd(prj, joblast, job, jobnext) --executed after the last movement of the current job
s2.printnl("\tend of ", job:get("nameJob"))
end
function ppEnd(prj, joblast, job) --executed after all jobs have been completed
s2.printnl("pp run finished")
end
function ppMain(objlist, prj) – entry point
s2.pp.run(objlist) – start postprocessor run (ppStart, ppJobStart, ppMove, ppJobEnd, ppEnd)
s2.success("Done!")
end
Example B
This advanced example shows a simple post-processor script for outputting G-code in the console and/or file (not intended for productive use).

-------------------------------------------------------------------------------
-- This template is a very minimal example script and is not intended for productive use.
-------------------------------------------------------------------------------
local pushToConsole = true -- Write NC Code to Console (if set it's recomented to set debugLimiter also)
local pushToFile = true -- Write NC Code to File
local debugLimiter = 50 -- Set this value to limit ppMove to maximum lines (very useful for debugging).
local debugCounter
local ncbuffer
local codeFeed
local codeFeedRamp
local codeFeedDrill
local codeFeedRapid
function ppStart (prj, job, jobnext) --executed within s2.pp.run(objlist) -> see ppMain()
s2.clear() – clear console
ncbuffer = s2.txtBuf() – create empty text buffer
ncbuffer:addnl("G17 G40 G49 G80 G90 G94") – write NC code header at beginning of the text buffer
ncbuffer:addnl("G21 (mm)")
end
function ppJobStart (prj, joblast, job, jobnext) --executed every time a new job starts
if job:get("collision") then s2.abort("collision detected") return end --abort in case of a collision
debugCounter = 0
codeFeed = string.format("G1 F%d", job:get("feed") or 0) -- save codesnippet for the feedrate
codeFeedRamp = string.format("G1 F%d", job:get("feedRamp") or 0) -- (used frequently in ppMove)
codeFeedDrill = string.format("G1 F%d", job:get("feedDrill") or 0)
codeFeedRapid = string.format("G0")
if job:get("feedMode") ~= 0 then
codeFeedRapid = string.format("G1 F%d", job:get("feedRapid") or 0)
end
ncbuffer:addnl("(start job "", job:get("nameJob"), "")") --write jobname as NC Comment to textbuffer
ncbuffer:addnl(string.format("T%d M06 (%s)\n", job:get("toolId"),job:get("nameTool"))) --Tool call
end
function ppMove (job,x,y,z,gflag,mflag) --executed for each toolpath point
if debugLimiter > 0 and debugCounter == debugLimiter then
ncbuffer:addnl("M5 M0\n(...skipped)") --debugLimiter -> write NC Stop to textbuffer
debugCounter = debugCounter + 1
end
if debugLimiter > 0 and debugCounter >= debugLimiter then return end --Limit calls in debugmode (debugLimiter)
if mflag ~= nil then --check cooling and spindle start and write to textbuffer
if (mflag & FLAG_M_SPINDLE_START) ~= 0 then
speed = math.tointeger(job:get("spindleSpeed"))
if speed == nil or speed <= 0 then
s2.abort("no spindlespeed set in job <",job:get("nameJob"),">" )
return
end
if job:get("spindleRot") == 0 then ncbuffer:addnl("S", speed, "M03")
else ncbuffer:addnl("S", speed, "M04")
end
if job:get("cooling") == 1 then ncbuffer:addnl("M08")
elseif job:get("cooling") == 2 then ncbuffer:addnl("M09")
end
end
end
local gcode = nil --check if feedmode changed
if gflag ~= nil then
if (gflag&FLAG_G1_PLUNGE) ~= 0 then gcode = codeFeedDrill
elseif (gflag&FLAG_G1_RAMP) ~= 0 then gcode = codeFeedRamp
elseif (gflag&FLAG_G1) ~= 0 then gcode = codeFeed
elseif (gflag&FLAG_G0) ~= 0 then gcode = codeFeedRapid
end
end
if gcode ~= nil then gcode = gcode .. " " end --create string for coordinates
if x ~= nil then x = string.format("X%g ", x) end
if y ~= nil then y = string.format("Y%g ", y) End
if z ~= nil then z = string.format("Z%g ", z) End
ncbuffer:add((gcode or "") .. (x or "") .. (y or "") .. (z or "").. "\n") -- write movement to textbuffer
debugCounter = debugCounter + 1
end
function ppJobEnd (prj, joblast, job, jobnext) --executed after the last movement of the current job
if job:get("pause") then
ncbuffer:addnl("M00")
end
ncbuffer:addnl("(end of job "", job:get("nameJob"), "")")
end
function ppEnd (prj, job) --executed after all jobs have been completed
ncbuffer:addnl("M30") --write "nc end and rewind" in the end of the textbuffer
outpath = s2.path.build(prj:get("dirProject"),s2.path.basename(prj:get("nameProject"))..".txt") --build savepath (projectfolder\projectname.txt)
s2.printnl("saving nc code to file <", outpath, ">")
if pushToFile then ncbuffer:save(outpath) end --save textbuffer to disc
if pushToConsole then ncbuffer:print() end --write textbuffer to console
end
function ppMain(objlist, prj) --entry point
s2.pp.run(objlist) --start postprocessor run (ppStart, ppJobStart, ppMove, ppJobEnd, ppEnd)
s2.success("Done!")
end