Create Custom FreeCAD Path Post-Processors in Python
This guide explains how to build a custom post-processor for the
FreeCAD CAM (formerly Path) Workbench using Python. You will learn where
to store your post-processor script, how the core export
function works, how to parse FreeCAD toolpath commands into
machine-specific G-code, and how to format header, movement, and footer
blocks for your specific CNC controller.
Locating the Post-Processor Directory
FreeCAD searches for post-processors in its internal application directory and your user macro directory. To ensure your custom script persists across FreeCAD updates, place your script in the user macro directory:
- Linux:
~/.local/share/FreeCAD/Macro/or~/.FreeCAD/Macro/ - Windows:
%APPDATA%\FreeCAD\Macro\ - macOS:
~/Library/Application Support/FreeCAD/Macro/
Post-processor file names must end with _post.py (for
example, my_controller_post.py). Once saved in this
directory with the proper suffix, FreeCAD will automatically register
and display it in the CAM Job configuration dropdown.
Basic Script Structure and Metadata
A FreeCAD post-processor is a standard Python file containing
metadata definitions and an export() entry point.
Create a new file named custom_cnc_post.py and start
with the standard metadata header:
TOOLTIP = "Custom post-processor for specific CNC controller"
UNITS = "G21" # Default to metric (G21) or imperial (G20)
CORNER_MAX = None
CORNER_MIN = None
import FreeCAD
import Path
from Path.Post.Command import CommandThe TOOLTIP variable provides the description visible
inside FreeCAD’s Job Edit dialog under the Output
tab.
The export Function
The CAM workbench executes the
export(objectslist, filename, argstring) function when you
post-process a job. This function accepts three arguments:
objectslist: A list of FreeCAD document objects selected for post-processing (typically operations, tools, and compound paths).filename: The absolute destination path of the output file.argstring: Optional string arguments passed from the export dialogue.
Here is the fundamental implementation of the export
function:
def export(objectslist, filename, argstring):
out = open(filename, "w")
# Write machine preamble / safety block
out.write("%\n")
out.write("G90 G17 G21\n") # Absolute mode, XY plane, Metric
for obj in objectslist:
if not hasattr(obj, "Path"):
continue
# Parse the Path object
for command in obj.Path.Commands:
gcode_line = parse_command(command)
if gcode_line:
out.write(gcode_line + "\n")
# Write postamble / shutdown block
out.write("M5\n") # Spindle off
out.write("M30\n") # Program end
out.write("%\n")
out.close()
return filenameParsing Path Commands
FreeCAD stores toolpath data as a sequence of
Path.Command objects. Each command contains:
command.Name: The movement or state instruction (e.g.,G0,G1,G2,G3,M3,M5,message).command.Parameters: A dictionary holding the axes coordinates and feed values (e.g.,{'X': 10.0, 'Y': 5.5, 'F': 1200}).
Implement a command parsing function to translate these internal objects into properly formatted text:
def parse_command(command):
name = command.Name
params = command.Parameters
# Ignore comments or internal FreeCAD markers
if name in ["message", "comment"]:
return f"({params.get('message', '')})"
# Format coordinate values to 3 decimal places
line = [name]
for axis in ["X", "Y", "Z", "A", "B"]:
if axis in params:
line.append(f"{axis}{params[axis]:.3f}")
# Format feed rate and spindle speed as integers
if "F" in params:
line.append(f"F{int(params['F'])}")
if "S" in params:
line.append(f"S{int(params['S'])}")
if "T" in params:
line.append(f"T{int(params['T'])}")
# Return space-delimited G-code instruction
return " ".join(line)Handling Arc Commands (G2 and G3)
Circular interpolation commands (G2 for clockwise,
G3 for counter-clockwise) require center offsets relative
to the start point (I, J, K) or
absolute radius (R).
Extend the parsing function to include arc offsets:
if name in ["G2", "G3"]:
for offset in ["I", "J", "K", "R"]:
if offset in params:
line.append(f"{offset}{params[offset]:.3f}")Loading and Testing the Post-Processor
- Save your completed file as
my_machine_post.pyinto your FreeCADMacrofolder. - Restart FreeCAD or reload macros via the Python console using
import my_machine_post. - Open a document containing a CAM Job.
- Double-click the Job in the tree view to edit it, and navigate to the Output tab.
- In the Post Processor dropdown, select
my_machine. - Click the Post Process button on the CAM toolbar, select your output location, and verify that the output G-code matches your controller's formatting rules.