About
TPL or Tool Path Language is a programming language for creating machine tool paths for CNCs. It is based on JavaScript and is a powerful replacement for the venerable but horribly outdated GCode language. However, TPL can output GCode so it remains compatible with existing machine control software such as LinuxCNC.
TPL is part of the CAMotics project and can be used in conjunction with the CAMotics CNC simulator.
Downloads
TPL is part of the CAMotics project. See camotics.org downloads for information on downloading and installing the software.
An Example
This section aims to give you a quick and dirty introduction to TPL and some of its features via a few simple examples.
TPL code is JavaScript with a library of built-in functions which allow you to generate tool paths. It looks like this:
feed(400); // Set the feed rate to 400 millimeters per minute
tool(1); // Select tool 1
rapid({z: 5}); // Move to a safe height of 5mm
rapid({x: 1, y: 1}); // Go to start position
speed(2000); // Spin at 2000 RPM in the clockwise direction
cut({z: -3}); // Cut down to depth
cut({x: 11}); // Cut to second corner
cut({y: 11}); // Cut to third corner
cut({x: 1}); // Cut to forth corner
cut({y: 1}); // Cut back to begining
rapid({z: 5}); // Move back to safe position
speed(0); // Stop spinning
The CAMotics simulator shows the result:
The previous example initializes the machine then cuts a square.
Note, you must always set the feed rate to something other than zero
before making a cut. It's also a good idea to select a tool and
set the spindle speed. Also note, TPL uses metric units by default.
You can switch to imperial units by calling units(IMPERIAL).
Note, the bracket notation {} used above lets you
specify specific arguments and leave others to their default values.
The following function calls are all equivalent:
rapid(5, 10, 15);
rapid({x: 5, y: 10, z: 15});
rapid({z: 15, x: 5, y: 10});
The code in the square example may be more readable than typical GCode but TPL's real power comes from its ability to use more advanced language constructs such as functions. The above square example could be wrapped in a function like this:
function square(size, depth, safe) {
cut({z: depth}); // Cut down to depth
icut({x: size}); // Cut to second corner
icut({y: size}); // Cut to third corner
icut({x: -size}); // Cut to forth corner
icut({y: -size}); // Cut back to begining
rapid({z: safe}); // Move back to safe position
}
feed(400); // Set the feed rate to 400 mm per minute
tool(1); // Select tool 1
rapid({z: 5}); // Move to a safe height of 5mm
rapid({x: 1, y: 1}); // Go to start position
speed(2000); // Spin at 2000 RPM in the clockwise direction
square(10, -3, 5);
speed(0); // Stop spinning
Note the square() function uses icut()
the incremental version of cut().
Given the square() function you can now cut repeated
squares like this:
for (i = 1; i <= 10; i++) square(i * 5, -3, 5);
Here is the result of running the square function as
in the for loop above.
To continue the example a bit further and show more of the power of TPL we can repeat and rotate the squares to create an interesting pattern using the following program:
function square(size, depth, safe) {
cut({z: depth}); // Cut down to depth
icut({x: size}); // Cut to second corner
icut({y: size}); // Cut to third corner
icut({x: -size}); // Cut to forth corner
icut({y: -size}); // Cut back to begining
rapid({z: safe}); // Move back to safe position
}
function squares(count, space, depth, safe) {
for (var i = 1; i <= count; i++) {
rapid({x: -space * i, y: -space * i});
square(i * 2 * space, depth, safe);
}
}
feed(400); // Set the feed rate to 40 mm per second
tool(1); // Select tool 1
rapid({z: 5}); // Move to a safe height of 5mm
rapid({x: 0, y: 0}); // Go to center position
speed(2000); // Spin at 2000 RPM in the clockwise direction
for (var i = 0; i < 4; i++) {
squares(10, 5, -3, 5);
rotate(Math.PI / 8); // Rotate clockwise 15 degrees
}
speed(0); // Stop spinning
The simulator shows the final result:
Using TPL
After downloading and installing the latest CAMotics you can write
a TPL program, or try out one of the examples. TPL programs
usually have the file extension .tpl. You can execute
a TPL program like this:
tplang input.tpl
This will print the resulting GCode to the screen. You can also save the output to a file like this:
tplang input.tpl > output.gc
You can then view the results in the CAMotics simulator like this:
camotics output.gc
You will also probably have to configure a tool table and define the workpeice (the material being cut) in order to get a correct simulation. See camotics.org for more information on using the simulator.
API
The TPL Application Programming Interface allows you to duplicate any GCode program easily in a much more readable way but you also have the full power of the JavaScript language at your finger tips so much more is possible. In fact you don't even need CAM software you can model the shapes you want to cut directly in TPL.
GCode
gcode(path)
Include an external gcode file. Note that the contents are processed, not included raw, so unsupported commands or phrasings may be lost.
gcode("plugin.gcode"); // Include "plugin.gcode"
gexec(code)
Includes a gcode string. Note that the contents are processed, not included raw, so unsupported commands or phrasings may be lost.
gexec("G0 X100 Y200"); // Add gcode command "G0 X100 Y200"
Tool Movement
rapid(x, y, z, a, b, c, u, v, w, incremental=false)
Issue a linear motion at the rapid feed rate from the current position to the new position defined by the provided axes arguments.
rapid(0, 0, 0); // Rapid to x=0, y=0, z=0
rapid(10, 10); // Rapid to x=10, y=10, z remains 0
rapid({z: 10, y: 10}); // Rapid to y=10, z=10, x remains 10
irapid(x, y, z, a, b, c, u, v, w, incremental=true)
The same as rapid() but incremental
defaults to true.
cut(x, y, z, a, b, c, u, v, w, incremental=false)
The same as rapid() except moves are at the current
feed rate set by a call to feed().
icut(x, y, z, a, b, c, u, v, w, incremental=true)
The same as cut() but incremental
defaults to true.
arc(x = 0, y = 0, z = 0, angle = Math.PI * 2, plane = XY)
x, y and z specify the
offset from the current position to the far center of the
helical move in the selected plane. If the z value
is zero then the move is a simple arc in the selected plane,
otherwise it is a true helical move with an axis of length equal
to the absolute value of the z value either above or
below the selected plane through the current control point
depending on the sign.
angle is the rotation of the arc in radians. A
positive value indicates a clockwise rotation, negative a
counter-clockwise rotation.
plane specifies to which plane the helical axis is
perpendicular. Valid values are XY (the default),
XZ or YZ.
probe(x, y, z, a, b, c, u, v, w, port=0, active=true, error=true)
Make a linear move in the direction indicated until the probe
state changes to the target state. If active is true
the probe motion will stop on contact. Otherwise it will stop on
loss of contact.
probe() will return the coordinates of the position
at the time the probe changed state. If the programmed point was
reached before the state changed then the programmed point will be
returned instead.
probe(z = -10); // Probe towards z=-10
probe(z = 10, active = false); // Probe away from current z
It is an error if:
- The current point is the same as the programmed point.
- The current feed rate is zero.
- The probe is already in the target state.
dwell(seconds)
seconds indicate the time in seconds that all axes
will remain unmoving. Fractions of a second may be used.
dwell(0.5); // Dwell for half a second.
Machine State
feed(rate, mode = FEED_UNITS_PER_MIN)
rate indicates the units per minute in the XYZ
Cartesian system of all subsequent non-rapid moves until the feed
rate is changed.
mode may be one of the following:
-
FEED_UNITS_PER_MIN- The feed rate is in units per minute. The unit may be inches, millimeters or degrees depending on the current unit of length and which axes are moving. -
FEED_INVERSE_TIME- Indicates that moves should be completed in one divided byrateminutes. For example, ifrateis 2.0, moves should be completed in half a minute. -
FEED_UNITS_PER_REV- Means that the controlled point should move a certain number of units per revolution.
If no arguments are given, returns the current feed rate and mode.
feed(4); // Move at 4 units per minute
rate = feed()[0]; // Get the current feed rate
speed(rate, surface, max)
rate indicates the revolutions per minute of the
spindle.
If rate is positive the spindle will turn in the
clockwise direction. If negative in the counterclockwise
direction.
A rate of zero turns the spindle off.
If surface is specified it selects a constant surface
speed of surface feet per minute if
IMPERIAL units are selected or meters per minute if
METRIC units are selected. If max is
also specified it indicates the maximum spindle RPM.
If no arguments are given, returns the current spindle speed, surface speed and maximum RPM.
speed(1000); // Spin clockwise at 1K RPM.
speed(-20000); // Spin counterclockwise at 20K RPM.
tool(number)
Make tool number the current tool.
tool(1); // Select tool 1
If no arguments are given, information about the currently selected tool is returned. E.g.:
{
number: 1,
shape: CONICAL, // A tool type constant
angle: Math.PI * 0.5, // In radians, 90°
length: 10, // In current units
diameter: 3.175, // In current units
snub_diameter: 1 // If shape == SNUBNOSE
}
For example, to get the current tool radius:
var tool_radius = tool().diameter / 2;
The tool type constants are as follows:
CYLINDRICALCONICALBALLNOSESPHEROIDSNUBNOSE
units(type)
type may be either METRIC which selects
millimeters as the unit of measure or IMPERIAL
which selects inches.
If no arguments are given the current type is returned.
units(METRIC); // Select millimeters
pause(optional=false)
Pause the program until the user resumes. If
optional is true then only pause if the stop switch
is activated.
pause(true);
workpiece()
Get current simulation's workpiece offset and dimensions.
workpiece() = {
dims: {x: 100, y: 100, z: 10},
offset: {x: -50, y: -50, z: -10}
}
Matrix
pushMatrix(matrix)popMatrix(matrix)loadIdentity(matrix)scale(x=1, y=1, z=1, matrix)translate(x=0, y=0, z=0, matrix)rotate(angle, x=0, y=0, z=1, matrix)getXYZ()
Return an array of the current xyz position, i.e. [x, y, z].
getX()
Return the current x position.
getY()
Return the current y position.
getZ()
Return the current z position.
Math
JavaScript has built in math functions and constants which are members of the built-in Math object. The Math object is defined in the EMCAScript specification, section 15.8 or in a perhaps more readable format at W3 Schools, Math object reference.
var negOne = Math.cos(Math.PI);
Debugging
print(...)
Convert all arguments to strings and print them to the output stream.
Note that print() writes to the same output stream as the
gcode functions so if you don't want to interfere with the gcode
output you should wrap text in parentheses () so that
it is treated as a gcode comment.
print() does not automatically add an end of line and does not interpret formatting strings.
print("Hello World!\n");
print("(A gcode comment)\n");
position()
Return a dict of the current positions, i.e. {x y z a b c u v w}.
print(position().x, position().y, position().z);
comment(...)
Convert all arguments to strings and output a GCode comment.
comment("A gcode comment");
message(...)
Convert all arguments to strings and output a GCode message.
In some contexts GCode messages are displayed to the user.
message("Hello World");
console.log(...)
Print to log info stream.
console.debug(...)
Print to log debug stream.
console.warn(...)
Print to log warning stream.
console.error(...)
Print to log error stream.
Module
Load code modules.
require(path)
Load the specified module. Modules are searched for in the directories
specified in the TPL_PATH environment variable. A TPL module
may be either a file ending in .tpl, .js or
.json or a directory containing a package.json file.
.tpl or .js files are loaded as code whereas
.json files are loaded as data. Code modules must assign values
to the module.exports (alternatively exports)
dictionary in order for them to be accessible. For example:
// A TPL module
module.exports = {
data: [1, 2, 3],
hello: function () {print("Hello World!\n");}
}
If the above code were in a file called hello.tpl, you might use the following to access it:
var m = require('hello.tpl');
m.hello();
for (var i = 0; i < m.data.length; i++)
print(m.data[i], '\n');
package.json files are treated specially. The contents are interpreted as a dictionary of JSON data and the attribute main is read to get the relative path of the entry point for the module. The specified file is then loaded as a code or data module as above.
When a module is being loaded the following variables are defined:
| module.name | The module name. |
|---|---|
| module.id | Same as module.name. |
| module.filename | The full path to the module. |
| module.exports | The exports dictionary. Used to export public data and functions. Can be assigned to a new value. |
| exports | Same as module.exports but reassignment has no effect. |
| id | Same as module.name. |
If any modules have cyclic require() calls, module values may not be accessible at module load time. For example:
a.tpl:var b = require('b.tpl');
print(b.data, '\n');
module.exports = {data: 'A'}b.tpl:
var a = require('a.tpl');
print(a.data, '\n');
module.exports = {data: 'B'}main.tpl:
var a = require('a.tpl');
var b = require('b.tpl');
print(a.data, '\n');
print(b.data, '\n');
Running main.tpl will produce the following output:
undefined
B
A
B
The first reference to a.data is undefined because module A is not yet fully loaded when accessed in module B. However, all data has been loaded by the time main.tpl accesses it.
2D CAM
Some basic CAM operations on 2D polygons.
offset(polys, delta, join, limit=1000, autoFix=true, scale=1000000)
| polys | A list of polygons. See format below. |
|---|---|
| delta | The offset delta. Positive for outset. Negative for inset. |
| join |
Type of join. Can be JOIN_SQUARE,
JOIN_ROUND or JOIN_MITER.
|
| limit | Default 1000. Miter limit for JOIN_MITER. |
| autoFix |
Default true. Automatically fix polygon orientation if
necessary and remove duplicate vertices.
|
| scale | Default 10,000,000. Scale the polyon points to gain accuracy. |
Use the Clipper library to compute polygon offsets.
The polys argument is an array of one or more polygons
and is formatted either as an array of arrays of dictionaries:
[
[{x: 0, y: 0}, {x: 1, y: 0}, . . ., {x: 0, y: 0}],
. . .
]
or an array of arrays of 2D arrays:
[
[[0, 0], [1, 0], . . ., [0, 0]],
. . .
]
Note, the results of dxf.open() are not directly usable by
offset() but other functions in the dxf module
can convert raw DXF data to polygons.
DXF
DXF file format support.
Constants
| POINT |
|---|
| LINE |
| ARC |
| POLYLINE |
| SPLINE |
open(path)
Read a DXF file and return JSON data as a dictionary of layers. Each layer is a list of any of the following entities:
Points
{
type: dxf.POINT,
x: 0,
y: 0,
z: 0
}
Lines
{
type: dxf.LINE,
start: {x: 0, y: 0, z: 0},
end: {x: 1, y: 1, z: 1}
}
Arcs
{
type: dxf.ARC,
center: {x: 0, y: 0, z: 0},
radius: 1,
startAngle: 0,
endAngle: 2 * Math.PI,
clockwise: true
}
Polylines
{
type: dxf.POLYLINE,
vertices: [
{type: dxf.POINT, x: 0, y: 0, z: 0},
. . .
]
}
Splines
{
type: dxf.SPLINE,
degree: 3,
ctrlPts: [
{type: dxf.POINT, x: 0, y: 0, z: 0},
. . .
],
knots: [0, . . .]
}
Example
var dxf = require('dxf');
var layers = dxf.open('test.dxf');
Bug Reports
See the GitHub issues page.
Links
OpenJSCAM a pure JS implementation of TPLang (in development).