Postprocessor Customization

Overview

The CAM workbench stores each Toolpath as a sequence of G-code commands. The commands describe straight moves, circular arcs, feed values, speed values and machine functions. A more complex curve, such as a B-spline, is already approximated by lines and arcs in the Toolpath.

A Post-Processor (Post) translates these commands into a complete file for the control of one machine. The file is usually G-code in the dialect of the control. A Post can also write a different language, such as HPGL, DXF or Heidenhain Klartext.

This page explains how to change a Post or write a new one. Post Process is the command that runs a Post.

Two kinds of Post-Processor

The workbench has two kinds of Post.

  • A legacy script Post is a Python module with the function export(objectslist, filename, argstring) and an argparse parser. The Processor field of the Job selects the Post. The Arguments field of the Job configures the Post.

  • introduced in 26.3 A machine-based Post is a class that inherits from Path.Post.Processor.PostProcessor. The module sets the constant POST_TYPE = "machine". A Machine selects the Post. The .fcm file of the Machine and the property schema of the Post configure the Post.

Only a machine-based Post can post an Operation on a tilted Work Plane. A legacy Post stops the export for such an Operation. A Work Plane parallel to the table posts with the two kinds of Post.

Functions of a Post-Processor

The output for a control can be different from the internal commands in these areas:

  • The start sequence of the program.

  • The stop sequence of the program.

  • The arc centre, which is relative or absolute.

  • The line numbers and their format.

  • The canned cycles for standard sequences, such as a drill cycle.

  • The output units, metric or imperial.

  • The moves before a tool change that help the operator.

  • The comments, which make the program easier to read and also make the file larger.

  • The header that identifies the program.

Preparation

Make a small Model that shows how the control reads straight lines and arcs. Prepare a reference program for the same geometry with software that is known to operate correctly with the machine.

A Toolpath that starts at (0, 0, 0) and moves in the Y direction is a good first test. The centre of the Tool Bit must follow the Toolpath, thus do not apply tool radius compensation. One level in the XY plane is sufficient for the first test.

The figure shows a sketch of such a test geometry.

Sketch of the test geometry: straight lines and arcs that start at the origin and go in the Y direction

In the 3D view, a small blue arrow at the start point shows the start direction of the Toolpath.

The figure shows the arrow on the first move of the Toolpath, where the pointer is.

Toolpath of the test geometry in the 3D view, one level in the XY plane, with the blue arrow near the start point

Compare the reference program with the output of an included Post, such as linuxcnc_legacy_post.py or grbl_post.py. Then adapt the Post that gives the nearest output.

Naming convention

The file name of a Post is <name>_post.py. The suffix _post.py must be lower case. The Post name is the file name without _post.py.

  • A script Post defines export() at module level.

  • A class Post defines a class whose name is the title-cased Post name. Examples: linuxcnc gives Linuxcnc, mach3_mach4 gives Mach3_Mach4, masso_g3 gives Masso_G3.

  • A class with a different name gets a module-level alias with the title-cased name. The heidenhain Post contains the line Heidenhain = HeidenhainPost. The generic_sheet_cutting Post contains the line Generic_Sheet_Cutting = GenericSheetCutting.

  • A class Post with POST_TYPE = "machine" is available in the Machine Editor. A class Post without that constant is a legacy Post and is available in the Processor list of the Job.

In a script Post, set prog of the argparse parser to the Post name. The tooltip of the Arguments field of the Job then shows the correct help text.

import argparse

parser = argparse.ArgumentParser(prog="grbl", add_help=False)

Included Post-Processors

The included Posts are in the folder <FreeCAD>/Mod/CAM/Path/Post/scripts. Post Process lists the included Posts by kind. Post-Processor Genealogy lists the class of each class Post.

These Posts are good examples to read:

  • linuxcnc_legacy_post.py: a script Post for LinuxCNC.

  • grbl_post.py: a class Post for grbl.

  • generic_post.py: the smallest machine-based Post. Use this file as the start of a new machine-based Post.

  • linuxcnc_post.py: a machine-based Post with two Job-scope properties.

  • fanuc_post.py: a machine-based Post that changes many defaults of the common properties and adds its own properties.

  • generic_sheet_cutting_post.py: a machine-based Post for plasma, laser and waterjet machines. The Post changes the Toolpath commands before the conversion.

  • heidenhain_post.py: a machine-based Post that does not write G-code. The Post replaces each conversion function and writes Klartext blocks.

Legacy script Post-Processor

This section applies to the *_legacy_post.py files and to other script Posts. The example shows the structure of a script Post.

import Path.Post.Utils as PostUtils
from PathScripts import PathUtils


def export(objectslist, filename, argstring):
    gcode = ""
    for obj in objectslist:
        gcode += parse(obj)
    if filename != "-":
        with open(filename, "w") as gfile:
            gfile.write(gcode)
    return gcode


def parse(pathobj):
    out = ""
    if not hasattr(pathobj, "Path"):
        return out
    path = PostUtils.cannedCycleTerminator(PathUtils.getPathWithPlacement(pathobj))
    for c in path.Commands:
        words = [c.Name]
        for name, value in c.Parameters.items():
            words.append(f"{name}{value:.3f}")
        out += " ".join(words) + "\n"
    return out

The export() function collects the output text for all objects: Operations, Tool Controllers and Fixture items. The function writes the header, the comments, the preamble and the postamble. The function gives each object that has Toolpath commands to parse().

The parse() function reads each command of one object and makes one line of text for the command. The Name of a command is the G or M word. The Parameters of a command are its other words. An included script Post records the last command name and thus suppresses a modal command that occurs again. An included script Post also has a linenumber() function. That function returns the line number text when line numbers are on, and an empty string when they are off.

These rules apply to a script Post:

  • The export() function returns the output text. When filename is -, the function does not write a file.

  • The export gives the script each Toolpath in world coordinates. PathUtils.getPathWithPlacement() does not apply the Work Plane a second time.

  • Call PostUtils.cannedCycleTerminator() on the Toolpath before the command scan. A Drilling Operation records the retract mode only as an annotation on each cycle command. cannedCycleTerminator() adds a literal G98 or G99 before each group of canned cycles and G80 after the group. A script that does not call the function does not see G99.

  • A cycle with no retract annotation gets the last literal G98 or G99 of the Toolpath. When the Toolpath has none, the cycle gets G98.

A class Post that uses arguments does not contain parse(). The class calls the shared functions Path.Post.UtilsArguments.init_shared_arguments, Path.Post.UtilsExport.export_common and Path.Post.UtilsParse.parse_a_path. The class replaces init_values() and init_argument_defaults() to change the defaults.

Common arguments

A class Post that uses arguments accepts the arguments below in the Arguments field of the Job. Many arguments have a --no- form, for example --header and --no-header.

Area Arguments

Units and precision

--metric, --inches, --precision N, --axis-precision N, --feed-precision N

Header and comments

--header, --comments, --comment_symbol, --output_machine_name, --output_path_labels, --list_tools_in_preamble, --bcnc

Format

--line-numbers, --line_number_start, --line_number_increment, --command_space, --end_of_line_characters, --modal, --axis-modal

Blocks

--preamble, --postamble, --post_operation, --finish_label, --return-to=x,y,z

Machine functions

--tlo, --tool_change, --enable_coolant, --disable_coolant, --enable_machine_specific_commands, --wait-for-spindle S

Conversion

--translate_drill, --chipbreaking_amount, --split-arcs

Interface

--show-editor, --output_all_arguments, --output_visible_arguments

A script Post defines its own set of arguments. The linuxcnc_legacy Post accepts --no-header, --no-comments, --line-numbers, --no-show-editor, --precision, --preamble, --postamble, --inches, --modal, --axis-modal, --no-tlo and --rigid-tap.

Machine-based Post-Processor

introduced in 26.3: A machine-based Post is a class that a Machine selects. Writing a Machine-Based Post-Processor is the full developer reference. Machine Configuration describes the Machine file. This section is the short version.

Start from generic_post.py. The example shows the minimum form.

from Path.Post.Processor import PostProcessor, SCOPE_JOB, SCOPE_MACHINE

POST_TYPE = "machine"


class Mymachine(PostProcessor):
    @classmethod
    def get_common_property_schema(cls):
        props = super().get_common_property_schema()
        for prop in props:
            if prop["name"] == "file_extension":
                prop["default"] = "ngc"
            elif prop["name"] == "preamble":
                prop["default"] = "G17 G54 G40 G49 G80 G90"
        return props

    @classmethod
    def get_property_schema(cls):
        return [
            {
                "name": "blend_mode",
                "scope": SCOPE_JOB,
                "type": "choice",
                "label": "Path Blending Mode",
                "default": "BLEND",
                "choices": ["EXACT_PATH", "EXACT_STOP", "BLEND"],
                "help": "The blend mode that the control uses between moves.",
            },
            {
                "name": "automatic_tool_changer",
                "scope": SCOPE_MACHINE,
                "type": "bool",
                "label": "Automatic Tool Changer",
                "default": False,
                "help": "The machine has an automatic tool changer.",
            },
        ]

    def __init__(self, job, tooltip="My machine post processor", tooltipargs=[], units="Metric"):
        super().__init__(job=job, tooltip=tooltip, tooltipargs=tooltipargs, units=units)

    def init_values(self, values):
        super().init_values(values)
        values["MACHINE_NAME"] = "My machine"
        values["POSTPROCESSOR_FILE_NAME"] = __name__

Property schema

A machine-based Post declares its settings as Post-Processor Properties in two class methods.

  • get_common_property_schema() returns the properties that every machine-based Post has. A Post replaces this method to change a default or a help text, as the example shows. Machine lists the common properties.

  • get_property_schema() returns the properties that only this Post has.

Each schema entry is a dictionary with these keys:

Key Content

name

The name of the property. The Machine file stores the value by this name. The Post reads the value from self.values with the name in upper case.

type

One of bool, int, float, str, text, choice or file. The common schema also uses string.

label

The label that the interface shows.

default

The default value.

scope

The Property Scope: machine, job, run or internal.

help

The help text.

choices

The permitted values of a choice property.

min, max

The limits of an int or float property.

decimals

The number of decimal places of a float property.

Path.Post.Processor supplies the scope values as the constants SCOPE_MACHINE, SCOPE_JOB, SCOPE_RUN and SCOPE_INTERNAL. The scope sets where the property is edited.

Scope Where the property is edited

machine

The Machine Editor only. Use this scope for a property that describes the machine or its control.

job

The Machine Editor sets the default. The Options tab of the Post-Processing Dialog overrides the default for one run.

run

The Overview tab of the Post-Processing Dialog only. The value is not stored.

internal

No interface shows the property. The Post derives the value.

These rules apply to the scope key:

  • An entry with no scope key has Job scope.

  • An entry with "runtime": True and no scope key has Run scope.

  • An entry with an unknown scope value has Job scope. The export writes a warning to the log.

The function property_scope(prop) returns the scope of one entry. The function properties_in_scope(schema, *scopes) returns the entries that have one of the given scopes.

The Machine file stores the changed values in postprocessor.properties. Machine Configuration gives the priority of the value sources.

Properties of the included Post-Processors

The table lists the properties that the included machine-based Posts add to the common properties.

Post Property Scope Values and default

linuxcnc

Path Blending Mode

Job

EXACT_PATH, EXACT_STOP, BLEND. Default: BLEND.

linuxcnc

Blend Tolerance

Job

Default: 0.0.

linuxcnc

Tilted work plane: kinematics select

Machine

Default: empty.

opensbp

Automatic Tool Changer

Machine

Default: unchecked.

opensbp

Automatic Spindle Control

Machine

Default: unchecked.

generic_sheet_cutting

Pierce Delay

Job

Default: 1000.

generic_sheet_cutting

Cooling Delay

Job

Default: 500.

generic_sheet_cutting

Marking Delay

Job

Default: 100.

generic_sheet_cutting

Control Method For Cutter

Job

Z_Control, Spindle_Control, G0_Control. Default: Z_Control.

generic_sheet_cutting

Strip Z Parameters

Job

Default: unchecked.

generic_sheet_cutting

Strip F Parameters

Job

Default: unchecked.

generic_sheet_cutting

Force Rapid Feeds

Run

Default: unchecked.

generic_sheet_cutting

Mark Entry Points Only

Run

Default: unchecked.

heidenhain

Program Name

Job

Default: empty. An empty value uses the Job label.

heidenhain

Output BLK FORM

Job

Default: checked.

heidenhain

Tool Axis

Machine

X, Y, Z. Default: Z.

heidenhain

Rapid Feed Rate (0 = FMAX)

Machine

Default: 0.

heidenhain

Drill Cycle Format

Machine

q_parameter, legacy. Default: q_parameter.

heidenhain

Drill Cycle Set-Up Clearance

Machine

Default: 2.0.

heidenhain

Chip Breaking Retract

Machine

Default: 0.5.

heidenhain

Fixture Output

Machine

preset, datum, none. Default: preset.

heidenhain

Fixture Number for G54

Machine

Default: 1.

fanuc

Program Number

Job

Default: 0. The value 0 writes no O line.

fanuc

Tape Marks (%)

Machine

Default: checked.

The linuxcnc Post-Processor writes tilted Work Planes, and Tilted work plane: kinematics select (twp_kinematics_select) is a part of that output. linuxcnc Post-Processor describes the output and the property.

The Control Method For Cutter values of the generic_sheet_cutting Post have these effects:

  • Z_Control: the Post writes M3 on a downward Z move and M5 on an upward Z move.

  • G0_Control: the Post writes M3 on a change from G0 to G1 and M5 on a change from G1 to G0.

  • Spindle_Control: the Post writes each M3 and M5 command of the Toolpath without a change.

The opensbp Post sets the default of the common property Ignore G-code Commands to its own list of tolerated commands. The opensbp Post always expands the drill cycles into moves.

Export sequence

The entry point of a machine-based Post is export2(). The method does these stages in sequence:

  1. Shows the dialog of pre_processing_dialog().

  2. Builds the ordered list of items to post: Fixture items, Tool Controller items and Operations.

  3. Calls _expand_postprocessor_commands(). A Post replaces this method to change the commands of the items.

  4. Puts each Toolpath into the coordinates that the Machine runs and adds the rotary moves and the plane commands (_expand_workplane_frames()).

  5. Expands the commands with the expand* methods. The expansions cover the blocks, the canned cycle end, the drill cycle expansion, the arc split, the rapid move translation, the tool change and the bCNC blocks.

  6. Removes modal duplicates (_optimize_duplicates_doubles()).

  7. Adds line numbers (_add_line_numbers()).

  8. Converts each command to text with the convert* methods and applies the line end characters.

All stages before the conversion operate on Path.Command objects. No stage changes the text after the conversion.

A Post replaces these methods to change the output:

  • _merge_machine_config(): sets values that the control makes necessary, after the Machine values are applied. Call the method of the parent class first.

  • expand_prefix(), _expand_trailing_lines() and the other _expand* methods: change one expansion stage.

  • _expand_tool_length_offset_post_command(item, command): returns the commands that apply the length offset of the Tool Bit after an M6 command. The default returns G43 H<tool>.

  • _convert_rapid_move(), _convert_linear_move(), _convert_arc_move(), _convert_drill_cycle(), _convert_dwell(), _convert_tool_change(), _convert_spindle_command(), _convert_coolant_command(), _convert_program_control(), _convert_fixture(), _convert_modal_command() and _convert_generic_command(): convert one command to text. A method that returns None writes no line.

  • _convert_job_sections(): changes the complete text of each section.

  • get_sanity_checks(job): returns the Squawks of the Post for the Sanity Check. The Warnings tab of the Post-Processing Dialog and Quick Validate show them.

The heidenhain Post replaces every conversion method, because no Klartext block has the form of a G-code command. The same Post replaces _add_line_numbers() and gives each block a number in _convert_job_sections().

Tool change

The Post writes the output of a tool change in this sequence:

  1. The Pre-Tool Change block.

  2. The tool change comment.

  3. The M6 command.

  4. The Post-Tool Change block.

  5. The G43 command, when TLO after tool-change is checked.

  6. The spindle start command.

  7. The Tool Return after tool changes block.

When Allow tool-change is unchecked, the Post does not write M6 and does not write the Post-Tool Change block. The Post writes the Tool Return after tool changes block in the two conditions. When TLO after tool-change is unchecked, the Post replaces each G43 command of the Toolpath with a comment.

Drill cycles

The Translate Drill Cycles option of the Machine expands drill cycles into G0 and G1 moves. The common property Drill Cycles to Translate lists the cycles that the Post expands. The default list is G73, G81, G82, G83 and G85. The common properties contain no other switch for the expansion.

The Post ends each group of canned cycles before the expansion. The expansion thus knows the retract mode of each cycle. A G85 cycle becomes a G1 move to the depth, a G1 move to the retract height and a G0 move to the clearance height.

A Post for a control that runs every cycle sets the default of Drill Cycles to Translate to an empty list. The fanuc Post and the heidenhain Post do this.

Commands and rapid moves

  • Supported G-code Commands (supported_commands) lists the commands that the Post accepts. A command that is not in the list stops the export with the error Unsupported command: and the command name.

  • Ignore G-code Commands (ignored_commands) lists commands that the Post accepts and removes from the output. A command in this list is not necessary in Supported G-code Commands.

  • translate_no_engagement_feed changes a marked G0 move into a G1 move. An Operation that has a no-engagement feed marks these moves with that feed. Default: unchecked.

The export does not stop for these items when they are not in Supported G-code Commands:

  • A command in Ignore G-code Commands.

  • A comment.

  • A bare T word.

  • A command with the allow_unsupported annotation.

The Post compares a command name with Supported G-code Commands and Ignore G-code Commands as a full command name. Each line of the two properties is one name. The Post removes the space characters at the two ends of each line. A name does not match a part of a different name: G1 does not match the entry G17, and G5 does not match the entry G54.

The common default of Supported G-code Commands contains G17, G18 and G19, which select the arc plane. A Post that uses the common conversion writes each of these commands as one line. Scripting lists the supported commands. The opensbp Post accepts G17 and does not accept G18 and G19, as Post Process describes; the heidenhain Post accepts only G17 and writes no block for it.

When Duplicate Commands of the Machine is unchecked, the Post removes a command word that is the same as the command word before it. When Duplicate Parameters is unchecked, the Post removes an axis parameter whose value did not change. A tool change and an item of plain text reset this state. A comment does not change this state.

The Post also follows the arc plane (G17, G18, G19). These rules apply:

  • The arc plane is G17 at the start of each output section.

  • A G17, G18 or G19 command in the Toolpath changes the arc plane.

  • A text block that the Post writes without change can contain a G17, G18 or G19 word. The preamble and a command with the as-is annotation are such text blocks. The last of these words in the text block sets the arc plane.

  • A text block with no G17, G18 or G19 word does not change the arc plane.

  • A tool change does not change the arc plane.

An arc gives its center in its arc plane. The center word (I, J, K) along the plane normal has no function, because a helical arc gives its pitch in the axis word. LinuxCNC and Fanuc controls do not accept that center word. For a G2 or G3 command, the common conversion of a machine-based Post thus drops the center word along the plane normal. The arc plane causes no other change to the center words.

Arc plane Center word that the Post drops Center words that stay

G17

K

I, J

G18

J

I, K

G19

I

J, K

With the linuxcnc Post and the arc plane G17, the command G2 X10 Y0 Z-1 I5 J0 K0 gives a line with I5.000 and J0.000 and no K word. With the arc plane G18, the command G2 X0 Y0 Z-10 I0 J0 K-5 gives a line with I0.000 and K-5.000 and no J word.

A Post writes a center word only when the word is in parameter_order. The common default of parameter_order is XYZABCUVWFSIJKTQRPDHL, which has K. The linuxcnc Post and the fanuc Post use the common default.

When Line Numbers of the Machine is checked, the Post gives each command an N parameter. These rules apply:

  • The count starts again at Line Number Start in each section of the output.

  • A comment gets no number.

  • A line of a text block, such as the preamble, is counted but gets no number.

  • A command that already has an N parameter keeps its value.

  • The number is not the physical line number of the file.

The filter_inefficient_moves option of the Machine has no effect on the output of a machine-based Post.

Work Planes and rotary axes

Two class attributes tell the export what a Post can write for an Operation on a tilted Work Plane.

  • ROTATION_STRATEGIES: the strategies that the Post can emit. Default: ("dwo", "twp"). The Machine selects one strategy. The export stops when the Post cannot emit that strategy.

  • PLANE_COMMAND: the plane command family for the twp strategy, a member of Path.Post.TiltedWorkPlane.PlaneCommand. Default: PlaneCommand.G68_2.

PlaneCommand Declare line Align line Cancel line

G68_2

G68.2 X{x} Y{y} Z{z} I{a1} J{a2} K{a3}

G53.1

G69

G268

G268 X{x} Y{y} Z{z} I{a1} J{a2} K{a3}

G53.1

G269

PLANE_SPATIAL

PLANE SPATIAL SPA{a1} SPB{a2} SPC{a3} TURN FMAX

None

PLANE RESET STAY

G68_2 and G268 take intrinsic Z-X-Z Euler angles. PLANE_SPATIAL takes spatial angles about the fixed machine axes X, Y and Z. The common properties Tilted work plane: declare, Tilted work plane: align and Tilted work plane: cancel replace one line for a control with a different form. Machine describes these properties and the Rotation Strategy of the Machine.

The linuxcnc Post sets ROTATION_STRATEGIES to ("twp",) and PLANE_COMMAND to PlaneCommand.G68_2. The linuxcnc Post thus writes a tilted Work Plane only with the TWP Rotation Strategy. linuxcnc Post-Processor gives the declare line, the align line, the cancel line and the limits of this output.

When the rotary axes move, the Post writes the Pre-Rotary Move block before the move and the Post-Rotary Move block after the move. The Post writes rotary axis angles with the axis precision and does not convert them between millimetres and inches.

Machine output options

A machine-based Post gets its output format from the output options of the Machine, not from arguments. The options are in these groups:

  • Units, tool length offset output and header output.

  • Header items: date, description, document name, Machine name, project file, units, Tool Bit list, Fixture list. A Fixture is a G54 to G59.9 selection, not the workholding.

  • Comments: enabled, symbol, Operation labels, empty lines, bCNC comments.

  • Format: line numbers, start, prefix, increment, command space, line end characters.

  • Precision: axis, feed, spindle.

  • Duplicates: commands, parameters.

  • Processing: early tool prep, split arcs, tool change, translate drill cycles, translate rapid moves, XY before Z after tool change, return-to position.

Edit the options on the Options tab of the Machine Editor. Machine Configuration describes each option.

Add a custom Post-Processor

Put the Post file in one of these locations:

  • The macro folder, set in Edit  Preferences  Python  Macro  Macro path.

  • The CAM file path, set in Edit  Preferences  CAM  Job preferences.

  • An add-on whose package.xml declares a Post-Processor content item.

Then do these steps:

  1. Make sure that the file has read permission.

  2. For a legacy Post, enable the Post in Edit  Preferences  CAM  Job preferences  Post processor  Post processors selection.

  3. Open the Job task panel again. If the file was already loaded, start FreeCAD again.

Use the macro folder while you test a new Post.

Tests

The unit tests for the Posts are in Mod/CAM/Tests/, for example TestPostCore and TestLinuxCNCPost. Run the tests with this command:

FreeCADCmd -t TestCAMApp

test_post.py is a minimum Post that the tests use.