Last active
February 16, 2022 23:18
-
-
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/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
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