Export & post-processor


What is a Post-Processor?

A post-processor is a custom piece of software that translates instructions from CAM software into commands that can be understood by machines. These instructions are usually passed through a text file, where each line represents a movement, procedure, or function that the machine should perform. While G-code is the standard machine language, not all machines are the same (e.g., a laser cutter versus a lathe), so it is often necessary to use a post-processor to fine-tune the output from the CAM software.

How Does It Work?

Within CAM software, all the information needed to create a toolpath is stored in as standardized a format as possible. A CAM toolpath object contains a list of coordinates and direction vectors that the tool should follow, along with process inputs at each point such as movement type (rapid/feed), feed rates, spindle speeds, and tool properties like diameter and length, as well as any other relevant information for the machining task.

When it’s time to generate the NC file for a specific machine, the post-processor loops through all of this information and writes the correct sequence of commands into the NC file, which the machine will read, interpret, and execute.

Illustrating the concept with C#:

We have a (very) simplified toolpath structure:

class ArrayOfMove
{
    List<Move> Moves; // List of moves
}

Where each move has the following structure:

class Move
{
    string Type; // "Rapid"/"Linear"/...
    double TargetX; // X target position 
    double TargetY; // Y target position 
    double TargetZ; // Z target position 
}

The post-processor can be constructed as a function that takes such a Toolpath as input and prints the corresponding G-code into the NC file:

void PostProcess(ArrayOfMove toolPath)
{
    // Initialize file content variable
    string fileContent = "";

    // Loop through each move in the toolpath
    foreach (Move move in toolPath.Moves)
    {
        // Check the move type: G1 for linear feed, G0 for rapid
        if (move.Type == "Linear")
            fileContent += "G1"; // Feed move
        else
            fileContent += "G0"; // Rapid move

        // Add the X, Y, and Z coordinates to the file content
        fileContent += " X" + move.X.ToString();
        fileContent += " Y" + move.Y.ToString();
        fileContent += " Z" + move.Z.ToString();

        // Add a new line after each move command
        fileContent += "\r\n";
    }

    // Write the complete file content to a .nc file
    File.WriteAllText("C:\\myNCFile.nc", fileContent);
}

Why is it so expensive?

As mentionned before, the post-processor is a custom piece of software. Anything that is not standard in a CAM system will be expensive, as it needed to be developped, installed, tested and supported by expert in the field. Some incredible prices can arrise from the time consuming iterations necessary to set everything up.

Building custom post-processor with NCnetic.

When NCnetic or any backplotter reads an NC file, it performs the reverse operation of a post-processor. The NC file is translated back into its standard native structure.

It is then possible to reprocess the file and output a new file format.

To do this, an ’export’ function is available, supporting XML, JSON, or CSV formats. In the ‘Options & Settings’ window, go to the ‘EXPORT’ section and select the ‘EXPORT FILE TYPE’. You can enable the ‘FILE DIALOG’ property (set it to true) to open a file-saving dialog window.

The output file can then be processed by a custom application by checking the ‘RUN EXE’ property and specifying an *.exe file in the ‘EXE PATH’ property.

When the ’export’ is performed, the executable is called with the exported file as an argument, which is equivalent to performing this action in a command prompt:

c:\MyCustomExe.exe "c:\ExportedFile.xml"

Additionally, it is possible to call a script by enabling the ‘INCLUDE SCRIPT’ property and specifying a script file in the ‘SCRIPT PATH’ property. For example, running Python with a *.py script during ’export’ is equivalent to performing this action in a command prompt:

python c:\MyPythonScript.py "c:\ExportedFile.xml"

The executable should read the exported file, extract relevant informations, and write (post-process) a new output file.

Practical code sample: DXF post-processor

The following code is the Program.cs content of a .Net console application. It will convert the XML export into a DXF file in the XY view (Use ‘EXPORT=XML’ & ‘FILE DIALOG=true’).

using System;
using System.IO;
using System.Collections.Generic;
using System.Xml.Serialization;

namespace ConsoleApp1
{
    public class Program
    {
        [XmlRoot("ArrayOfMove")]
        public class ArrayOfMove
        {
            [XmlElement("Move")]
            public List<Move> Moves { get; set; }
        }

        public class Move
        {
            public int Line { get; set; }
            public int Block { get; set; }
            public int ToolNumber { get; set; }
            public string Type { get; set; }
            public string WorkPlane { get; set; }
            public string MaterialSide { get; set; }
            public double FeedRate { get; set; }
            public double SpindleSpeed { get; set; }
            public double OriginX { get; set; }
            public double OriginY { get; set; }
            public double OriginZ { get; set; }
            public double OriginRot1 { get; set; }
            public double OriginRot2 { get; set; }
            public double TargetX { get; set; }
            public double TargetY { get; set; }
            public double TargetZ { get; set; }
            public double TargetRot1 { get; set; }
            public double TargetRot2 { get; set; }
            public double Length { get; set; }
            public double ArcCenterX { get; set; }
            public double ArcCenterY { get; set; }
            public double ArcCenterZ { get; set; }
            public double ArcRadius { get; set; }
        }

        public static void Main(string[] args)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(ArrayOfMove));

            using (FileStream fs = new FileStream(args[0], FileMode.Open))
            {
                ArrayOfMove arrayOfMove = (ArrayOfMove)serializer.Deserialize(fs);

                string dxfFile = Path.GetDirectoryName(args[0]) + "\\" + Path.GetFileNameWithoutExtension(args[0]) + ".dxf";
                using (FileStream FileStream = new FileStream(dxfFile, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
                {
                    using (StreamWriter sw = new StreamWriter(FileStream))
                    {
                        sw.WriteLine("  0");
                        sw.WriteLine("SECTION");
                        sw.WriteLine("  2");
                        sw.WriteLine("ENTITIES");
                        sw.WriteLine("  0");

                        bool first = true;
                        foreach (Move move in arrayOfMove.Moves)
                        {
                            if (first)
                            {
                                sw.WriteLine("SEQEND");
                                sw.WriteLine("  0");

                                if (move.Type != "Rapid")
                                {
                                    sw.WriteLine("POLYLINE");
                                    sw.WriteLine("  8");
                                    sw.WriteLine("0");
                                    sw.WriteLine("  66");
                                    sw.WriteLine("1");
                                    sw.WriteLine("  70");
                                    sw.WriteLine("0");
                                    sw.WriteLine("62");
                                    sw.WriteLine("7");
                                    sw.WriteLine("0");

                                    sw.WriteLine("VERTEX");
                                    sw.WriteLine("  8");
                                    sw.WriteLine("0");
                                    sw.WriteLine("  10");
                                    sw.WriteLine("0.0");
                                    sw.WriteLine("  20");
                                    sw.WriteLine("0.0");
                                    sw.WriteLine("  0");
                                }
                            }

                            if (move.Type == "Rapid")
                            {
                                if (!first)
                                {
                                    sw.WriteLine("  0");
                                }

                                sw.WriteLine("POLYLINE");
                                sw.WriteLine("  8");
                                sw.WriteLine("0");
                                sw.WriteLine("  66");
                                sw.WriteLine("1");
                                sw.WriteLine("  70");
                                sw.WriteLine("0");
                                sw.WriteLine("62");
                                sw.WriteLine("7");
                                sw.WriteLine("0");

                                sw.WriteLine("VERTEX");
                                sw.WriteLine("  8");
                                sw.WriteLine("0");
                                sw.WriteLine("  10");
                                sw.WriteLine(move.TargetX.ToString("0.######"));
                                sw.WriteLine("  20");
                                sw.WriteLine(move.TargetY.ToString("0.######"));
                            }
                            else if (move.Type == "Linear")
                            {
                                if (!first)
                                {
                                    sw.WriteLine("  0");
                                }

                                sw.WriteLine("VERTEX");
                                sw.WriteLine("  8");
                                sw.WriteLine("0");
                                sw.WriteLine("  10");
                                sw.WriteLine(move.TargetX.ToString("0.######"));
                                sw.WriteLine("  20");
                                sw.WriteLine(move.TargetY.ToString("0.######"));
                            }
                            else if (move.Type == "CircularCW")
                            {
                                if (!first)
                                {
                                    sw.WriteLine("  42");
                                    double bulge = -1 * GetBulge(move.OriginX, move.OriginY, move.TargetX, move.TargetY, move.ArcCenterX, move.ArcCenterY);
                                    sw.WriteLine(bulge.ToString("0.######"));
                                    sw.WriteLine("  0");
                                }

                                sw.WriteLine("VERTEX");
                                sw.WriteLine("  8");
                                sw.WriteLine("0");
                                sw.WriteLine("  10");
                                sw.WriteLine(move.TargetX.ToString("0.######"));
                                sw.WriteLine("  20");
                                sw.WriteLine(move.TargetY.ToString("0.######"));
                            }
                            else if (move.Type == "CircularCCW")
                            {
                                if (!first)
                                {
                                    sw.WriteLine("  42");
                                    double bulge = GetBulge(move.OriginX, move.OriginY, move.TargetX, move.TargetY, move.ArcCenterX, move.ArcCenterY);
                                    sw.WriteLine(bulge.ToString("0.######"));
                                    sw.WriteLine("  0");
                                }

                                sw.WriteLine("VERTEX");
                                sw.WriteLine("  8");
                                sw.WriteLine("0");
                                sw.WriteLine("  10");
                                sw.WriteLine(move.TargetX.ToString("0.######"));
                                sw.WriteLine("  20");
                                sw.WriteLine(move.TargetY.ToString("0.######"));
                            }

                            first = false;
                        }

                        sw.WriteLine("  0");

                        sw.WriteLine("SEQEND");
                        sw.WriteLine("  0");

                        sw.WriteLine("ENDSEC");
                        sw.WriteLine("  0");
                        sw.WriteLine("EOF");
                    }
                }
            }
        }

        static double GetBulge(double x0, double y0, double x1, double y1,double xc, double yc)
        {
            double r, a, i, u;

            r = Math.Sqrt(Math.Pow(x1 - xc, 2) + Math.Pow(y1 - yc, 2));
            u = Math.Sqrt(Math.Pow(x1 - x0, 2) + Math.Pow(y1 - y0, 2));
            a = Math.Sqrt(Math.Pow(r, 2) - Math.Pow(u, 2) / 4.0);
            i = r - a;

            return Math.Abs(2 * i / u);
        }
    }
}