Skip to content

Instantly share code, notes, and snippets.

@nraynaud
Last active February 16, 2022 23:18
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nraynaud/6ffedde0097893ff49b2c660ae57b162 to your computer and use it in GitHub Desktop.
Save nraynaud/6ffedde0097893ff49b2c660ae57b162 to your computer and use it in GitHub Desktop.
A Fusion 360 DXF post processor whose output uses one color per operation, useful for users of Lasercut 5.3. See: https://www.reddit.com/r/ChineseLaserCutters/comments/b793x5/i_improved_the_dxf_post_processor_for_fusion_360/
/**
Copyright (C) 2015-2018 by Autodesk, Inc.
All rights reserved.
Altered by nraynaud
*/
description = "Color AutoCAD DXF";
vendor = "Autodesk/nraynaud";
vendorUrl = "http://www.autodesk.com";
legal = "Copyright (C) 2015-2018 by Autodesk, Inc.";
certificationLevel = 2;
longDescription = "This post outputs the toolpath in the DXF (AutoCAD) file format." +
" Note that the direction of the toolpath will only be preserved if you enabled the 'forceSameDirection' property which will trigger linearization of clockwise arcs."+
" You can turn on 'onlyCutting' to get rid of the linking motion. And you can turn off 'includeDrill' to avoid points at the drilling positions." +
" You can specify a specific layer by setting the property called 'layer'.";
capabilities = CAPABILITY_INTERMEDIATE | CAPABILITY_MILLING | CAPABILITY_JET;
extension = "dxf";
mimetype = "application/dxf";
setCodePage("utf-8");
minimumCircularSweep = toRad(0.01);
maximumCircularSweep = toRad(180);
allowHelicalMoves = false;
allowSpiralMoves = true;
allowedCircularPlanes = undefined; // only XY arcs
properties = {
includeDrill: true, // output circle for drill positions
only2D: false, // only output toolpath as 2D
forceSameDirection: false, // enable to keep the direction of the toolpath - clockwise arcs will be linearized
allowFullCircles: false, // enable to output full 360 degree circles
allowPolylines: false // enable to output a cutting operation as a polyline instead of a series of lines
};
// user-defined property definitions
propertyDefinitions = {
includeDrill: {title:"Include drill", description:"If enabled circles will be output for all drill positions.", type:"boolean"},
only2D: {title:"Output as 2D", description:"Only output toolpath as 2D.", type:"boolean"},
forceSameDirection: {title:"Force same direction", description:"Enable to keep the direction of the toolpath, clockwise arcs will be linearized.", type:"boolean"},
allowFullCircles: {title:"Output full circles", description:"Enable this property to output full 360 degree circles.", type:"boolean"},
allowPolylines: {title:"Output polylines", description:"Enable this property to output polylines for cutting operations.", type:"boolean"}
};
var xyzFormat = createFormat({decimals:(unit == MM ? 3 : 4)});
var nFormat = createFormat({decimals:9});
var angleFormat = createFormat({decimals:6, scale:DEG});
/** Returns the layer for the current section. */
function getLayer() {
// the layer to output into
return getCurrentSectionId();
}
function onOpen() {
// use this to force unit to mm
// xyzFormat = createFormat({decimals:(unit == MM ? 3 : 4), scale:(unit == MM) ? 1 : 25.4});
if (properties.allowFullCircles) {
maximumCircularSweep = toRad(360);
}
writeln("999");
writeln("Generated by Autodesk CAM - http://cam.autodesk.com");
var d = new Date();
writeln("999");
writeln("Generated at " + d);
writeln("0");
writeln("SECTION");
writeln("2");
writeln("HEADER");
writeln("9");
writeln("$ACADVER");
writeln("1");
writeln("AC1006");
writeln("9");
writeln("$ANGBASE");
writeln("50");
writeln("0"); // along +X
writeln("9");
writeln("$ANGDIR");
writeln("70");
writeln("0"); // ccw arcs
writeln("0");
writeln("ENDSEC");
writeln("0");
writeln("SECTION");
writeln("2");
writeln("BLOCKS");
writeln("0");
writeln("ENDSEC");
var box = new BoundingBox(); // always includes origin
for (var i = 0; i < getNumberOfSections(); ++i) {
box.expandToBox(getSection(i).getGlobalBoundingBox());
}
writeln("9");
writeln("$EXTMIN");
writeln("10"); // X
writeln(xyzFormat.format(box.lower.x));
writeln("20"); // Y
writeln(xyzFormat.format(box.lower.y));
writeln("30"); // Z
writeln(xyzFormat.format(box.lower.z));
writeln("9");
writeln("$EXTMAX");
writeln("10"); // X
writeln(xyzFormat.format(box.upper.x));
writeln("20"); // Y
writeln(xyzFormat.format(box.upper.y));
writeln("30"); // Z
writeln(xyzFormat.format(box.upper.z));
writeln("0");
writeln("SECTION");
writeln("2");
writeln("ENTITIES");
// entities start here
}
function onComment(text) {
}
var drillingMode = false;
function onSection() {
var remaining = currentSection.workPlane;
if (!isSameDirection(remaining.forward, new Vector(0, 0, 1))) {
error(localize("Tool orientation is not supported."));
return;
}
setRotation(remaining);
drillingMode = hasParameter("operation-strategy") && (getParameter("operation-strategy") == "drill");
}
function onParameter(name, value) {
}
function onDwell(seconds) {
}
function onCycle() {
}
function onCyclePoint(x, y, z) {
if (!properties.includeDrill) {
return;
}
writeln("0");
writeln("POINT");
writeln("8"); // layer
writeln(getLayer());
writeln("62"); // color
writeln(1);
writeln("10"); // X
writeln(xyzFormat.format(x));
writeln("20"); // Y
writeln(xyzFormat.format(y));
writeln("30"); // Z
writeln(xyzFormat.format(z));
}
function onCycleEnd() {
}
function laserCutPalette(dxfColor) {
// see https://www.globalmapperforum.com/discussion/4028/dxf-color
// and https://smokeandmirrors.store/pages/lasercut-5-3
var palette = [7, 5, 1, 3, 8, 2, 4];
return dxfColor < palette.length ? palette[dxfColor] : (dxfColor + 8);
}
function getColor(_movement) {
switch (_movement) {
case MOVEMENT_CUTTING:
case MOVEMENT_REDUCED:
case MOVEMENT_FINISH_CUTTING:
return laserCutPalette(getCurrentSectionId());
case MOVEMENT_RAPID:
case MOVEMENT_HIGH_FEED:
return undefined; // skip
case MOVEMENT_LEAD_IN:
case MOVEMENT_LEAD_OUT:
case MOVEMENT_LINK_TRANSITION:
case MOVEMENT_LINK_DIRECT:
return laserCutPalette(getCurrentSectionId());
default:
return undefined; // skip
}
}
function writeLine(x, y, z) {
if (drillingMode) {
return; // ignore since we only want points
}
if (radiusCompensation != RADIUS_COMPENSATION_OFF) {
error(localize("Compensation in control is not supported. You can change it in the 'Passes tab'. See https://imgur.com/a/M544CDY"));
return;
}
var color = getColor(movement);
if (color == undefined) {
return;
}
var start = getCurrentPosition();
if (properties.only2D) {
if (!xyzFormat.areDifferent(start.x, x) &&
!xyzFormat.areDifferent(start.y, y)) {
return; // ignore vertical
}
}
writeln("0");
writeln("LINE");
writeln("8"); // layer
writeln(getLayer());
writeln("62"); // color
writeln(color);
writeln("10"); // X
writeln(xyzFormat.format(start.x));
writeln("20"); // Y
writeln(xyzFormat.format(start.y));
writeln("30"); // Z
writeln(xyzFormat.format(properties.only2D ? 0 : start.z));
writeln("11"); // X
writeln(xyzFormat.format(x));
writeln("21"); // Y
writeln(xyzFormat.format(y));
writeln("31"); // Z
writeln(xyzFormat.format(properties.only2D ? 0 : z));
}
var polyline = new Array();
function pushPolyline(x, y, z, _bulge, _insert) {
if (drillingMode) {
return; // ignore since we only want points
}
if (radiusCompensation != RADIUS_COMPENSATION_OFF) {
error(localize("Compensation in control is not supported. You can change it in the 'Passes tab'. See https://imgur.com/a/M544CDY"));
return;
}
if ((movement == MOVEMENT_CUTTING) || (movement == MOVEMENT_REDUCED) || (movement == MOVEMENT_FINISH_CUTTING)) {
// store previous position as start of polyline
var start = getCurrentPosition();
if (polyline.length == 0) {
polyline.push({vertex:start, bulge:0});
}
if (properties.only2D) { // ignore vertical moves with 2D profiles
if (!xyzFormat.areDifferent(start.x, x) &&
!xyzFormat.areDifferent(start.y, y)) {
return; // ignore vertical
}
} else if (xyzFormat.areDifferent(polyline[0].vertex.z, z)) { // flush polyline with 3D move
writePolyline();
writeLine(x, y, z);
return;
}
// set previous vertex to start of circle
if (_bulge != 0) {
polyline[polyline.length -1].bulge = _bulge;
}
// push position onto polyline stack
polyline.push({vertex:new Vector(x, y, z), bulge:0});
if (!_insert && !getNextRecord().isMotion()) {
writePolyline();
}
} else { // non-cutting move
writePolyline();
writeLine(x, y, z);
}
}
function writePolyline() {
// writeln("length = " + polyline.length)
if (polyline.length <= 1) { // a polyline has not been defined
polyline.length = 0;
return;
}
var closed = 0;
if (Vector.diff(polyline[0].vertex, polyline[polyline.length-1].vertex).length <= 0.002) {
polyline[polyline.length - 1] = polyline[0];
closed = 1;
}
var color = getColor(MOVEMENT_CUTTING);
if (color == undefined) {
return;
}
writeln("0");
writeln("POLYLINE");
writeln("8"); // layer
writeln(getLayer());
writeln("62"); // color
writeln(color);
writeln("66"); // vertices follow
writeln("1");
writeln("70"); // polyline flag
writeln(closed);
for (var i = 0; i < polyline.length; ++i) {
writeln("0");
writeln("VERTEX");
writeln("8"); // layer
writeln(getLayer());
writeln("10"); // X
writeln(xyzFormat.format(polyline[i].vertex.x));
writeln("20"); // Y
writeln(xyzFormat.format(polyline[i].vertex.y));
if (polyline[i].bulge != 0) { // circle bulge
writeln("42");
writeln(xyzFormat.format(polyline[i].bulge));
}
}
writeln("0");
writeln("SEQEND");
writeln("8"); // layer
writeln(getLayer());
polyline.length = 0;
}
function onRapid(x, y, z) {
writePolyline();
writeLine(x, y, z);
}
function onLinear(x, y, z, feed) {
if (properties.allowPolylines) {
pushPolyline(x, y, z, 0, false);
} else {
writePolyline();
writeLine(x, y, z);
}
}
function onRapid5D(x, y, z, dx, dy, dz) {
onRapid(x, y, z);
}
function onLinear5D(x, y, z, dx, dy, dz, feed) {
onLinear(x, y, z);
}
function onCircular(clockwise, cx, cy, cz, x, y, z, feed) {
if (getCircularPlane() != PLANE_XY) {
// start and end angle reference is unknown
linearize(tolerance);
return;
}
if (clockwise && properties.forceSameDirection) {
linearize(tolerance);
return;
}
if (properties.only2D) {
if (getCircularPlane() != PLANE_XY) {
linearize(tolerance);
return;
}
}
if (radiusCompensation != RADIUS_COMPENSATION_OFF) {
error(localize("Compensation in control is not supported. You can change it in the 'Passes tab'. See https://imgur.com/a/M544CDY"));
return;
}
var fullCircle = Math.abs(getCircularSweep()) >= (Math.PI * 2 - 0.001);
if (properties.allowPolylines) {
if ((getCircularPlane() != PLANE_XY) || fullCircle) {
writePolyline();
} else {
var sweep = getCircularSweep();
if (sweep > Math.PI) { // maximum sweep of 180 deg for circles in a polyline
var center = new Vector(cx, cy, cz);
var start = getCurrentPosition();
var end = Vector.diff(center, start);
end = Vector.sum(center, end);
pushPolyline(end.x, end.y, end.z, 1.0, true);
sweep -= Math.PI;
}
var bulge = Math.tan(sweep / 4) * (clockwise ? -1 : 1);
pushPolyline(x, y, z, bulge, false);
return;
}
}
var color = getColor(movement);
if (color == undefined) {
return;
}
writeln("0");
writeln(fullCircle ? "CIRCLE" : "ARC");
writeln("8"); // layer
writeln(getLayer());
writeln("62"); // color
writeln(color);
writeln("10"); // X
writeln(xyzFormat.format(cx));
writeln("20"); // Y
writeln(xyzFormat.format(cy));
writeln("30"); // Z
writeln(xyzFormat.format(properties.only2D ? 0 : cz));
writeln("40"); // radius
writeln(xyzFormat.format(getCircularRadius()));
if (!fullCircle) {
var start = getCurrentPosition();
var startAngle = Math.atan2(start.y - cy, start.x - cx);
var endAngle = Math.atan2(y - cy, x - cx);
// var endAngle = startAngle + (clockwise ? -1 : 1) * getCircularSweep();
if (clockwise) { // we must be ccw
var temp = startAngle;
startAngle = endAngle;
endAngle = temp;
}
writeln("50"); // start angle
writeln(angleFormat.format(startAngle));
writeln("51"); // end angle
writeln(angleFormat.format(endAngle));
}
if (getCircularPlane() != PLANE_XY) {
validate(!properties.only2D, "Invalid handling on onCircular().");
var n = getCircularNormal();
writeln("210"); // X
writeln(nFormat.format(n.x));
writeln("220"); // Y
writeln(nFormat.format(n.y));
writeln("230"); // Y
writeln(nFormat.format(n.z));
}
}
function onCommand() {
}
function onSectionEnd() {
}
function onClose() {
writeln("0");
writeln("ENDSEC");
writeln("0");
writeln("EOF");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment