├── .gitattributes ├── .gitignore ├── OB_4axis_router.f3d ├── OpenbuildsFusion360PostGrbl.cps ├── OpenbuildsFusion360PostGrblX32-4thaxis-beta.cps ├── README-plasma.md └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### NotepadPP ### 2 | # Notepad++ backups # 3 | *.bak 4 | 5 | 6 | ### Windows ### 7 | # Windows image file caches 8 | Thumbs.db 9 | ehthumbs.db 10 | 11 | # Folder config file 12 | Desktop.ini 13 | 14 | # Recycle Bin used on file shares 15 | $RECYCLE.BIN/ 16 | 17 | # Windows Installer files 18 | *.cab 19 | *.msi 20 | *.msm 21 | *.msp 22 | 23 | # Windows shortcuts 24 | *.lnk 25 | -------------------------------------------------------------------------------- /OB_4axis_router.f3d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBuilds/OpenBuilds-Fusion360-Postprocessor/9184fc75e736b4d157b81332ddd8d5fd04682947/OB_4axis_router.f3d -------------------------------------------------------------------------------- /OpenbuildsFusion360PostGrbl.cps: -------------------------------------------------------------------------------- 1 | /* 2 | Custom Post-Processor for GRBL based Openbuilds-style CNC machines, router and laser-cutting 3 | Made possible by 4 | Swarfer https://github.com/swarfer/GRBL-Post-Processor 5 | Sharmstr https://github.com/sharmstr/GRBL-Post-Processor 6 | Strooom https://github.com/Strooom/GRBL-Post-Processor 7 | This post-Processor should work on GRBL-based machines 8 | 9 | Changelog 10 | 22/Aug/2016 - V01 : Initial version (Stroom) 11 | 23/Aug/2016 - V02 : Added Machining Time to Operations overview at file header (Stroom) 12 | 24/Aug/2016 - V03 : Added extra user properties - further cleanup of unused variables (Stroom) 13 | 07/Sep/2016 - V04 : Added support for INCHES. Added a safe retract at beginning of first section (Stroom) 14 | 11/Oct/2016 - V05 : Update (Stroom) 15 | 30/Jan/2017 - V06 : Modified capabilities to also allow waterjet, laser-cutting (Stroom) 16 | 28 Jan 2018 - V07 : Fix arc errors and add gotoMCSatend option (Swarfer) 17 | 16 Feb 2019 - V08 : Ensure X, Y, Z output when linear differences are very small (Swarfer) 18 | 27 Feb 2019 - V09 : Correct way to force word output for XYZIJK, see 'force:true' in CreateVariable (Swarfer) 19 | 27 Feb 2018 - V10 : Added user properties for router type. Added rounding of dial settings to 1 decimal (Sharmstr) 20 | 16 Mar 2019 - V11 : Added rounding of tool length to 2 decimals. Added check for machine config in setup (Sharmstr) 21 | : Changed RPM warning so it includes operation. Added multiple .nc file generation for tool changes (Sharmstr) 22 | : Added check for duplicate tool numbers with different geometry (Sharmstr) 23 | 17 Apr 2019 - V12 : Added check for minimum feed rate. Added file names to header when multiple are generated (Sharmstr) 24 | : Added a descriptive title to gotoMCSatend to better explain what it does. 25 | : Moved machine vendor, model and control to user properties (Sharmstr) 26 | 15 Aug 2019 - V13 : Grouped properties for clarity (Sharmstr) 27 | 05 Jun 2020 - V14 : description and comment changes (Swarfer) 28 | 09 Jun 2020 - V15 : remove limitation to MM units - will produce inch output but user must note that machinehomeX/Y/Z values are always MILLIMETERS (Swarfer) 29 | 10 Jun 2020 - V1.0.16 : OpenBuilds-Fusion360-Postprocessor, Semantic Versioning, Automatically add router dial if Router type is set (OpenBuilds) 30 | 11 Jun 2020 - V1.0.17 : Improved the header comments, code formatting, removed all tab chars, fixed multifile name extensions 31 | 21 Jul 2020 - V1.0.18 : Combined with Laser post - will output laser file as if an extra tool. 32 | 08 Aug 2020 - V1.0.19 : Fix for spindleondelay missing on subfiles 33 | 02 Oct 2020 - V1.0.20 : Fix for long comments and new restrictions 34 | 05 Nov 2020 - V1.0.21 : poweron/off for plasma, coolant can be turned on for laser/plasma too 35 | 04 Dec 2020 - V1.0.22 : Add Router11 and dial settings 36 | 16 Jan 2021 - V1.0.23 : Remove end of file marker '%' from end of output, arcs smaller than toolRadius will be linearized 37 | 25 Jan 2021 - V1.0.24 : Improve coolant codes 38 | 26 Jan 2021 - V1.0.25 : Plasma pierce height, and probe 39 | 29 Aug 2021 - V1.0.26 : Regroup properties for display, Z height check options 40 | 03 Sep 2021 - V1.0.27 : Fix arc ramps not changing Z when they should have 41 | 12 Nov 2021 - V1.0.28 : Added property group names, fixed default router selection, now uses permittedCommentChars (sharmstr) 42 | 24 Nov 2021 - V1.0.28 : Improved coolant selection, tweaked property groups, tweaked G53 generation, links for help in comments. 43 | 21 Feb 2022 - V1.0.29 : Fix sideeffects of drill operation having rapids even when in noRapid mode by always resetting haveRapid in onSection 44 | 10 May 2022 - V1.0.30 : Change naming convention for first file in multifile output (Sharmstr) 45 | xx Sep 2022 - V1.0.31 : better laser, with pierce option if cutting 46 | 06 Dec 2022 - V1.0.32 : fix long comments that were getting extra brackets 47 | 22 Dec 2022 - V1.0.33 : refactored file naming and debugging, indented with astyle 48 | 10 Mar 2023 - V1.0.34 : move coolant code to the spindle control line to help with restarts 49 | 26 Mar 2023 - V1.0.35 : plasma pierce height override, spindle speed change always with an M3, version number display 50 | 03 Jun 2023 - V1.0.36 : code to recenter arcs with bad radii 51 | 04 Oct 2023 - V1.0.37 : Tape splitting 52 | Nov 2023 - V1.0.38 : Simple probing, each axis on its own, and xy corner, for BB4x with 3D probe, and machine simulation 53 | 10 Feb 3024 - V1.0.39 : Add missing drill cycles, missing because probing failed to expand unhandled cycles 54 | 13 Mar 2024 - V1.0.40 : force position after plasma probe, fix plasma linearization of small arcs to avoid GRBL bug in arc after probe, fix pierceClearance and pierceHeight, fix plasma kerfWidth 55 | 27 Mar 2024 - V1.0.41 : replace 'power' with OB.power (and friends) because postprocessor.power now exists and is readonly 56 | 30 Mar 2024 - V1.0.42 : postprocessor.alert() method has disappeared - replaced with warning(msg) and writeComment(msg), moved more stuff into OB. and SPL. 57 | xx Oct 2024 - V1.0.43 : fix plasma pierce/cut height, pierceTime so it uses the tool settings if provided 58 | */ 59 | obversion = 'V1.0.43'; 60 | description = "OpenBuilds CNC : GRBL/BlackBox"; // cannot have brackets in comments 61 | longDescription = description + " : Post " + obversion; // adds description to post library dialog box 62 | vendor = "OpenBuilds"; 63 | vendorUrl = "https://openbuilds.com"; 64 | model = "GRBL"; 65 | legal = "Copyright Openbuilds 2024"; 66 | certificationLevel = 2; 67 | minimumRevision = 45892; 68 | 69 | debugMode = false; 70 | 71 | extension = "gcode"; // file extension of the gcode file 72 | setCodePage("ascii"); // character set of the gcode file 73 | //setEOL(CRLF); // end-of-line type : use CRLF for windows 74 | 75 | var permittedCommentChars = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,=_-*/\\:"; 76 | capabilities = CAPABILITY_MILLING | CAPABILITY_JET | CAPABILITY_INSPECTION | CAPABILITY_MACHINE_SIMULATION; // intended for a CNC, so Milling, and waterjet/plasma/laser 77 | tolerance = spatial(0.002, MM); 78 | minimumChordLength = spatial(0.25, MM); 79 | minimumCircularRadius = spatial(0.125, MM); 80 | maximumCircularRadius = spatial(1000, MM); 81 | minimumCircularSweep = toRad(0.1); // was 0.01 82 | maximumCircularSweep = toRad(180); 83 | allowHelicalMoves = true; 84 | allowSpiralMoves = false; 85 | allowedCircularPlanes = (1 << PLANE_XY); // allow only XY plane 86 | // if you need vertical arcs then uncomment the line below 87 | //allowedCircularPlanes = (1 << PLANE_XY) | (1 << PLANE_ZX) | (1 << PLANE_YZ); // allow all planes, recentering arcs solves YZ/XZ arcs 88 | // if you allow vertical arcs then be aware that ObCONTROL will not display the gcode correctly, but it WILL cut correctly. 89 | 90 | // things for splitting on linecount, aka tapesplitting 91 | var SPL = 92 | { 93 | tapelines : 0, 94 | linecnt : 0, 95 | forceSplit : false 96 | } 97 | 98 | // user-defined properties : defaults are set, but they can be changed from a dialog box in Fusion when doing a post. 99 | properties = 100 | { 101 | spindleOnOffDelay: 1.8, // time (in seconds) the spindle needs to get up to speed or stop, or laser/plasma pierce delay 102 | spindleTwoDirections : false, // true : spindle can rotate clockwise and counterclockwise, will send M3 and M4. false : spindle can only go clockwise, will only send M3 103 | hasCoolant : false, // true : machine uses the coolant output, M8 M9 will be sent. false : coolant output not connected, so no M8 M9 will be sent 104 | routerType : "other", 105 | generateMultiple: true, // specifies if a file should be generated for each tool change 106 | splitLines: 0, // if > 0 then split on line count (and tool change if that is also set) 107 | machineHomeZ : -10, // absolute machine coordinates where the machine will move to at the end of the job - first retracting Z, then moving home X Y 108 | machineHomeX : -10, // always in millimeters 109 | machineHomeY : -10, 110 | gotoMCSatend : false, // true will do G53 G0 x{machinehomeX} y{machinehomeY}, false will do G0 x{machinehomeX} y{machinehomeY} at end of program 111 | PowerVaporise : 5, // cutting power in percent, to vaporize plastic coatings 112 | PowerThrough : 100, // for through cutting 113 | PowerEtch : 10, // for etching the surface 114 | UseZ : false, // if true then Z will be moved to 0 at beginning and back to 'retract height' at end 115 | UsePierce : false, // if true && islaser && cutting use M3 and honor pierce delays, else use M4 116 | //plasma stuff 117 | plasma_usetouchoff : false, // use probe for touchoff if true 118 | plasma_touchoffOffset : 5.0, // offset from trigger point to real Z0, used in G10 line 119 | plasma_pierceHeightoverride: false, // if true replace all pierce height settings with value below 120 | plasma_pierceHeightValue : toPreciseUnit(10,MM), //mmOrInch(10, 0.375), // not forcing mm, user beware 121 | plasma_postcutdelay : 0, // seconds to delay after the cut stops to allow assist air to bleed off 122 | 123 | linearizeSmallArcs: true, // arcs with radius < toolRadius have radius errors, linearize instead? 124 | machineVendor : "OpenBuilds", 125 | modelMachine : "Generic XYZ", 126 | machineControl : "Grbl 1.1 / BlackBox", 127 | 128 | checkZ : false, // true for a PS tool height checkmove at start of every file 129 | checkFeed : 200 // always MM/min 130 | //postProcessorDocs : 'https://docs.openbuilds.com/doku.php', // for future use. link to post processor help docs. be sure to uncomment comment as well 131 | }; 132 | 133 | // user-defined property definitions - note, do not skip any group numbers 134 | groupDefinitions = 135 | { 136 | //postInfo: {title: "OpenBuilds Post Documentation: https://docs.openbuilds.com/doku.php", description: "", order: 0}, 137 | spindle: {title: "Spindle/Plasma", description: "Spindle and Plasma Cutter options", order: 1}, 138 | safety: {title: "Safety", description: "Safety options", order: 2}, 139 | toolChange: {title: "Tool Changes", description: "Tool change options", order: 3}, 140 | startEndPos: {title: "Job Start Z and Job End X,Y,Z Coordinates", description: "Set the spindle start and end position", order: 4}, 141 | arcs: {title: "Arcs", description: "Arc options", order: 5}, 142 | laserPlasma: {title: "Laser / Plasma", description: "Laser / Plasma options", order: 6}, 143 | machine: {title: "Machine", description: "Machine options", order: 7} 144 | }; 145 | propertyDefinitions = 146 | { 147 | /* 148 | postProcessorDocs: { 149 | group: "postInfo", 150 | title: "Copy and paste linke to docs", 151 | description: "Link to docs", 152 | type: "string", 153 | }, 154 | */ 155 | routerType: { 156 | group: "spindle", 157 | title: "SPINDLE Router type", 158 | description: "Select the type of spindle you have.", 159 | type: "enum", 160 | values: [ 161 | {title:"Other", id:"other"}, 162 | {title: "Router11", id: "Router11"}, 163 | {title: "Makita RT0701", id: "Makita"}, 164 | {title: "Dewalt 611", id: "Dewalt"} 165 | ] 166 | }, 167 | spindleTwoDirections: { 168 | group: "spindle", 169 | title: "SPINDLE can rotate clockwise and counterclockwise?", 170 | description: "Yes : spindle can rotate clockwise and counterclockwise, will send M3 and M4. No : spindle can only go clockwise, will only send M3", 171 | type: "boolean", 172 | }, 173 | spindleOnOffDelay: { 174 | group: "spindle", 175 | title: "SPINDLE on/off or Plasma Pierce Delay", 176 | description: "Time (in seconds) the spindle needs to get up to speed or stop, also used for plasma pierce delay if > 0, else uses tool.pierceTime", 177 | type: "number", 178 | }, 179 | hasCoolant: { 180 | group: "spindle", 181 | title: "SPINDLE Has coolant?", 182 | description: "Yes: machine uses the coolant output, M8 M9 will be sent. No : coolant output not connected, so no M8 M9 will be sent", 183 | type: "boolean", 184 | }, 185 | checkFeed: { 186 | group: "safety", 187 | title: "SAFETY: Check tool feedrate", 188 | description: "Feedrate to be used for the tool length check, always millimeters.", 189 | type: "spatial", 190 | }, 191 | checkZ: { 192 | group: "safety", 193 | title: "SAFETY: Check tool Z length?", 194 | description: "Insert a safe move and program pause M0 to check for tool length, tool will lower to clearanceHeight set in the Heights tab.", 195 | type: "boolean", 196 | }, 197 | 198 | generateMultiple: { 199 | group: "toolChange", 200 | title: "TOOL: Generate muliple files for tool changes?", 201 | description: "Generate multiple files. One for each tool change.", 202 | type: "boolean", 203 | }, 204 | splitLines: { 205 | group: "toolChange", 206 | title: "Split on line count (0 for none)", 207 | description: "Split files after given number of lines, or 0 for no split on line count.", 208 | type: "number", 209 | }, 210 | 211 | gotoMCSatend: { 212 | group: "startEndPos", 213 | title: "EndPos: Use Machine Coordinates (G53) at end of job?", 214 | description: "Yes will do G53 G0 x{machinehomeX} y(machinehomeY) (Machine Coordinates), No will do G0 x(machinehomeX) y(machinehomeY) (Work Coordinates) at end of program", 215 | type: "boolean", 216 | }, 217 | machineHomeX: { 218 | group: "startEndPos", 219 | title: "EndPos: End of job X position (MM).", 220 | description: "(G53 or G54) X position to move to in Millimeters", 221 | type: "spatial", 222 | }, 223 | machineHomeY: { 224 | group: "startEndPos", 225 | title: "EndPos: End of job Y position (MM).", 226 | description: "(G53 or G54) Y position to move to in Millimeters.", 227 | type: "spatial", 228 | }, 229 | machineHomeZ: { 230 | group: "startEndPos", 231 | title: "startEndPos: START and End of job Z position (MCS Only) (MM)", 232 | description: "G53 Z position to move to in Millimeters, normally negative. Moves to this distance below Z home.", 233 | type: "spatial", 234 | }, 235 | 236 | linearizeSmallArcs: { 237 | group: "arcs", 238 | title: "ARCS: Linearize Small Arcs", 239 | description: "Arcs with radius < toolRadius can have mismatched radii, set this to Yes to linearize them. This solves G2/G3 radius mismatch errors.", 240 | type: "boolean", 241 | }, 242 | 243 | PowerVaporise: {title: "LASER: Power for Vaporizing", description: "Just enough Power to VAPORIZE plastic coating, in percent.", group: "laserPlasma", type: "integer"}, 244 | PowerThrough: {title: "LASER: Power for Through Cutting", description: "Normal Through cutting power, in percent.", group: "laserPlasma", type: "integer"}, 245 | PowerEtch: {title: "LASER: Power for Etching", description: "Just enough power to Etch the surface, in percent.", group: "laserPlasma", type: "integer"}, 246 | UseZ: {title: "P+L: Use Z motions at start and end.", description: "Use True if you have a laser on a router with Z motion, or a PLASMA cutter.", group: "laserPlasma", type: "boolean"}, 247 | UsePierce: {title: "LASER: Use pierce delays with M3 motion when cutting.", description: "True will use M3 commands and pierce delays, else use M4 with no delays.", group: "laserPlasma", type: "boolean"}, 248 | plasma_usetouchoff: {title: "PLASMA: Use Z touchoff probe routine", description: "Set to true if have a touchoff probe for Plasma.", group: "laserPlasma", type: "boolean"}, 249 | plasma_touchoffOffset: {title: "PLASMA: Plasma touch probe offset", description: "Offset in Z at which the probe triggers, always Millimeters, always positive.", group: "laserPlasma", type: "spatial"}, 250 | plasma_pierceHeightoverride: {title: "P+L: Override the pierce height", description: "Set to true if want to always use the pierce height Z value.", group: "laserPlasma", type: "boolean"}, 251 | plasma_pierceHeightValue : {title: "P+L: Override the pierce height Z value", description: "Offset in Z for the plasma pierce height, always positive. Set to 0 to avoid all piercing.", group: "laserPlasma", type: "spatial"}, 252 | plasma_postcutdelay : {title: "PLASMA: Seconds to delay after cut", description: "Seconds to delay after cut stops to allow assist air to bleed off before next pierce.", group: "laserPlasma", type: "number"}, 253 | 254 | machineVendor: { 255 | group: "machine", 256 | title: "Machine Vendor", 257 | description: "Machine vendor defined here will be displayed in header if machine config not set.", 258 | type: "string", 259 | }, 260 | modelMachine: { 261 | group: "machine", 262 | title: "Machine Model", 263 | description: "Machine model defined here will be displayed in header if machine config not set.", 264 | type: "string", 265 | }, 266 | machineControl: { 267 | group: "machine", 268 | title: "Machine Control", 269 | description: "Machine control defined here will be displayed in header if machine config not set.", 270 | type: "string", 271 | } 272 | }; 273 | 274 | // USER ADJUSTMENTS FOR PLASMA 275 | plasma_probedistance = 15; // distance to probe down in Z, always in millimeters 276 | plasma_proberate = 100; // feedrate for probing, in mm/minute 277 | // END OF USER ADJUSTMENTS 278 | 279 | 280 | // creation of all kinds of G-code formats - controls the amount of decimals used in the generated G-Code 281 | var gFormat = createFormat({prefix: "G", decimals: 0}); 282 | var gPFormat = createFormat({prefix: "G", decimals: 1}); // for probing commands 283 | var mFormat = createFormat({prefix: "M", decimals: 0}); 284 | 285 | var xyzFormat = createFormat({decimals: (unit == MM ? 3 : 4)}); 286 | var abcFormat = createFormat({decimals: 3, forceDecimal: true, scale: DEG}); 287 | var arcFormat = createFormat({decimals: (unit == MM ? 3 : 4)}); 288 | var feedFormat = createFormat({decimals: 0}); 289 | var rpmFormat = createFormat({decimals: 0}); 290 | var pFormat = createFormat({decimals: 0}); 291 | var secFormat = createFormat({decimals: 3, forceDecimal: true}); // seconds 292 | //var taperFormat = createFormat({decimals:1, scale:DEG}); 293 | 294 | var xOutput = createVariable({prefix: "X", force: false}, xyzFormat); 295 | var yOutput = createVariable({prefix: "Y", force: false}, xyzFormat); 296 | var zOutput = createVariable({prefix: "Z", force: false}, xyzFormat); // dont need Z every time 297 | var feedOutput = createVariable({prefix: "F"}, feedFormat); 298 | var sOutput = createVariable({prefix: "S", force: false}, rpmFormat); 299 | var pWord = createVariable({prefix: "P", force: true}, pFormat); 300 | var mOutput = createVariable({force: false}, mFormat); // only use for M3/4/5 301 | 302 | // for arcs 303 | var iOutput = createReferenceVariable({prefix: "I", force: true}, arcFormat); 304 | var jOutput = createReferenceVariable({prefix: "J", force: true}, arcFormat); 305 | var kOutput = createReferenceVariable({prefix: "K", force: true}, arcFormat); 306 | 307 | var gMotionModal = createModal({}, gFormat); // modal group 1 // G0-G3, ... 308 | var gProbeModal = createModal({onchange: function () { gMotionModal.reset(); }, force: true }, gPFormat); 309 | var gPlaneModal = createModal({onchange: function () 310 | { 311 | gMotionModal.reset(); 312 | } 313 | }, gFormat); // modal group 2 // G17-19 314 | var gAbsIncModal = createModal({}, gFormat); // modal group 3 // G90-91 315 | var gFeedModeModal = createModal({}, gFormat); // modal group 5 // G93-94 316 | var gUnitModal = createModal({}, gFormat); // modal group 6 // G20-21 317 | var gWCSOutput = createModal({}, gFormat); // for G54 G55 etc 318 | 319 | var sequenceNumber = 1; //used for multiple file naming 320 | var multipleToolError = false; //used for alerting during single file generation with multiple tools 321 | var filesToGenerate = 1; //used to figure out how many files will be generated so we can diplay in header 322 | var minimumFeedRate = toPreciseUnit(45, MM); // GRBL lower limit in mm/minute 323 | var fileIndexFormat = createFormat({width: 2, zeropad: true, decimals: 0}); 324 | var isNewfile = false; // set true when a new file has just been started 325 | 326 | // group our private variables together to Autodesk does not break us when they make something into a property 327 | var OB = { 328 | power : 0, // the setpower value, for S word when laser/plasma cutting 329 | powerOn : false, // is the laser power on? used for laser when haveRapid=false 330 | isLaser : false, // set true for laser/water/ 331 | isPlasma : false, // set true for plasma 332 | cutmode : 0, // M3 or M4 333 | cuttingMode : 'none', // set by onParameter for laser/plasma 334 | haveRapid : false // assume no rapid moves 335 | } 336 | 337 | var plasma = { 338 | pierceHeight : 3.14, // set by onParameter or tool.pierceHeight 339 | pierceTime : 3.14, // plasma pierce delay set by tool if properties.spindleOnOffDelay = 0 340 | leadinRate : 314, // set by onParameter: the lead-in feedrate,plasma : onparameter:movement:lead_in is always metric so convert when needed 341 | cutHeight : 3.14, // tool cut height from onParameter 342 | mode : 1 // mode 1 is the old mode using topheight as cutheight, mode 2 is new mode, using tool cutheight 343 | } 344 | 345 | //var workOffset = 0; 346 | var retractHeight = 1; // will be set by onParameter and used in onLinear to detect rapids 347 | var clearanceHeight = 10; // will be set by onParameter 348 | var topHeight = 1; // set by onParameter 349 | var linmove = 1; // linear move mode 350 | var toolRadius; // for arc linearization 351 | var coolantIsOn = 0; // set when coolant is used to we can do intelligent turn off 352 | var currentworkOffset = 54;// the current WCS in use, so we can retract Z between sections if needed 353 | var clnt = ''; // coolant code to add to spindle line 354 | 355 | var PRB = { 356 | feedProbeLink : 1000, // probe linking moves feedrate 357 | feedProbeMeasure : 102, // probing feedrate 358 | probe_output_work_offset : 0 // the WCS to update when probing 359 | } 360 | 361 | // Start of machine configuration logic 362 | var compensateToolLength = false; // add the tool length to the pivot distance for nonTCP rotary heads 363 | 364 | // internal variables, do not change 365 | var receivedMachineConfiguration; 366 | var operationSupportsTCP; 367 | var multiAxisFeedrate; 368 | 369 | function activateMachine() { 370 | // disable unsupported rotary axes output 371 | if (!machineConfiguration.isMachineCoordinate(0) && (typeof aOutput != "undefined")) { 372 | aOutput.disable(); 373 | } 374 | if (!machineConfiguration.isMachineCoordinate(1) && (typeof bOutput != "undefined")) { 375 | bOutput.disable(); 376 | } 377 | if (!machineConfiguration.isMachineCoordinate(2) && (typeof cOutput != "undefined")) { 378 | cOutput.disable(); 379 | //machineConfiguration.setControl(properties.machineControl); 380 | } 381 | 382 | // setup usage of multiAxisFeatures 383 | useMultiAxisFeatures = getProperty("useMultiAxisFeatures") != undefined ? getProperty("useMultiAxisFeatures") : 384 | (typeof useMultiAxisFeatures != "undefined" ? useMultiAxisFeatures : false); 385 | useABCPrepositioning = getProperty("useABCPrepositioning") != undefined ? getProperty("useABCPrepositioning") : 386 | (typeof useABCPrepositioning != "undefined" ? useABCPrepositioning : false); 387 | 388 | if (!machineConfiguration.isMultiAxisConfiguration()) { 389 | return; // don't need to modify any settings for 3-axis machines 390 | } 391 | 392 | // save multi-axis feedrate settings from machine configuration 393 | var mode = machineConfiguration.getMultiAxisFeedrateMode(); 394 | var type = mode == FEED_INVERSE_TIME ? machineConfiguration.getMultiAxisFeedrateInverseTimeUnits() : 395 | (mode == FEED_DPM ? machineConfiguration.getMultiAxisFeedrateDPMType() : DPM_STANDARD); 396 | multiAxisFeedrate = { 397 | mode : mode, 398 | maximum : machineConfiguration.getMultiAxisFeedrateMaximum(), 399 | type : type, 400 | tolerance: mode == FEED_DPM ? machineConfiguration.getMultiAxisFeedrateOutputTolerance() : 0, 401 | bpwRatio : mode == FEED_DPM ? machineConfiguration.getMultiAxisFeedrateBpwRatio() : 1 402 | }; 403 | 404 | // setup of retract/reconfigure TAG: Only needed until post kernel supports these machine config settings 405 | if (receivedMachineConfiguration && machineConfiguration.performRewinds()) { 406 | safeRetractDistance = machineConfiguration.getSafeRetractDistance(); 407 | safePlungeFeed = machineConfiguration.getSafePlungeFeedrate(); 408 | safeRetractFeed = machineConfiguration.getSafeRetractFeedrate(); 409 | } 410 | if (typeof safeRetractDistance == "number" && getProperty("safeRetractDistance") != undefined && getProperty("safeRetractDistance") != 0) { 411 | safeRetractDistance = getProperty("safeRetractDistance"); 412 | } 413 | 414 | if (machineConfiguration.isHeadConfiguration()) { 415 | compensateToolLength = typeof compensateToolLength == "undefined" ? false : compensateToolLength; 416 | } 417 | 418 | if (machineConfiguration.isHeadConfiguration() && compensateToolLength) { 419 | for (var i = 0; i < getNumberOfSections(); ++i) { 420 | var section = getSection(i); 421 | if (section.isMultiAxis()) { 422 | machineConfiguration.setToolLength(getBodyLength(section.getTool())); // define the tool length for head adjustments 423 | section.optimizeMachineAnglesByMachine(machineConfiguration, OPTIMIZE_AXIS); 424 | } 425 | } 426 | } else { 427 | optimizeMachineAngles2(OPTIMIZE_AXIS); 428 | } 429 | } 430 | 431 | function getBodyLength(tool) { 432 | for (var i = 0; i < getNumberOfSections(); ++i) { 433 | var section = getSection(i); 434 | if (tool.number == section.getTool().number) { 435 | return section.getParameter("operation:tool_overallLength", tool.bodyLength + tool.holderLength); 436 | } 437 | } 438 | return tool.bodyLength + tool.holderLength; 439 | } 440 | 441 | function defineMachine() { 442 | var useTCP = true; 443 | if (false) { // note: setup your machine here 444 | var aAxis = createAxis({coordinate:0, table:true, axis:[1, 0, 0], range:[-120, 120], preference:1, tcp:useTCP}); 445 | var cAxis = createAxis({coordinate:2, table:true, axis:[0, 0, 1], range:[-360, 360], preference:0, tcp:useTCP}); 446 | machineConfiguration = new MachineConfiguration(aAxis, cAxis); 447 | 448 | setMachineConfiguration(machineConfiguration); 449 | if (receivedMachineConfiguration) { 450 | warning(localize("The provided CAM machine configuration is overwritten by the postprocessor.")); 451 | receivedMachineConfiguration = false; // CAM provided machine configuration is overwritten 452 | } 453 | } 454 | 455 | if (!receivedMachineConfiguration) { 456 | // multiaxis settings 457 | if (machineConfiguration.isHeadConfiguration()) { 458 | machineConfiguration.setVirtualTooltip(false); // translate the pivot point to the virtual tool tip for nonTCP rotary heads 459 | } 460 | 461 | // retract / reconfigure 462 | var performRewinds = false; // set to true to enable the rewind/reconfigure logic 463 | if (performRewinds) { 464 | machineConfiguration.enableMachineRewinds(); // enables the retract/reconfigure logic 465 | safeRetractDistance = (unit == IN) ? 1 : 25; // additional distance to retract out of stock, can be overridden with a property 466 | safeRetractFeed = (unit == IN) ? 20 : 500; // retract feed rate 467 | safePlungeFeed = (unit == IN) ? 10 : 250; // plunge feed rate 468 | machineConfiguration.setSafeRetractDistance(safeRetractDistance); 469 | machineConfiguration.setSafeRetractFeedrate(safeRetractFeed); 470 | machineConfiguration.setSafePlungeFeedrate(safePlungeFeed); 471 | var stockExpansion = new Vector(toPreciseUnit(0.1, IN), toPreciseUnit(0.1, IN), toPreciseUnit(0.1, IN)); // expand stock XYZ values 472 | machineConfiguration.setRewindStockExpansion(stockExpansion); 473 | } 474 | 475 | // multi-axis feedrates 476 | if (machineConfiguration.isMultiAxisConfiguration()) { 477 | machineConfiguration.setMultiAxisFeedrate( 478 | useTCP ? FEED_FPM : FEED_INVERSE_TIME, 479 | 9999.99, // maximum output value for inverse time feed rates 480 | INVERSE_MINUTES, // INVERSE_MINUTES/INVERSE_SECONDS or DPM_COMBINATION/DPM_STANDARD 481 | 0.5, // tolerance to determine when the DPM feed has changed 482 | 1.0 // ratio of rotary accuracy to linear accuracy for DPM calculations 483 | ); 484 | setMachineConfiguration(machineConfiguration); 485 | } 486 | 487 | /* home positions */ 488 | // machineConfiguration.setHomePositionX(toPreciseUnit(0, IN)); 489 | // machineConfiguration.setHomePositionY(toPreciseUnit(0, IN)); 490 | // machineConfiguration.setRetractPlane(toPreciseUnit(0, IN)); 491 | } 492 | } 493 | // End of machine configuration logic ====================================================================== 494 | 495 | function toTitleCase(str) 496 | { 497 | // function to reformat a string to 'title case' 498 | return str.replace( /\w\S*/g, function(txt) 499 | { 500 | // /\w\S*/g keep that format, astyle will put spaces in it 501 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); 502 | }); 503 | } 504 | 505 | function rpm2dial(rpm, op) 506 | { 507 | // translates an RPM for the spindle into a dial value, eg. for the Makita RT0700 and Dewalt 611 routers 508 | // additionally, check that spindle rpm is between minimum and maximum of what our spindle can do 509 | // array which maps spindle speeds to router dial settings, 510 | // according to Makita RT0700 Manual : 1=10000, 2=12000, 3=17000, 4=22000, 5=27000, 6=30000 511 | // according to Dewalt 611 Manual : 1=16000, 2=18200, 3=20400, 4=22600, 5=24800, 6=27000 512 | var wmsg = ""; 513 | 514 | if (isProbeOperation()) 515 | return 1; 516 | 517 | if (properties.routerType == "Dewalt") 518 | { 519 | var speeds = [0, 16000, 18200, 20400, 22600, 24800, 27000]; 520 | } 521 | else 522 | if (properties.routerType == "Router11") 523 | { 524 | var speeds = [0, 10000, 14000, 18000, 23000, 27000, 32000]; 525 | } 526 | else 527 | { 528 | // this is Makita R0701 529 | var speeds = [0, 10000, 12000, 17000, 22000, 27000, 30000]; 530 | } 531 | 532 | if (rpm < speeds[1]) 533 | { 534 | wmsg = "WARNING " + rpm + " rpm is below minimum spindle RPM of " + speeds[1] + " rpm in the " + op + " operation."; 535 | warning(wmsg); 536 | writeComment(wmsg); 537 | return 1; 538 | } 539 | 540 | if (rpm > speeds[speeds.length - 1]) 541 | { 542 | wmsg = "WARNING " + rpm + " rpm is above maximum spindle RPM of " + speeds[speeds.length - 1] + " rpm in the " + op + " operation."; 543 | warning(wmsg); 544 | writeComment(wmsg); 545 | return (speeds.length - 1); 546 | } 547 | 548 | var i; 549 | for (i = 1; i < (speeds.length - 1); i++) 550 | { 551 | if ((rpm >= speeds[i]) && (rpm <= speeds[i + 1])) 552 | { 553 | return (((rpm - speeds[i]) / (speeds[i + 1] - speeds[i])) + i).toFixed(1); 554 | } 555 | } 556 | 557 | error("Fatal Error calculating router speed dial."); 558 | return 0; 559 | } 560 | 561 | function checkMinFeedrate(section, op) 562 | { 563 | var alertMsg = ""; 564 | if (section.getParameter("operation:tool_feedCutting") < minimumFeedRate) 565 | { 566 | alertMsg = "Cutting\n"; 567 | } 568 | 569 | if (section.getParameter("operation:tool_feedRetract") < minimumFeedRate) 570 | { 571 | alertMsg = alertMsg + "Retract\n"; 572 | } 573 | 574 | if (section.getParameter("operation:tool_feedEntry") < minimumFeedRate) 575 | { 576 | alertMsg = alertMsg + "Entry\n"; 577 | } 578 | 579 | if (section.getParameter("operation:tool_feedExit") < minimumFeedRate) 580 | { 581 | alertMsg = alertMsg + "Exit\n"; 582 | } 583 | 584 | if (section.getParameter("operation:tool_feedRamp") < minimumFeedRate) 585 | { 586 | alertMsg = alertMsg + "Ramp\n"; 587 | } 588 | 589 | if (section.getParameter("operation:tool_feedPlunge") < minimumFeedRate) 590 | { 591 | alertMsg = alertMsg + "Plunge\n"; 592 | } 593 | 594 | if (alertMsg != "") 595 | { 596 | var fF = createFormat({decimals: 0, suffix: (unit == MM ? "mm" : "in" )}); 597 | var fo = createVariable({}, fF); 598 | var wmsg = "WARNING " + "The following feedrates in " + op + " are set below the minimum feedrate that GRBL supports. The feedrate should be higher than " + fo.format(minimumFeedRate) + " per minute.\n\n" + alertMsg; 599 | warning(wmsg); 600 | writeComment(wmsg); 601 | } 602 | } 603 | 604 | /** 605 | * write a block of gcode 606 | * counts lines if tapelines is set 607 | */ 608 | function writeBlock() 609 | { 610 | writeWords(arguments); 611 | if (SPL.tapelines) SPL.linecnt++; 612 | } 613 | 614 | /** 615 | Thanks to nyccnc.com 616 | Thanks to the Autodesk Knowledge Network for help with this at 617 | https://knowledge.autodesk.com/support/hsm/learn-explore/caas/sfdcarticles/sfdcarticles/How-to-use-Manual-NC-options-to-manually-add-code-with-Fusion-360-HSM-CAM.html! 618 | */ 619 | function onPassThrough(text) 620 | { 621 | var commands = String(text).split(","); 622 | for (text in commands) 623 | { 624 | writeBlock(commands[text]); 625 | } 626 | } 627 | 628 | function myMachineConfig() 629 | { 630 | // 3. here you can set all the properties of your machine if you havent set up a machine config in CAM. These are optional and only used to print in the header. 631 | myMachine = getMachineConfiguration(); 632 | if (!myMachine.getVendor()) 633 | { 634 | // machine config not found so we'll use the info below 635 | myMachine.setWidth(600); 636 | myMachine.setDepth(800); 637 | myMachine.setHeight(130); 638 | myMachine.setMaximumSpindlePower(700); 639 | myMachine.setMaximumSpindleSpeed(30000); 640 | myMachine.setMilling(true); 641 | myMachine.setTurning(false); 642 | myMachine.setToolChanger(false); 643 | myMachine.setNumberOfTools(1); 644 | myMachine.setNumberOfWorkOffsets(6); 645 | myMachine.setVendor(properties.machineVendor); 646 | myMachine.setModel(properties.modelMachine); 647 | myMachine.setControl(properties.machineControl); 648 | } 649 | } 650 | 651 | // Remove special characters which could confuse GRBL : $, !, ~, ?, (, ) 652 | // In order to make it simple, I replace everything which is not A-Z, 0-9, space, : , . 653 | // Finally put everything between () as this is the way GRBL & UGCS expect comments 654 | function formatComment(text) 655 | { 656 | return ("(" + filterText(String(text), permittedCommentChars) + ")"); 657 | } 658 | 659 | /** 660 | * returns the time as 'machining time 00h00m00s' 661 | */ 662 | function getMachineTime(sec) 663 | { 664 | var machineTimeInSeconds = sec.getCycleTime(); 665 | var machineTimeHours = Math.floor(machineTimeInSeconds / 3600); 666 | machineTimeInSeconds = machineTimeInSeconds % 3600; 667 | var machineTimeMinutes = Math.floor(machineTimeInSeconds / 60); 668 | var machineTimeSeconds = Math.floor(machineTimeInSeconds % 60); 669 | var machineTimeText = " Machining time : "; 670 | machineTimeText += subst(localize("%1h:%2m:%3s"), machineTimeHours, machineTimeMinutes, machineTimeSeconds); 671 | return machineTimeText; 672 | } 673 | 674 | function writeComment(text) 675 | { 676 | // v20 - split the line so no comment is longer than 70 chars 677 | if (text) 678 | if (text.length > 70) 679 | { 680 | //text = String(text).replace( /[^a-zA-Z\d:=,.]+/g, " "); // remove illegal chars 681 | text = filterText(text.trim(), permittedCommentChars); 682 | var bits = text.split(" "); // get all the words 683 | var out = ''; 684 | for (i = 0; i < bits.length; i++) 685 | { 686 | out += bits[i] + " "; // additional space after first line 687 | if (out.length > 60) // a long word on the end can take us to 80 chars! 688 | { 689 | writeln(formatComment( out.trim() ) ); 690 | out = ""; 691 | } 692 | } 693 | if (out.length > 0) 694 | writeln(formatComment( out.trim() ) ); 695 | } 696 | else 697 | writeln(formatComment(text)); 698 | } 699 | 700 | function writeHeader(secID) 701 | { 702 | //writeComment("Header start " + secID); 703 | if (multipleToolError) 704 | { 705 | writeComment("Warning: Multiple tools found. This post does not support tool changes. You should repost and select True for Multiple Files in the post properties."); 706 | writeln(""); 707 | } 708 | 709 | var productName = getProduct(); 710 | writeComment("Made in : " + productName); 711 | writeComment("G-Code optimized for " + properties.machineControl + " controller"); 712 | writeComment(description); 713 | cpsname = FileSystem.getFilename(getConfigurationPath()); 714 | writeComment("Post-Processor : " + cpsname + " " + obversion ); 715 | //writeComment("Post processor documentation: " + properties.postProcessorDocs ); 716 | var unitstr = (unit == MM) ? 'mm' : 'inch'; 717 | writeComment("Units = " + unitstr ); 718 | if (isJet()) 719 | { 720 | writeComment("Laser UseZ = " + properties.UseZ); 721 | writeComment("Laser UsePierce = " + properties.UsePierce); 722 | } 723 | if (allowedCircularPlanes == 1) 724 | { 725 | writeln(""); 726 | writeComment("Arcs are limited to the XY plane: if you want vertical arcs then edit allowedCircularPlanes in the CPS file"); 727 | } 728 | else 729 | { 730 | writeln(""); 731 | writeComment("Arcs can occur on XY,YZ,ZX planes: CONTROL may not display them correctly but they will cut correctly"); 732 | } 733 | 734 | writeln(""); 735 | if (hasGlobalParameter("document-path")) 736 | { 737 | var path = getGlobalParameter("document-path"); 738 | if (path) 739 | { 740 | writeComment("Drawing name : " + path); 741 | } 742 | } 743 | 744 | if (programName) 745 | { 746 | writeComment("Program Name : " + programName); 747 | } 748 | if (programComment) 749 | { 750 | writeComment("Program Comments : " + programComment); 751 | } 752 | writeln(""); 753 | numberOfSections = getNumberOfSections(); 754 | if (properties.generateMultiple && filesToGenerate > 1) 755 | { 756 | if (properties.splitLines > 0) 757 | { 758 | writeComment("Since we are splitting on line count we don't know how many files will be written."); 759 | writeComment("There will be at least " + filesToGenerate + " files, from the number of tools."); 760 | writeComment("Files will be named like programName.01ofMany.nc") 761 | writeComment(numberOfSections + " Operation" + ((numberOfSections == 1) ? "" : "s") ); 762 | } 763 | else 764 | { 765 | writeComment(numberOfSections + " Operation" + ((numberOfSections == 1) ? "" : "s") + " in " + filesToGenerate + " files."); 766 | writeComment("File List:"); 767 | //writeComment(" " + FileSystem.getFilename(getOutputPath())); 768 | for (var i = 0; i < filesToGenerate; ++i) 769 | { 770 | filename = makeFileName(i + 1); 771 | writeComment(" " + filename); 772 | } 773 | writeln(""); 774 | writeComment("This is file: " + sequenceNumber + " of " + filesToGenerate); 775 | } 776 | writeln(""); 777 | writeComment("This file contains the following operations: "); 778 | } 779 | else 780 | { 781 | writeComment(numberOfSections + " Operation" + ((numberOfSections == 1) ? "" : "s") + " :"); 782 | } 783 | 784 | for (var i = secID; i < numberOfSections; ++i) 785 | { 786 | var section = getSection(i); 787 | var tool = section.getTool(); 788 | var rpm = section.getMaximumSpindleSpeed(); 789 | OB.isLaser = OB.isPlasma = false; 790 | switch (tool.type) 791 | { 792 | case TOOL_LASER_CUTTER: 793 | OB.isLaser = true; 794 | break; 795 | case TOOL_WATER_JET: 796 | case TOOL_PLASMA_CUTTER: 797 | OB.isPlasma = true; 798 | break; 799 | default: 800 | OB.isLaser = false; 801 | OB.isPlasma = false; 802 | } 803 | 804 | if (section.hasParameter("operation-comment")) 805 | { 806 | writeComment((i + 1) + " : " + section.getParameter("operation-comment")); 807 | var op = section.getParameter("operation-comment") 808 | } 809 | else 810 | { 811 | writeComment(i + 1); 812 | var op = i + 1; 813 | } 814 | if (section.workOffset > 0) 815 | { 816 | writeComment(" Work Coordinate System : G" + (section.workOffset + 53)); 817 | } 818 | if (OB.isLaser || OB.isPlasma) 819 | { 820 | writeComment(" Tool #" + tool.number + ": " + toTitleCase(getToolTypeName(tool.type)) + " Diam = " + xyzFormat.format(tool.jetDiameter) + unitstr); 821 | writeComment("howto use this post for plasma https://github.com/OpenBuilds/OpenBuilds-Fusion360-Postprocessor/blob/master/README-plasma.md"); 822 | } 823 | else 824 | { 825 | if (getToolTypeName( tool.type) == 'probe') 826 | writeComment(" Tool #" + tool.number + ": " + toTitleCase(getToolTypeName(tool.type)) + " Diam = " + xyzFormat.format(tool.diameter) + unitstr + ", Len = " + tool.fluteLength.toFixed(2) + unitstr); 827 | else 828 | { 829 | writeComment(" Tool #" + tool.number + ": " + toTitleCase(getToolTypeName(tool.type)) + " " + tool.numberOfFlutes + " Flutes, Diam = " + xyzFormat.format(tool.diameter) + unitstr + ", Len = " + tool.fluteLength.toFixed(2) + unitstr); 830 | if (isProbeOperation()) 831 | { 832 | writeComment('Probing, no dial to set') ; 833 | } 834 | else 835 | if (properties.routerType != "other") 836 | { 837 | writeComment(" Spindle : RPM = " + round(rpm, 0) + ", set " + properties.routerType + " dial to " + rpm2dial(rpm, op)); 838 | } 839 | else 840 | { 841 | writeComment(" Spindle : RPM = " + round(rpm, 0)); 842 | } 843 | } 844 | } 845 | if (section.strategy != 'probe') 846 | checkMinFeedrate(section, op); 847 | machineTimeText = getMachineTime(section); 848 | writeComment(machineTimeText); 849 | 850 | if (properties.generateMultiple && (i + 1 < numberOfSections)) 851 | { 852 | if (tool.number != getSection(i + 1).getTool().number) 853 | { 854 | writeln(""); 855 | writeComment("Remaining operations located in additional files."); 856 | break; 857 | } 858 | } 859 | } 860 | if (OB.isLaser || OB.isPlasma) 861 | { 862 | allowHelicalMoves = false; // laser/plasma not doing this, ever 863 | } 864 | writeln(""); 865 | 866 | gAbsIncModal.reset(); 867 | gFeedModeModal.reset(); 868 | gPlaneModal.reset(); 869 | writeBlock(gAbsIncModal.format(90), gFeedModeModal.format(94), gPlaneModal.format(17) ); 870 | switch (unit) 871 | { 872 | case IN: 873 | writeBlock(gUnitModal.format(20)); 874 | break; 875 | case MM: 876 | writeBlock(gUnitModal.format(21)); 877 | break; 878 | } 879 | //writeComment("Header end"); 880 | writeln(""); 881 | if (debugMode) 882 | { 883 | var msg = "debugMode is true"; 884 | writeComment(msg); 885 | warning(msg); 886 | writeln(""); 887 | } 888 | } 889 | 890 | function onOpen() 891 | { 892 | 893 | receivedMachineConfiguration = machineConfiguration.isReceived(); 894 | if (typeof defineMachine == "function") 895 | { 896 | defineMachine(); // hardcoded machine configuration 897 | } 898 | activateMachine(); // enable the machine optimizations and settings 899 | 900 | 901 | // 3. moved to top of file 902 | //myMachineConfig(); 903 | numberOfSections = getNumberOfSections(); 904 | if (properties.splitLines > 0) 905 | { 906 | SPL.tapelines = properties.splitLines; 907 | } 908 | 909 | if (debugMode) writeComment("onOpen"); 910 | // Number of checks capturing fatal errors 911 | // 2. is RadiusCompensation not set incorrectly ? 912 | onRadiusCompensation(); 913 | 914 | // 4. checking for duplicate tool numbers with the different geometry. 915 | // check for duplicate tool number 916 | for (var i = 0; i < getNumberOfSections(); ++i) 917 | { 918 | var sectioni = getSection(i); 919 | var tooli = sectioni.getTool(); 920 | if (i < (getNumberOfSections() - 1) && (tooli.number != getSection(i + 1).getTool().number)) 921 | { 922 | filesToGenerate++; 923 | } 924 | for (var j = i + 1; j < getNumberOfSections(); ++j) 925 | { 926 | var sectionj = getSection(j); 927 | var toolj = sectionj.getTool(); 928 | if (tooli.number == toolj.number) 929 | { 930 | if (xyzFormat.areDifferent(tooli.diameter, toolj.diameter) || 931 | xyzFormat.areDifferent(tooli.cornerRadius, toolj.cornerRadius) || 932 | abcFormat.areDifferent(tooli.taperAngle, toolj.taperAngle) || 933 | (tooli.numberOfFlutes != toolj.numberOfFlutes)) 934 | { 935 | error( subst( 936 | localize("Using the same tool number for different cutter geometry for operation '%1' and '%2'."), 937 | sectioni.hasParameter("operation-comment") ? sectioni.getParameter("operation-comment") : ("#" + (i + 1)), 938 | sectionj.hasParameter("operation-comment") ? sectionj.getParameter("operation-comment") : ("#" + (j + 1)) 939 | ) ); 940 | return; 941 | } 942 | } 943 | else 944 | { 945 | if (properties.generateMultiple == false) 946 | { 947 | multipleToolError = true; 948 | } 949 | } 950 | } 951 | } 952 | if (multipleToolError) 953 | { 954 | var mte = "WARNING " + "Multiple tools found. This post does not support tool changes. You should repost and select True for Multiple Files in the post properties."; 955 | warning(mte); 956 | writeComment(mte); 957 | } 958 | 959 | writeHeader(0); 960 | gMotionModal.reset(); 961 | 962 | if (properties.plasma_usetouchoff) 963 | properties.UseZ = true; // force it on, we need Z motion, always 964 | 965 | if (properties.UseZ) 966 | zOutput.format(1); 967 | else 968 | zOutput.format(0); 969 | //writeComment("onOpen end"); 970 | } 971 | 972 | function onComment(message) 973 | { 974 | writeComment(message); 975 | } 976 | 977 | function forceXYZ() 978 | { 979 | xOutput.reset(); 980 | yOutput.reset(); 981 | zOutput.reset(); 982 | } 983 | 984 | function forceAny() 985 | { 986 | forceXYZ(); 987 | feedOutput.reset(); 988 | gMotionModal.reset(); 989 | } 990 | 991 | function forceAll() 992 | { 993 | //writeComment("forceAll"); 994 | forceAny(); 995 | sOutput.reset(); 996 | gAbsIncModal.reset(); 997 | gFeedModeModal.reset(); 998 | gMotionModal.reset(); 999 | gPlaneModal.reset(); 1000 | gUnitModal.reset(); 1001 | gWCSOutput.reset(); 1002 | mOutput.reset(); 1003 | } 1004 | 1005 | // calculate the power setting for the laser 1006 | function calcPower(perc) 1007 | { 1008 | var PWMMin = 0; // make it easy for users to change this 1009 | var PWMMax = 1000; 1010 | var v = PWMMin + (PWMMax - PWMMin) * perc / 100.0; 1011 | return v; 1012 | } 1013 | 1014 | // go to initial position and optionally output the height check code before spindle turns on 1015 | function gotoInitial(checkit) 1016 | { 1017 | if (debugMode) writeComment("gotoInitial start"); 1018 | var sectionId = getCurrentSectionId(); // what is the number of this operation (starts from 0) 1019 | var section = getSection(sectionId); // what is the section-object for this operation 1020 | var maxfeedrate = section.getMaximumFeedrate(); 1021 | var f = ""; 1022 | 1023 | // Rapid move to initial position, first XY, then Z, and do tool height check if needed 1024 | forceAny(); 1025 | var initialPosition = getFramePosition(currentSection.getInitialPosition()); 1026 | if (OB.isLaser || OB.isPlasma) 1027 | { 1028 | f = feedOutput.format(maxfeedrate); 1029 | checkit = false; // never do a tool height check for laser/plasma, even if the user turns it on 1030 | } 1031 | else 1032 | f = ""; 1033 | writeBlock(gAbsIncModal.format(90), gMotionModal.format(0), xOutput.format(initialPosition.x), yOutput.format(initialPosition.y), f); 1034 | if (checkit) 1035 | if ( (isNewfile || isFirstSection()) && properties.checkZ && (properties.checkFeed > 0) ) 1036 | { 1037 | // do a Peter Stanton style Z seek and stop for a height check 1038 | z = zOutput.format(clearanceHeight); 1039 | f = feedOutput.format(toPreciseUnit(properties.checkFeed, MM)); 1040 | writeln("(Tool Height check https://youtu.be/WMsO24IqRKU?t=1059)"); 1041 | writeBlock(gMotionModal.format(1), z, f ); 1042 | writeBlock(mOutput.format(0)); 1043 | } 1044 | if (debugMode) writeComment("gotoInitial end"); 1045 | } 1046 | 1047 | /* 1048 | * write a G53 Z retract 1049 | * might need to gMotionModal.reset() before this to force output 1050 | */ 1051 | function writeZretract() 1052 | { 1053 | zOutput.reset(); 1054 | writeln("(This relies on homing, see https://openbuilds.com/search/127200199/?q=G53+fusion )"); 1055 | writeBlock(gFormat.format(53), gMotionModal.format(0), zOutput.format(toPreciseUnit( properties.machineHomeZ, MM))); // Retract spindle to Machine Z Home 1056 | gMotionModal.reset(); 1057 | zOutput.reset(); 1058 | } 1059 | 1060 | 1061 | function onSection() 1062 | { 1063 | var nmbrOfSections = getNumberOfSections(); // how many operations are there in total 1064 | var sectionId = getCurrentSectionId(); // what is the number of this operation (starts from 0) 1065 | var section = getSection(sectionId); // what is the section-object for this operation 1066 | var tool = section.getTool(); 1067 | var maxfeedrate = section.getMaximumFeedrate(); 1068 | var amProbing = false; 1069 | OB.haveRapid = false; // drilling sections will have rapids even when other ops do not, and so do probe routines 1070 | 1071 | onRadiusCompensation(); // must check every section 1072 | 1073 | if (OB.isPlasma || OB.isLaser) 1074 | { 1075 | //DAF Mar2024 - pierceclearance is not the pierceheight, that is defined for the tool 1076 | var whoami = OB.isLaser ? 'laser' : 'plasma'; 1077 | if (properties.plasma_pierceHeightoverride) 1078 | plasma.pierceHeight = parseFloat(properties.plasma_pierceHeightValue); 1079 | else 1080 | plasma.pierceHeight = tool.pierceHeight; // NOT pierceClearance! 1081 | // now we can do a valid height check 1082 | if (plasma.cutHeight > plasma.pierceHeight) 1083 | { 1084 | writeComment(whoami + ".cutHeight " + plasma.cutHeight) ; 1085 | writeComment(whoami + ".pierceHeight " + plasma.pierceHeight); 1086 | error("CUT HEIGHT MUST BE BELOW PLASMA TOOL PIERCE HEIGHT (tool setting)"); 1087 | } 1088 | if ( (plasma.cutHeight == 0) || (tool.cutHeight == 0) ) 1089 | if ((topHeight <= 0) && properties.plasma_usetouchoff) 1090 | error("TOPHEIGHT MUST BE GREATER THAN 0 (heights tab) when tool has no cutHeight"); 1091 | writeComment(whoami + " pierce height " + round(plasma.pierceHeight,3)); 1092 | writeComment(whoami + " topHeight " + round(topHeight,3)); 1093 | writeComment(whoami + " cutHeight " + round(plasma.cutHeight,3)); 1094 | writeComment(whoami + " pierceTime " + round(plasma.pierceTime,3)); 1095 | } 1096 | if (OB.isLaser || OB.isPlasma) 1097 | { 1098 | // fake the radius larger else the arcs are too small before being linearized since kerfwidth is very small compared to normal tools 1099 | toolRadius = tool.kerfWidth * 3; 1100 | } 1101 | else 1102 | { 1103 | toolRadius = tool.diameter / 2.0; 1104 | } 1105 | 1106 | //TODO : plasma check that top height mode is from stock top and the value is positive 1107 | //(onParameter =operation:topHeight mode= from stock top) 1108 | //(onParameter =operation:topHeight value= 0.8) 1109 | 1110 | var splitHere = !isFirstSection() && properties.generateMultiple && (tool.number != getPreviousSection().getTool().number); 1111 | // to split on linecount, we need to force it here 1112 | if (SPL.forceSplit) 1113 | { 1114 | splitHere = true; // will open a new file 1115 | writeComment('Starting new file due to line count'); 1116 | filesToGenerate++; 1117 | } 1118 | 1119 | if (splitHere) 1120 | { 1121 | sequenceNumber++; 1122 | var path = makeFileName(sequenceNumber); 1123 | if (SPL.forceSplit) 1124 | writeComment("Next file " + path); 1125 | 1126 | if (isRedirecting()) 1127 | { 1128 | if (debugMode) writeComment("onSection: closing redirection"); 1129 | onClose(); 1130 | closeRedirection(); 1131 | } 1132 | redirectToFile(path); 1133 | forceAll(); 1134 | writeHeader(getCurrentSectionId()); 1135 | isNewfile = true; // trigger a spindleondelay 1136 | } 1137 | if (SPL.forceSplit) 1138 | { 1139 | forceAll(); 1140 | writeComment("Continuing operation, run previous file, " + String(sequenceNumber - 1) + ", first"); 1141 | SPL.forceSplit = false; 1142 | gMotionModal.reset(); 1143 | writeZretract(); 1144 | } 1145 | 1146 | if (debugMode) writeComment("onSection " + sectionId); 1147 | writeln(""); // put these here so they go in the new file 1148 | //writeComment("Section : " + (sectionId + 1) + " haveRapid " + haveRapid); 1149 | 1150 | // Insert a small comment section to identify the related G-Code in a large multi-operations file 1151 | var comment = "Operation " + (sectionId + 1) + " of " + nmbrOfSections; 1152 | if (hasParameter("operation-comment")) 1153 | { 1154 | comment = comment + " : " + getParameter("operation-comment"); 1155 | } 1156 | writeComment(comment); 1157 | if (debugMode) 1158 | writeComment("retractHeight = " + round(retractHeight,3)); 1159 | 1160 | // Write the WCS, ie. G54 or higher.. default to WCS1 / G54 if no or invalid WCS 1161 | if (!isFirstSection() && (currentworkOffset != (53 + section.workOffset)) ) 1162 | { 1163 | writeZretract(); 1164 | } 1165 | 1166 | if ((section.workOffset < 1) || (section.workOffset > 6)) 1167 | { 1168 | var mcsmsg = "WARNING " + "Invalid Work Coordinate System. Select WCS 1..6 in SETUP:PostProcess tab. Selecting default WCS1/G54"; 1169 | warning(mcsmsg); 1170 | writeComment(mcsmsg); 1171 | //section.workOffset = 1; // If no WCS is set (or out of range), then default to WCS1 / G54 : swarfer: this appears to be readonly 1172 | writeBlock(gWCSOutput.format(54)); // output what we want, G54 1173 | currentworkOffset = 54; 1174 | } 1175 | else 1176 | { 1177 | writeBlock(gWCSOutput.format(53 + section.workOffset)); // use the selected WCS 1178 | currentworkOffset = 53 + section.workOffset; 1179 | } 1180 | writeBlock(gAbsIncModal.format(90)); // Set to absolute coordinates 1181 | 1182 | // If the machine has coolant, write M8/M7 or M9 on spindle control line 1183 | // if probing ensure coolant is off 1184 | if (properties.hasCoolant) 1185 | { 1186 | if (OB.isLaser || OB.isPlasma) 1187 | { 1188 | clnt = setCoolant(1); // always turn it on since plasma tool has no coolant option in fusion 1189 | writeComment('laser coolant ' + clnt); 1190 | } 1191 | else 1192 | clnt = setCoolant(tool.coolant); // use tool setting 1193 | } 1194 | 1195 | 1196 | OB.cutmode = -1; 1197 | //writeComment("isMilling=" + isMilling() + " isjet=" +isJet() + " islaser=" + isLaser); 1198 | switch (tool.type) 1199 | { 1200 | case TOOL_WATER_JET: 1201 | writeComment("Waterjet cutting with GRBL."); 1202 | OB.power = calcPower(100); // always 100% 1203 | OB.cutmode = 3; 1204 | OB.isLaser = false; 1205 | OB.isPlasma = true; 1206 | //writeBlock(mOutput.format(cutmode), sOutput.format(OB.power)); 1207 | break; 1208 | case TOOL_LASER_CUTTER: 1209 | //writeComment("Laser cutting with GRBL."); 1210 | OB.isLaser = true; 1211 | OB.isPlasma = false; 1212 | var pwas = OB.power; 1213 | switch (currentSection.jetMode) 1214 | { 1215 | case JET_MODE_THROUGH: 1216 | OB.power = calcPower(properties.PowerThrough); 1217 | writeComment("LASER THROUGH CUTTING " + properties.PowerThrough + "percent = S" + OB.power); 1218 | break; 1219 | case JET_MODE_ETCHING: 1220 | OB.power = calcPower(properties.PowerEtch); 1221 | writeComment("LASER ETCH CUTTING " + properties.PowerEtch + "percent = S" + OB.power); 1222 | break; 1223 | case JET_MODE_VAPORIZE: 1224 | OB.power = calcPower(properties.PowerVaporise); 1225 | writeComment("LASER VAPORIZE CUTTING " + properties.PowerVaporise + "percent = S" + OB.power); 1226 | break; 1227 | default: 1228 | error(localize("Unsupported cutting mode.")); 1229 | return; 1230 | } 1231 | // figure cutmode, M3 or M4 1232 | if ((OB.cuttingMode == 'etch') || (OB.cuttingMode == 'vaporize')) 1233 | OB.cutmode = 4; // always M4 mode unless cutting 1234 | else 1235 | OB.cutmode = 3; 1236 | if (pwas != OB.power) 1237 | { 1238 | sOutput.reset(); 1239 | //if (isFirstSection()) 1240 | if (OB.cutmode == 3) 1241 | writeBlock(mOutput.format(OB.cutmode), sOutput.format(0), '; flash preventer'); // else you get a flash before the first g0 move 1242 | else 1243 | if (OB.cuttingMode != 'cut') 1244 | writeBlock(mOutput.format(OB.cutmode), sOutput.format(OB.power), clnt, '; section power'); 1245 | } 1246 | break; 1247 | case TOOL_PLASMA_CUTTER: 1248 | writeComment("Plasma cutting with GRBL."); 1249 | if (properties.plasma_usetouchoff) 1250 | writeComment("Using torch height probe and pierce delay."); 1251 | OB.power = calcPower(100); // always 100% 1252 | OB.cutmode = 3; 1253 | OB.isLaser = false; 1254 | OB.isPlasma = true; 1255 | //writeBlock(mOutput.format(cutmode), sOutput.format(OB.power)); 1256 | break; 1257 | case TOOL_PROBE: 1258 | amProbing = true; 1259 | writeComment('Tool is a 3D Probe'); 1260 | clnt = setCoolant(0); 1261 | writeBlock(clnt); 1262 | clnt = ''; 1263 | break; 1264 | default: 1265 | //writeComment("tool.type = " + tool.type); // all milling tools 1266 | OB.isPlasma = OB.isLaser = false; 1267 | break; 1268 | } 1269 | 1270 | if ( !OB.isLaser && !OB.isPlasma ) 1271 | { 1272 | // To be safe (after jogging to whatever position), move the spindle up to a safe home position before going to the initial position 1273 | // At end of a section, spindle is retracted to clearance height, so it is only needed on the first section 1274 | // it is done with G53 - machine coordinates, so I put it in front of anything else 1275 | if (isFirstSection()) 1276 | { 1277 | writeZretract(); 1278 | } 1279 | else 1280 | if (properties.generateMultiple && (tool.number != getPreviousSection().getTool().number)) 1281 | writeZretract(); 1282 | // to enable tool code output, uncomment the toolformat line and 1 (one) of the writeblock lines according to your needs 1283 | //var toolFormat = createFormat({ decimals: 0 }); 1284 | //writeBlock("T" + toolFormat.format(tool.number), mOutput.format(6)); 1285 | //writeBlock("T" + toolFormat.format(tool.number)); 1286 | gotoInitial(true); 1287 | 1288 | // folks might want coolant control here 1289 | // Insert the Spindle start command 1290 | if (clnt) 1291 | { 1292 | // force S and M words if coolant command exists 1293 | sOutput.reset(); 1294 | mOutput.reset(); 1295 | } 1296 | if (amProbing) 1297 | { 1298 | m = mOutput.format(5); // stop the spindle 1299 | writeBlock(m); 1300 | m = ''; // prevent spindle delay 1301 | } 1302 | else 1303 | if (tool.clockwise) 1304 | { 1305 | s = sOutput.format(tool.spindleRPM); 1306 | var rpmChanged = false; 1307 | if (s) 1308 | { 1309 | rpmChanged = !mFormat.areDifferent(3, mOutput.getCurrent() ); 1310 | mOutput.reset(); // always output M3 if speed changes - helps with resume 1311 | } 1312 | m = mOutput.format(3); 1313 | writeBlock(m, s, clnt); 1314 | if (rpmChanged) // means a speed change, spindle was already on, delay half the time 1315 | onDwell(properties.spindleOnOffDelay / 2); 1316 | } 1317 | else 1318 | if (properties.spindleTwoDirections) 1319 | { 1320 | s = sOutput.format(tool.spindleRPM); 1321 | m = mOutput.format(4); 1322 | writeBlock(s, m, clnt); 1323 | } 1324 | else 1325 | { 1326 | warning("ERROR - Counter-clockwise Spindle Operation found, but your spindle does not support this"); 1327 | error("Fatal Error in Operation " + (sectionId + 1) + ": Counter-clockwise Spindle Operation found, but your spindle does not support this"); 1328 | return; 1329 | } 1330 | // spindle on delay if needed 1331 | if (m && (isFirstSection() || isNewfile)) 1332 | onDwell(properties.spindleOnOffDelay); 1333 | } 1334 | else 1335 | { 1336 | // laser or plasma 1337 | if (properties.UseZ) 1338 | if (isFirstSection() || (properties.generateMultiple && (tool.number != getPreviousSection().getTool().number)) ) 1339 | { 1340 | writeZretract(); 1341 | gotoInitial(false); 1342 | } 1343 | } 1344 | 1345 | forceXYZ(); 1346 | 1347 | var remaining = currentSection.workPlane; 1348 | if (!isSameDirection(remaining.forward, new Vector(0, 0, 1))) 1349 | { 1350 | warning("ERROR : Tool-Rotation detected - this GRBL post only supports 3 Axis"); 1351 | error("Fatal Error in Operation " + (sectionId + 1) + ": Tool-Rotation detected but GRBL only supports 3 Axis"); 1352 | } 1353 | setRotation(remaining); 1354 | 1355 | forceAny(); 1356 | 1357 | if (OB.isLaser && properties.UseZ) 1358 | writeBlock(gMotionModal.format(0), zOutput.format(0)); 1359 | isNewfile = false; 1360 | //writeComment("onSection end"); 1361 | } 1362 | 1363 | function onDwell(seconds) 1364 | { 1365 | if ((seconds < 0.0) || (seconds > 999)) 1366 | seconds = 3.14; 1367 | writeBlock(gFormat.format(4), "P" + secFormat.format(seconds)); 1368 | } 1369 | 1370 | function onSpindleSpeed(spindleSpeed) 1371 | { 1372 | writeBlock(sOutput.format(spindleSpeed)); 1373 | gMotionModal.reset(); // force a G word after a spindle speed change to keep CONTROL happy 1374 | } 1375 | 1376 | function onRadiusCompensation() 1377 | { 1378 | var radComp = getRadiusCompensation(); 1379 | var sectionId = getCurrentSectionId(); 1380 | if (radComp != RADIUS_COMPENSATION_OFF) 1381 | { 1382 | warning("ERROR : RadiusCompensation is not supported in GRBL - Change RadiusCompensation in CAD/CAM software to Off/Center/Computer"); 1383 | error("Fatal Error in Operation " + (sectionId + 1) + ": RadiusCompensation is found in CAD file but is not supported in GRBL"); 1384 | return; 1385 | } 1386 | } 1387 | 1388 | function onRapid(_x, _y, _z) 1389 | { 1390 | OB.haveRapid = true; 1391 | if (debugMode) writeComment("onRapid"); 1392 | if (!OB.isLaser && !OB.isPlasma) 1393 | { 1394 | var x = xOutput.format(_x); 1395 | var y = yOutput.format(_y); 1396 | var z = zOutput.format(_z); 1397 | 1398 | if (x || y || z) 1399 | { 1400 | writeBlock(gMotionModal.format(0), x, y, z); 1401 | feedOutput.reset(); 1402 | } 1403 | } 1404 | else 1405 | { 1406 | var x = xOutput.format(_x); 1407 | var y = yOutput.format(_y); 1408 | var z = ""; 1409 | if (OB.isPlasma && properties.UseZ) // laser does not move Z during cuts 1410 | { 1411 | z = (_z < plasma.pierceHeight) ? zOutput.format(plasma.pierceHeight) : zOutput.format(_z) ; 1412 | //writeComment("1408 z = " + z); 1413 | //z = zOutput.format(_z); 1414 | } 1415 | // if (OB.isPlasma && properties.UseZ && (xyzFormat.format(_z) == xyzFormat.format(plasma.pierceHeight)) ) 1416 | // { 1417 | // if (debugMode) writeComment("onRapid skipping Z motion"); 1418 | // if (x || y) 1419 | // writeBlock(gMotionModal.format(0), x, y); 1420 | // zOutput.reset(); // force it on next command 1421 | // } 1422 | // else 1423 | if (x || y || z) 1424 | writeBlock(gMotionModal.format(0), x, y, z); 1425 | } 1426 | } 1427 | 1428 | function onLinear(_x, _y, _z, feed) 1429 | { 1430 | //if (debugMode) writeComment("onLinear " + OB.haveRapid); 1431 | if (OB.powerOn || OB.haveRapid) // do not reset if power is off - for laser G0 moves 1432 | { 1433 | xOutput.reset(); 1434 | yOutput.reset(); // always output x and y else arcs go mad 1435 | } 1436 | var x = xOutput.format(_x); 1437 | var y = yOutput.format(_y); 1438 | var f = feedOutput.format(feed); 1439 | if (!OB.isLaser && !OB.isPlasma) 1440 | { 1441 | var z = zOutput.format(_z); 1442 | 1443 | if (x || y || z) 1444 | { 1445 | linmove = 1; // have to have a default! 1446 | if (!OB.haveRapid && z) // if z is changing 1447 | { 1448 | if (_z < retractHeight) // compare it to retractHeight, below that is G1, >= is G0 1449 | linmove = 1; 1450 | else 1451 | linmove = 0; 1452 | if (debugMode && (linmove == 0)) writeComment("NOrapid"); 1453 | } 1454 | writeBlock(gMotionModal.format(linmove), x, y, z, f); 1455 | } 1456 | else 1457 | if (f) 1458 | { 1459 | if (getNextRecord().isMotion()) 1460 | { 1461 | feedOutput.reset(); // force feed on next line 1462 | } 1463 | else 1464 | { 1465 | writeBlock(gMotionModal.format(1), f); 1466 | } 1467 | } 1468 | } 1469 | else 1470 | { 1471 | // laser, plasma 1472 | if (x || y) 1473 | { 1474 | // never going to cut lower than cutheight 1475 | var z = properties.UseZ ? (_z <= plasma.pierceHeight) ? zOutput.format(OB.powerOn ? plasma.cutHeight : plasma.pierceHeight) : zOutput.format(_z) : ""; 1476 | //if (OB.isLaser && !properties.UsePierce) 1477 | // z = 'z0'; 1478 | if (debugMode && z != "") writeComment("onlinear z = " + z); 1479 | var s = sOutput.format(OB.power); 1480 | if (OB.haveRapid) 1481 | { 1482 | // this is the old process when we have rapids inserted by onRapid 1483 | if (!OB.powerOn) // laser/plasma does some odd routing that should be rapid 1484 | { 1485 | writeBlock(gMotionModal.format(0), x, y, z, s); 1486 | feedOutput.reset(); 1487 | } 1488 | else 1489 | writeBlock(gMotionModal.format(1), x, y, z, f, s); 1490 | } 1491 | else 1492 | { 1493 | // this is the new process when we dont have onRapid but GRBL requires G0 moves for noncutting laser moves 1494 | if (OB.powerOn) 1495 | writeBlock(gMotionModal.format(1), x, y, z, f, s); 1496 | else 1497 | { 1498 | writeBlock(gMotionModal.format(0), x, y, z, s); 1499 | feedOutput.reset(); 1500 | } 1501 | } 1502 | 1503 | } 1504 | } 1505 | if (SPL.linecnt > SPL.tapelines) 1506 | { 1507 | if (debugMode) writeComment('Tapelines ' + SPL.tapelines); 1508 | SPL.linecnt = 0; 1509 | splitHere(_x,_y,_z,feed); 1510 | } 1511 | } 1512 | 1513 | function onRapid5D(_x, _y, _z, _a, _b, _c) 1514 | { 1515 | warning("ERROR : Tool-Rotation detected - this GRBL post only supports 3 Axis"); 1516 | error("Tool-Rotation detected but this GRBL post only supports 3 Axis"); 1517 | } 1518 | 1519 | function onLinear5D(_x, _y, _z, _a, _b, _c, feed) 1520 | { 1521 | warning("ERROR : Tool-Rotation detected - this GRBL post only supports 3 Axis"); 1522 | error("Tool-Rotation detected but this GRBL post only supports 3 Axis"); 1523 | } 1524 | 1525 | // this code was generated with the help of ChatGPT AI 1526 | // calculate the centers for the 2 circles passing through both points at the given radius 1527 | // if you ask chatgpt that ^^^ you will get incorrect code! 1528 | // if error then returns -9.9375 for all coordinates 1529 | // define points as var point1 = { x: 0, y: 0 }; 1530 | // returns an array of 2 of those things comprising the 2 centers 1531 | function calculateCircleCenters(point1, point2, radius) 1532 | { 1533 | // Calculate the distance between the points 1534 | var distance = Math.sqrt( Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2) ); 1535 | if (distance > (radius * 2)) 1536 | { 1537 | //-9.9375 is perfectly stored by doubles and singles and will pass an equality test 1538 | center1X = center1Y = center2X = center2Y = -9.9375; 1539 | } 1540 | else 1541 | { 1542 | // Calculate the midpoint between the points 1543 | var midpointX = (point1.x + point2.x) / 2; 1544 | var midpointY = (point1.y + point2.y) / 2; 1545 | 1546 | // Calculate the angle between the line connecting the points and the x-axis 1547 | var angle = Math.atan2(point2.y - point1.y, point2.x - point1.x); 1548 | 1549 | // Calculate the distance from the midpoint to the center of each circle 1550 | var halfChordLength = Math.sqrt(Math.pow(radius, 2) - Math.pow(distance / 2, 2)); 1551 | 1552 | // Calculate the centers of the circles 1553 | var center1X = midpointX + halfChordLength * Math.cos(angle + Math.PI / 2); 1554 | var center1Y = midpointY + halfChordLength * Math.sin(angle + Math.PI / 2); 1555 | 1556 | var center2X = midpointX + halfChordLength * Math.cos(angle - Math.PI / 2); 1557 | var center2Y = midpointY + halfChordLength * Math.sin(angle - Math.PI / 2); 1558 | } 1559 | 1560 | // Return the centers of the circles as an array of objects 1561 | return [ 1562 | { x: center1X, y: center1Y }, 1563 | { x: center2X, y: center2Y } ]; 1564 | } 1565 | 1566 | /** 1567 | * given the 2 points and existing center, find a new, more accurate center 1568 | * only works in x,y 1569 | * point parameters are Vectors, this converts them to arrays for the calc 1570 | * returns a Vector point with the revised center values in x,y, ignore Z 1571 | */ 1572 | function newCenter(p1, p2, oldcenter, radius) 1573 | { 1574 | // inputs are vectors, convert 1575 | var point1 = { x: p1.x, y: p1.y }; 1576 | var point2 = { x: p2.x, y: p2.y }; 1577 | 1578 | var newcenters = calculateCircleCenters(point1, point2, radius); 1579 | if ((newcenters[0].x == newcenters[1].x) && (newcenters[0].y == -9.9375)) 1580 | { 1581 | // error in calculation, distance between points > diameter 1582 | return oldcenter; 1583 | } 1584 | // now find the new center that is closest to the old center 1585 | //writeComment("nc1 " + newcenters[0].x + " " + newcenters[0].y); 1586 | nc1 = new Vector(newcenters[0].x, newcenters[0].y, 0); // note Z is not valid 1587 | //writeComment("nc2 " + newcenters[1].x + " " + newcenters[1].y); 1588 | nc2 = new Vector(newcenters[1].x, newcenters[1].y, 0); 1589 | d1 = Vector.diff(oldcenter, nc1).length; 1590 | d2 = Vector.diff(oldcenter, nc2).length; 1591 | // return the new center that is closest to the old center 1592 | if (d1 < d2) 1593 | return nc1; 1594 | else 1595 | return nc2; 1596 | } 1597 | 1598 | /* 1599 | helper for on Circular - calculates a new center for arcs with differing radii 1600 | returns the revised center vector 1601 | maps arcs to XY plane, recenters, and reversemaps to return the new center in the correct plane 1602 | */ 1603 | function ReCenter(start, end, center, radius, cp) 1604 | { 1605 | var r1,r2,diff,pdiff; 1606 | 1607 | switch (cp) 1608 | { 1609 | case PLANE_XY: 1610 | if (debugMode) writeComment('recenter XY'); 1611 | var nCenter = newCenter(start, end, center, radius ); 1612 | // writeComment("old center " + center.x + " , " + center.y); 1613 | // writeComment("new center " + nCenter.x + " , " + nCenter.y); 1614 | center.x = nCenter.x; 1615 | center.y = nCenter.y; 1616 | center.z = (start.z + end.z) / 2.0; 1617 | 1618 | r1 = Vector.diff(start, center).length; 1619 | r2 = Vector.diff(end, center).length; 1620 | if (r1 != r2) 1621 | { 1622 | diff = r1 - r2; 1623 | pdiff = Math.abs(diff / r1 * 100); 1624 | if (pdiff > 0.01) 1625 | { 1626 | if (debugMode) writeComment("R1 " + r1 + " r2 " + r2 + " d " + (r1 - r2) + " pdoff " + pdiff ); 1627 | } 1628 | } 1629 | break; 1630 | case PLANE_ZX: 1631 | if (debugMode) writeComment('recenter ZX'); 1632 | // generate fake x,y vectors 1633 | var st = new Vector( start.x, start.z, 0); 1634 | var ed = new Vector(end.x, end.z, 0) 1635 | var ct = new Vector(center.x, center.z, 0); 1636 | var nCenter = newCenter( st, ed, ct, radius); 1637 | // translate fake x,y values 1638 | center.x = nCenter.x; 1639 | center.z = nCenter.y; 1640 | r1 = Vector.diff(start, center).length; 1641 | r2 = Vector.diff(end, center).length; 1642 | if (r1 != r2) 1643 | { 1644 | diff = r1 - r2; 1645 | pdiff = Math.abs(diff / r1 * 100); 1646 | if (pdiff > 0.01) 1647 | { 1648 | if (debugMode) writeComment("ZX R1 " + r1 + " r2 " + r2 + " d " + (r1 - r2) + " pdoff " + pdiff ); 1649 | } 1650 | } 1651 | break; 1652 | case PLANE_YZ: 1653 | if (debugMode) writeComment('recenter YZ'); 1654 | var st = new Vector(start.z, start.y, 0); 1655 | var ed = new Vector(end.z, end.y, 0) 1656 | var ct = new Vector(center.z, center.y, 0); 1657 | var nCenter = newCenter(st, ed, ct, radius); 1658 | center.y = nCenter.y; 1659 | center.z = nCenter.x; 1660 | r1 = Vector.diff(start, center).length; 1661 | r2 = Vector.diff(end, center).length; 1662 | if (r1 != r2) 1663 | { 1664 | diff = r1 - r2; 1665 | pdiff = Math.abs(diff / r1 * 100); 1666 | if (pdiff > 0.01) 1667 | { 1668 | if (debugMode) writeComment("YZ R1 " + r1 + " r2 " + r2 + " d " + (r1 - r2) + " pdoff " + pdiff ); 1669 | } 1670 | } 1671 | break; 1672 | } 1673 | return center; 1674 | } 1675 | 1676 | function onCircular(clockwise, cx, cy, cz, x, y, z, feed) 1677 | { 1678 | var start = getCurrentPosition(); 1679 | var center = new Vector(cx, cy, cz); 1680 | var end = new Vector(x, y, z); 1681 | var cp = getCircularPlane(); 1682 | //writeComment("cp " + cp); 1683 | 1684 | if (isFullCircle()) 1685 | { 1686 | writeComment("full circle"); 1687 | linearize(tolerance); 1688 | return; 1689 | } 1690 | 1691 | // first fix the center 'height' 1692 | // for an XY plane, fix Z to be between start.z and end.z 1693 | switch (cp) 1694 | { 1695 | case PLANE_XY: 1696 | center.z = (start.z + end.z) / 2.0; // doing this fixes most arc radius lengths 1697 | break; // because the radius depends on the axial distance as well 1698 | case PLANE_YZ: 1699 | // fix X 1700 | center.x = (start.x + end.x) / 2.0; 1701 | break; 1702 | case PLANE_ZX: 1703 | // fix Y 1704 | center.y = (start.y + end.y) / 2.0; 1705 | break; 1706 | default: 1707 | writeComment("no plane"); 1708 | } 1709 | // check for differing radii 1710 | var r1 = Vector.diff(start, center).length; 1711 | var r2 = Vector.diff(end, center).length; 1712 | if ( (r1 != r2) && (r1 < toolRadius) ) // always recenter small arcs 1713 | { 1714 | var diff = r1 - r2; 1715 | var pdiff = Math.abs(diff / r1 * 100); 1716 | // if percentage difference too great 1717 | if (pdiff > 0.01) 1718 | { 1719 | if (debugMode) writeComment("recenter"); 1720 | // adjust center to make radii equal 1721 | if (debugMode) writeComment("r1 " + r1 + " r2 " + r2 + " d " + (r1 - r2) + " pdiff " + pdiff ); 1722 | center = ReCenter(start, end, center, (r1 + r2) /2, cp); 1723 | } 1724 | } 1725 | 1726 | // arcs smaller than bitradius always have significant radius errors, 1727 | // so get radius and linearize them (because we cannot change minimumCircularRadius here) 1728 | // note that larger arcs still have radius errors, but they are a much smaller percentage of the radius 1729 | // and GRBL won't care 1730 | var rad = Vector.diff(start,center).length; // radius to NEW Center if it has been calculated 1731 | if ( (rad < toPreciseUnit(2, MM)) || OB.isPlasma) // only for small arcs, dont need to linearize a 24mm arc on a 50mm tool 1732 | if (properties.linearizeSmallArcs && (rad < toolRadius)) 1733 | { 1734 | var tt = OB.powerOn ? tolerance : tolerance * 20; 1735 | if (debugMode) writeComment("linearizing arc radius " + round(rad, 4) + " toolRadius " + round(toolRadius, 3) + " tolerance " + tt); 1736 | linearize(tt); 1737 | if (debugMode) writeComment("done"); 1738 | return; 1739 | } 1740 | // not small and not a full circle, output G2 or G3 1741 | if ((OB.isLaser || OB.isPlasma) && !OB.powerOn) 1742 | { 1743 | if (debugMode) writeComment("arc linearize rapid"); 1744 | linearize(tolerance * 20); // this is a rapid move so tolerance can be increased for faster motion and fewer lines of code 1745 | if (debugMode) writeComment("arc linearize rapid done"); 1746 | } 1747 | else 1748 | switch (getCircularPlane()) 1749 | { 1750 | case PLANE_XY: 1751 | xOutput.reset(); // must always have X and Y 1752 | yOutput.reset(); 1753 | // dont need to do ioutput and joutput because they are reference variables 1754 | if (!OB.isLaser && !OB.isPlasma) 1755 | writeBlock(gPlaneModal.format(17), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), iOutput.format(center.x - start.x, 0), jOutput.format(center.y - start.y, 0), feedOutput.format(feed)); 1756 | else 1757 | { 1758 | //zo = properties.UseZ ? zOutput.format(z) : ""; 1759 | zo = properties.UseZ ? (z < plasma.cutHeight) ? zOutput.format(plasma.cutHeight) : zOutput.format(z) : ""; 1760 | writeBlock(gPlaneModal.format(17), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zo, iOutput.format(center.x - start.x, 0), jOutput.format(center.y - start.y, 0), feedOutput.format(feed)); 1761 | } 1762 | break; 1763 | case PLANE_ZX: 1764 | if (!OB.isLaser && !OB.isPlasma) 1765 | { 1766 | xOutput.reset(); // always have X and Z 1767 | zOutput.reset(); 1768 | writeBlock(gPlaneModal.format(18), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), iOutput.format(center.x - start.x, 0), kOutput.format(center.z - start.z, 0), feedOutput.format(feed)); 1769 | } 1770 | else 1771 | linearize(tolerance); 1772 | break; 1773 | case PLANE_YZ: 1774 | if (!OB.isLaser && !OB.isPlasma) 1775 | { 1776 | yOutput.reset(); // always have Y and Z 1777 | zOutput.reset(); 1778 | writeBlock(gPlaneModal.format(19), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), jOutput.format(center.y - start.y, 0), kOutput.format(center.z - start.z, 0), feedOutput.format(feed)); 1779 | } 1780 | else 1781 | linearize(tolerance); 1782 | break; 1783 | default: 1784 | linearize(tolerance); 1785 | } //switch plane 1786 | } 1787 | 1788 | /** 1789 | * force a file split here 1790 | * params are the current cut position and feedrate 1791 | * TODO - set a flag and split at the next rapid move instead of instant split 1792 | */ 1793 | function splitHere(_x,_y,_z,_f) 1794 | { 1795 | // output footer 1796 | if (debugMode) writeComment('splitHere: Splitting file'); 1797 | //onClose(); 1798 | // open new file 1799 | SPL.forceSplit = true; 1800 | // write header 1801 | onSection(); 1802 | // goto x,y 1803 | writeComment("Resume previous position"); 1804 | invokeOnRapid(_x,_y,retractHeight); 1805 | // goto z 1806 | var sectionId = getCurrentSectionId(); // what is the number of this operation (starts from 0) 1807 | var section = getSection(sectionId); // what is the section-object for this operation 1808 | var feed = section.getParameter("operation:tool_feedPlunge"); 1809 | writeComment("Resume previous cut depth"); 1810 | onLinear(_x,_y,_z,feed); // feed back to previous cut level at plunge rate 1811 | } 1812 | 1813 | function onSectionEnd() 1814 | { 1815 | writeln(""); 1816 | // writeBlock(gPlaneModal.format(17)); 1817 | if (isRedirecting()) 1818 | { 1819 | if ( (isLastSection() && isFirstSection() ) || 1820 | (!isLastSection() && properties.generateMultiple && (tool.number != getNextSection().getTool().number) || (isLastSection() && !isFirstSection())) 1821 | ) 1822 | { 1823 | writeln(""); 1824 | onClose(); 1825 | closeRedirection(); 1826 | } 1827 | } 1828 | //if (properties.hasCoolant) 1829 | // setCoolant(0); 1830 | forceAny(); 1831 | } 1832 | 1833 | function onClose() 1834 | { 1835 | writeBlock(gAbsIncModal.format(90)); // Set to absolute coordinates for the following moves 1836 | if (!OB.isLaser && !OB.isPlasma) 1837 | { 1838 | gMotionModal.reset(); // for ease of reading the code always output the G0 words 1839 | writeZretract(); 1840 | //writeBlock(gAbsIncModal.format(90), gFormat.format(53), gMotionModal.format(0), "Z" + xyzFormat.format(toPreciseUnit(properties.machineHomeZ, MM))); // Retract spindle to Machine Z Home 1841 | } 1842 | writeBlock(mFormat.format(5)); // Stop Spindle 1843 | if (properties.hasCoolant) 1844 | { 1845 | writeBlock( setCoolant(0) ); // Stop Coolant 1846 | } 1847 | //onDwell(properties.spindleOnOffDelay); // Wait for spindle to stop 1848 | gMotionModal.reset(); 1849 | if (!OB.isLaser && !OB.isPlasma) 1850 | { 1851 | if (properties.gotoMCSatend) // go to MCS home 1852 | { 1853 | writeBlock(gAbsIncModal.format(90), gFormat.format(53), gMotionModal.format(0), 1854 | "X" + xyzFormat.format(toPreciseUnit(properties.machineHomeX, MM)), 1855 | "Y" + xyzFormat.format(toPreciseUnit(properties.machineHomeY, MM))); 1856 | } 1857 | else // go to WCS home 1858 | { 1859 | writeBlock(gAbsIncModal.format(90), gMotionModal.format(0), 1860 | "X" + xyzFormat.format(toPreciseUnit(properties.machineHomeX, MM)), 1861 | "Y" + xyzFormat.format(toPreciseUnit(properties.machineHomeY, MM))); 1862 | } 1863 | } 1864 | else // laser 1865 | { 1866 | if (properties.UseZ) 1867 | { 1868 | if (OB.isLaser) 1869 | writeBlock( gAbsIncModal.format(90), gFormat.format(53), 1870 | gMotionModal.format(0), zOutput.format(toPreciseUnit(properties.machineHomeZ, MM)) ); 1871 | if (OB.isPlasma) 1872 | { 1873 | xOutput.reset(); 1874 | yOutput.reset(); 1875 | if (properties.gotoMCSatend) // go to MCS home 1876 | { 1877 | writeBlock( gAbsIncModal.format(90), gFormat.format(53), 1878 | gMotionModal.format(0), 1879 | zOutput.format(toPreciseUnit(properties.machineHomeZ, MM)) ); 1880 | writeBlock( gAbsIncModal.format(90), gFormat.format(53), 1881 | gMotionModal.format(0), 1882 | xOutput.format(toPreciseUnit(properties.machineHomeX, MM)), 1883 | yOutput.format(toPreciseUnit(properties.machineHomeY, MM)) ); 1884 | } 1885 | else 1886 | writeBlock(gMotionModal.format(0), xOutput.format(0), yOutput.format(0)); 1887 | } 1888 | } 1889 | } 1890 | writeBlock(mFormat.format(30)); // Program End 1891 | //writeln("%"); // EndOfFile marker 1892 | } 1893 | 1894 | function onTerminate() 1895 | { 1896 | // If we are generating multiple files, copy first file to add # of # 1897 | // Then remove first file and recreate with file list - sharmstr 1898 | var outputPath = getOutputPath(); 1899 | var programFilename = FileSystem.getFilename(outputPath); 1900 | if (filesToGenerate > 1) 1901 | { 1902 | 1903 | var outputFolder = FileSystem.getFolderPath(getOutputPath()); 1904 | // make sure file is closed 1905 | if (isRedirecting()) 1906 | closeRedirection(); 1907 | var newname = makeFileName(1); 1908 | FileSystem.copyFile(outputPath, newname); 1909 | FileSystem.remove(outputPath); 1910 | var file = new TextFile(outputFolder + "\\" + programFilename, true, "ansi"); 1911 | file.writeln("The following gcode files were Created: "); 1912 | var fname; 1913 | for (var i = 0; i < filesToGenerate; ++i) 1914 | { 1915 | fname = makeFileName(i + 1); 1916 | file.writeln(fname); 1917 | } 1918 | if (properties.splitLines > 0) 1919 | file.writeln("A total of " + filesToGenerate + " files were written."); 1920 | file.close(); 1921 | } 1922 | // from haas nextgen post, auto output a setup sheet 1923 | /* 1924 | this does not work as we cannot find the post in th epersonal post folder unless user tells us what it is 1925 | //var outputPath = getOutputPath(); 1926 | warning("outputpath " + outputPath); 1927 | 1928 | //var programFilename = FileSystem.getFilename(outputPath); 1929 | warning("programFilename " + programFilename); 1930 | 1931 | var programSize = FileSystem.getFileSize(outputPath); 1932 | warning("programSize " + programSize); 1933 | 1934 | var pfolder = getConfigurationPath(); // path to current post 1935 | warning('pfolder ' + pfolder); 1936 | 1937 | var postPath = findFile(".\\setup-sheet.cps"); 1938 | warning("postpath " + postPath); 1939 | 1940 | var intermediatePath = getIntermediatePath(); 1941 | debug("intermediatePath " + intermediatePath); 1942 | var a = "--property unit " + ((unit == IN) ? "0" : "1"); // use 0 for inch and 1 for mm 1943 | if (programName) 1944 | { 1945 | a += " --property programName \"'" + programName + "'\""; 1946 | } 1947 | if (programComment) 1948 | { 1949 | a += " --property programComment \"'" + programComment + "'\""; 1950 | } 1951 | a += " --property programFilename \"'" + programFilename + "'\""; 1952 | a += " --property programSize \"" + programSize + "\""; 1953 | a += " --noeditor --log temp.log \"" + postPath + "\" \"" + intermediatePath + "\" \"" + FileSystem.replaceExtension(outputPath, "html") + "\""; 1954 | debug(a); 1955 | */ 1956 | //execute(getPostProcessorPath(), a, false, ""); 1957 | //executeNoWait("start", "\"" + FileSystem.replaceExtension(outputPath, "html") + "\"", false, ""); 1958 | } 1959 | 1960 | function onCommand(command) 1961 | { 1962 | if (debugMode) writeComment("onCommand " + command); 1963 | switch (command) 1964 | { 1965 | case COMMAND_STOP: // - Program stop (M00) 1966 | writeComment("Program stop M00"); 1967 | writeBlock(mFormat.format(0)); 1968 | break; 1969 | case COMMAND_OPTIONAL_STOP: // - Optional program stop (M01) 1970 | writeComment("Optional program stop M01"); 1971 | writeBlock(mFormat.format(1)); 1972 | break; 1973 | case COMMAND_END: // - Program end (M02) 1974 | writeComment("Program end M02"); 1975 | writeBlock(mFormat.format(2)); 1976 | break; 1977 | case COMMAND_POWER_OFF: 1978 | if (debugMode) writeComment("power off"); 1979 | if (!OB.haveRapid) 1980 | writeln(""); 1981 | OB.powerOn = false; 1982 | if (OB.isPlasma || (OB.isLaser && (OB.cuttingMode == 'cut')) ) 1983 | { 1984 | writeBlock(mFormat.format(5)); 1985 | if (properties.plasma_postcutdelay > 0) 1986 | onDwell(properties.plasma_postcutdelay); 1987 | } 1988 | break; 1989 | case COMMAND_POWER_ON: 1990 | if (debugMode) writeComment("power ON"); 1991 | if (!OB.haveRapid) 1992 | writeln(""); 1993 | OB.powerOn = true; 1994 | if (OB.isPlasma || OB.isLaser) 1995 | { 1996 | if (properties.UseZ) 1997 | { 1998 | if (properties.plasma_usetouchoff && OB.isPlasma) 1999 | { 2000 | writeln(""); 2001 | writeBlock( "G38.2", zOutput.format(toPreciseUnit(-plasma_probedistance, MM)), feedOutput.format(toPreciseUnit(plasma_proberate, MM))); 2002 | if (debugMode) writeComment("touch offset " + xyzFormat.format(properties.plasma_touchoffOffset) ); 2003 | writeBlock( gMotionModal.format(10), "L20 P0", zOutput.format(toPreciseUnit(-parseFloat(properties.plasma_touchoffOffset), MM)) ); 2004 | feedOutput.reset(); 2005 | // force a G0 to existing position after the probe because this appears to avoid a GRBL bug in small arcs when arcing 2006 | // from an existing position after probing. 2007 | xOutput.reset(); 2008 | yOutput.reset(); 2009 | var cpos = getCurrentPosition(); 2010 | // after probing grbl appears to have forgotten the current position so we need to reset it else following moves are weird, so force X and Y 2011 | //writeComment(plasma.pierceHeight); 2012 | writeBlock(gMotionModal.format(0), xOutput.format(cpos.x), yOutput.format(cpos.y), zOutput.format(plasma.pierceHeight), " ; force position after probe and move to pierceheight"); 2013 | } 2014 | else 2015 | // move to pierce height 2016 | var _zz = zOutput.format(plasma.pierceHeight); 2017 | var _msg = ''; 2018 | if (debugMode) 2019 | _msg = " ; pierce height"; 2020 | if (_zz) 2021 | writeBlock( gMotionModal.format(0), _zz, _msg ); 2022 | } 2023 | if (OB.isPlasma || (OB.cuttingMode == 'cut') || (clnt)) 2024 | writeBlock(mFormat.format(3), sOutput.format(OB.power), clnt); 2025 | } 2026 | break; 2027 | default: 2028 | if (debugMode) writeComment("onCommand not handled " + command); 2029 | } 2030 | // for other commands see https://cam.autodesk.com/posts/reference/classPostProcessor.html#af3a71236d7fe350fd33bdc14b0c7a4c6 2031 | if (debugMode) writeComment("onCommand end"); 2032 | } 2033 | 2034 | function onParameter(name, value) 2035 | { 2036 | //onParameter('operation:keepToolDown', 0) 2037 | //if (debugMode) writeComment("onParameter =" + name + "= " + value); // (onParameter =operation:retractHeight value= :5) 2038 | name = name.replace(" ", "_"); // dratted indexOF cannot have spaces in it! 2039 | if ( (name.indexOf("retractHeight_value") >= 0 ) ) // == "operation:retractHeight value") 2040 | { 2041 | retractHeight = value; 2042 | if (debugMode) writeComment("onparameter - retractHeight = " + round(retractHeight,3)); 2043 | } 2044 | if (name.indexOf("operation:clearanceHeight_value") >= 0) 2045 | { 2046 | clearanceHeight = value; 2047 | if (debugMode) writeComment("onparameter - clearanceHeight = " + round(clearanceHeight,3)); 2048 | } 2049 | 2050 | if (name.indexOf("movement:lead_in") != -1) 2051 | { 2052 | // value is always mm so convert if needed 2053 | //if (unit == IN) 2054 | plasma.leadinRate = toPreciseUnit(value,MM); 2055 | //else 2056 | // plasma.leadinRate = value; 2057 | if (debugMode && OB.isPlasma) writeComment("onparameter - leadinRate set " + round(plasma.leadinRate,1) + " unit " + unit); 2058 | } 2059 | 2060 | if (name.indexOf("operation:topHeight_value") >= 0) 2061 | { 2062 | topHeight = value; 2063 | if (debugMode && OB.isPlasma) writeComment("onparameter - topHeight set " + topHeight); 2064 | } 2065 | if (name.indexOf('operation:cuttingMode') >= 0) 2066 | { 2067 | OB.cuttingMode = value; 2068 | if (debugMode) writeComment("onparameter - cuttingMode set " + OB.cuttingMode); 2069 | if (OB.cuttingMode.indexOf('cut') >= 0) // simplify later logic, auto/low/medium/high are all 'cut' 2070 | OB.cuttingMode = 'cut'; 2071 | if (OB.cuttingMode.indexOf('auto') >= 0) 2072 | OB.cuttingMode = 'cut'; 2073 | } 2074 | 2075 | if (name.indexOf("operation:tool_cutHeight") >= 0) 2076 | { 2077 | // todo decide which of topheight or cutHeight to use for plasma - which one is set first? topHeight 2078 | //if (value == 0) cannot be 0, tool edit wont let you 2079 | var msg = ''; 2080 | if (topHeight != 0) 2081 | { 2082 | plasma.cutHeight = topHeight; 2083 | msg = ' = topHeight'; 2084 | plasma.mode = 1; 2085 | } 2086 | else 2087 | { 2088 | if (OB.isPlasma) 2089 | { 2090 | plasma.cutHeight = value; 2091 | msg = ' = tool.cutHeight'; 2092 | } 2093 | else 2094 | { 2095 | plasma.cutHeight = 0; // laser cut height is always 0 2096 | msg = ' =0_for_laser'; 2097 | } 2098 | plasma.mode = 2; 2099 | } 2100 | if (debugMode) writeComment("onparameter - cutHeight set " + plasma.cutHeight + msg); 2101 | } 2102 | if (name.indexOf("operation:tool_pierceTime") >= 0) 2103 | { 2104 | msg = ' = tool_pierceTime'; 2105 | if (properties.spindleOnOffDelay > 0) 2106 | { 2107 | plasma.pierceTime = properties.spindleOnOffDelay; 2108 | msg = ' = spindleonoffdelay'; 2109 | } 2110 | else 2111 | plasma.pierceTime = value; 2112 | if (debugMode) writeComment("onparameter - pierceTime set " + plasma.pierceTime + msg); 2113 | } 2114 | 2115 | // (onParameter =operation:pierceClearance= 1.5) for plasma 2116 | // if (name == 'operation:pierceClearance') 2117 | // { 2118 | // if (properties.plasma_pierceHeightoverride) 2119 | // plasma_pierceHeight = properties.plasma_pierceHeightValue; 2120 | // else 2121 | // { 2122 | // var sectionId = getCurrentSectionId(); // what is the number of this operation (starts from 0) 2123 | // if (sectionId > -1) 2124 | // { 2125 | // writeComment("sectionid " + sectionId); 2126 | // var section = getSection(sectionId); // what is the section-object for this operation 2127 | // var tool = section.getTool(); // get the tool 2128 | // plasma_pierceHeight = tool.pierceHeight; // NOT pierceClearance! 2129 | // writeComment('onparameter pierceHeight ' + plasma_pierceHeight ); 2130 | // } 2131 | // } 2132 | // } 2133 | if ((name == 'action') && (value == 'pierce')) 2134 | { 2135 | if (OB.isLaser && !properties.UsePierce) 2136 | return; 2137 | if (debugMode) writeComment('action pierce'); 2138 | onDwell(plasma.pierceTime); 2139 | if (properties.UseZ) // done a probe and/or pierce, now lower to cut height 2140 | { 2141 | if (OB.isPlasma) 2142 | zOutput.reset(); 2143 | var _zz = zOutput.format(plasma.cutHeight); 2144 | if (_zz) 2145 | { 2146 | if (debugMode) writeComment('lower to cutheight'); 2147 | writeBlock( gMotionModal.format(1), _zz, feedOutput.format(plasma.leadinRate) ); 2148 | gMotionModal.reset(); 2149 | } 2150 | } 2151 | if (debugMode) writeComment('action pierce done'); 2152 | } 2153 | if (name == 'operation:tool_feedProbeLink') 2154 | { 2155 | PRB.feedProbeLink = value; 2156 | if (debugMode) writeComment("onparameter - feedProbeLink set " + PRB.feedProbeLink); 2157 | } 2158 | if (name == 'operation:tool_feedProbeMeasure') 2159 | { 2160 | PRB.feedProbeMeasure = value; 2161 | if (debugMode) writeComment("onparameter - feedProbeMeasure set " + PRB.feedProbeMeasure); 2162 | } 2163 | if (name == 'operation:probeWorkOffset') 2164 | { 2165 | //writeComment('override wcs ' + value) ; 2166 | if (value > 0) 2167 | warning("WARNING " + 'You set a probe *Overide Driving WCS* but I dont know how to do that yet'); 2168 | } 2169 | if (name == 'probe-output-work-offset') 2170 | { 2171 | PRB.probe_output_work_offset = value; 2172 | if (debugMode) writeComment("onparameter - probe_output_work_offset set " + PRB.probe_output_work_offset); 2173 | } 2174 | } 2175 | 2176 | function round(num, digits) 2177 | { 2178 | return toFixedNumber(num, digits, 10) 2179 | } 2180 | 2181 | function toFixedNumber(num, digits, base) 2182 | { 2183 | var pow = Math.pow(base || 10, digits); // cleverness found on web 2184 | return Math.round(num * pow) / pow; 2185 | } 2186 | 2187 | // set the coolant mode from the tool value 2188 | // changed 2023 - returns a string rather than writing the block itself 2189 | function setCoolant(coolval) 2190 | { 2191 | var cresult = ''; 2192 | 2193 | if ( debugMode) writeComment("setCoolant " + coolval); 2194 | // 0 if off, 1 is flood, 2 is mist, 7 is both 2195 | switch (coolval) 2196 | { 2197 | case 0: 2198 | if (coolantIsOn != 0) 2199 | cresult = mFormat.format(9); // off 2200 | coolantIsOn = 0; 2201 | break; 2202 | case 1: 2203 | if (coolantIsOn == 2) 2204 | cresult = mFormat.format(9); // turn mist off 2205 | cresult = cresult + mFormat.format(8); // flood 2206 | coolantIsOn = 1; 2207 | break; 2208 | case 2: 2209 | //writeComment("Mist coolant on pin A3. special GRBL compile for this."); 2210 | if (coolantIsOn == 1) 2211 | cresult = mFormat.format(9); // turn flood off 2212 | cresult += ' ' + mFormat.format(7); // mist 2213 | coolantIsOn = 2; 2214 | break; 2215 | case 7: // flood and mist 2216 | cresult = mFormat.format(8) ; // flood 2217 | cresult += ' ' + mFormat.format(7); // mist 2218 | coolantIsOn = 7; 2219 | break; 2220 | default: 2221 | var cmsg = "WARNING " + "Coolant option not understood: " + coolval; 2222 | warning(cmsg); 2223 | writeComment(cmsg); 2224 | coolantIsOn = 0; 2225 | } 2226 | if ( debugMode) writeComment("setCoolant end " + cresult); 2227 | return cresult; 2228 | } 2229 | 2230 | /** 2231 | make a numbered filename 2232 | will adjust for splitlines setting 2233 | @param index the number of the file, from 1 2234 | */ 2235 | function makeFileName(index) 2236 | { 2237 | debug("makefilename " + index) 2238 | var fullname = getOutputPath(); 2239 | debug(" fullname " + fullname); 2240 | //fullname = fullname.replace(' ', '_'); // messes with spaces in paths! 2241 | var filenamePath; 2242 | if (properties.splitLines > 0 ) 2243 | // since we don't know the final file count, dont say the wrong thing 2244 | filenamePath = FileSystem.replaceExtension(fullname, fileIndexFormat.format(index) + "ofMany" + "." + extension); 2245 | else 2246 | filenamePath = FileSystem.replaceExtension(fullname, fileIndexFormat.format(index) + "of" + filesToGenerate + "." + extension); 2247 | var filename = FileSystem.getFilename(filenamePath); 2248 | debug(" filename " + filename); 2249 | return filenamePath; 2250 | } 2251 | 2252 | function onCycle() 2253 | { 2254 | if (debugMode) writeComment('onCycle') ; 2255 | writeBlock(gPlaneModal.format(17)); 2256 | } 2257 | 2258 | function onCycleEnd() 2259 | { 2260 | if (debugMode) writeComment('onCycleEnd'); 2261 | if (isProbeOperation()) 2262 | { 2263 | zOutput.reset(); 2264 | gMotionModal.reset(); 2265 | //writeZretract(); 2266 | } 2267 | } 2268 | 2269 | // probe X from left or right 2270 | function probeX(x,y,z) 2271 | { 2272 | var dir = 0; 2273 | 2274 | writeComment('probeX : ' + x + " " + y + " " + z); 2275 | switch(cycle.approach1) 2276 | { 2277 | case "positive": // probing +Y toward stock 2278 | writeComment('probe X positive'); 2279 | dir = 1; 2280 | break; 2281 | case "negative": /// probing -y toward stock 2282 | writeComment('probe X negative'); 2283 | dir = -1; 2284 | break; 2285 | } 2286 | // current position half way along Y, -x/+x away from stock by probeClearance+tradius, Z=cycle.retract 2287 | var _z = zOutput.format(z); // probe retract height 2288 | writeBlock(gMotionModal.format(0), _z); 2289 | writeBlock(gAbsIncModal.format(91), " ; relative moves"); // all relative moves 2290 | // move Z down to cycle depth 2291 | _z = zOutput.format(-cycle.depth); 2292 | writeBlock(_z); 2293 | // probe probeClearance + overtravel in dir 2294 | var _x = xOutput.format( dir * (cycle.probeClearance + cycle.probeOvertravel) ); 2295 | var _f = feedOutput.format(cycle.feedrate); 2296 | writeBlock(gProbeModal.format(38.2), _x, _f, " ; probe fast"); 2297 | // retract a little 2298 | writeBlock(gMotionModal.format(0), xOutput.format(-dir * (cycle.probeOvertravel + toolRadius) ) ," ; retract"); 2299 | //reprobe slower 2300 | var _f = feedOutput.format(PRB.feedProbeMeasure); 2301 | writeBlock(gProbeModal.format(38.2), _x, _f, " ; probe slow"); 2302 | // setzero 2303 | _p = pWord.format(PRB.probe_output_work_offset); 2304 | writeBlock(gMotionModal.format(10), "L20", _p, xOutput.format(-dir * toolRadius)); 2305 | // move X away a bit, relative! 2306 | _x = xOutput.format(-dir * cycle.probeClearance); 2307 | writeBlock(gMotionModal.format(0), _x); 2308 | // G90 2309 | writeBlock(gAbsIncModal.format(90), " ; absolute moves"); 2310 | // retract Y and Z to cycleYZ 2311 | _z = zOutput.format(z); 2312 | writeBlock(gMotionModal.format(0), xOutput.format(x), _z); 2313 | writeComment('probeX finished'); 2314 | } 2315 | 2316 | function probeY(x,y,z) 2317 | { //move to Y-cycle.probeClearance feedrate(tool_feedProbeLink) 2318 | var dir = 0; 2319 | 2320 | writeComment('probeY : ' + x + " " + y + " " + z); 2321 | switch(cycle.approach1) 2322 | { 2323 | case "positive": // probing +Y toward stock 2324 | writeComment('probe Y positive'); 2325 | dir = 1; 2326 | break; 2327 | case "negative": /// probing -y toward stock 2328 | writeComment('probe Y negative'); 2329 | dir = -1; 2330 | break; 2331 | } 2332 | // current position half way along X, -y away from stock by probeClearance+radius, Z=cycle.retract 2333 | var _z = zOutput.format(z); // probre retract height 2334 | writeBlock(gMotionModal.format(0), _z); 2335 | writeBlock(gAbsIncModal.format(91)); // all relative moves 2336 | // move Z down to cycle depth 2337 | _z = zOutput.format(-cycle.depth); 2338 | writeBlock(_z); 2339 | // probe probeClearnace + overtravel in dir 2340 | var _y = yOutput.format( dir * (cycle.probeClearance + cycle.probeOvertravel) ); 2341 | var _f = feedOutput.format(cycle.feedrate); 2342 | writeBlock(gProbeModal.format(38.2), _y, _f, " ; probe fast"); 2343 | // retract a little 2344 | writeBlock(gMotionModal.format(0), yOutput.format(-dir * cycle.probeOvertravel) ," ; retract"); 2345 | //reprobe slower 2346 | var _f = feedOutput.format(PRB.feedProbeMeasure); 2347 | writeBlock(gProbeModal.format(38.2), _y, _f, " ; probe slow"); 2348 | // setzero 2349 | _p = pWord.format(PRB.probe_output_work_offset); 2350 | writeBlock(gMotionModal.format(10), "L20", _p, yOutput.format(-dir * toolRadius)); 2351 | // move Y away a bit, relative! 2352 | _y = yOutput.format(-dir * cycle.probeClearance); 2353 | writeBlock(gMotionModal.format(0), _y); 2354 | // G90 2355 | writeBlock(gAbsIncModal.format(90)); 2356 | // retract Y and Z to cycleYZ 2357 | _z = zOutput.format(z); 2358 | writeBlock(gMotionModal.format(0), yOutput.format(y), _z); 2359 | writeComment('probeY finished'); 2360 | } 2361 | 2362 | // probe Z - always negative? 2363 | function probeZ(x,y,z) 2364 | { 2365 | writeComment('probeZ: ' + x + " " + y + " " + z); 2366 | // we are at nominalZ + cycle.clearance, center of stock 2367 | // probe down by -(cycle.clearance + cycle.probeOverTravel) 2368 | writeBlock(gAbsIncModal.format(91)); // all relative moves 2369 | var _z = zOutput.format(-(cycle.clearance + cycle.probeOvertravel)); 2370 | var _f = feedOutput.format(cycle.feedrate); 2371 | // probe fast 2372 | writeBlock(gProbeModal.format(38.2), _z, _f, " ; probe fast"); 2373 | // retract 2374 | _z = zOutput.format(cycle.probeOvertravel); 2375 | writeBlock(gMotionModal.format(0) , _z); 2376 | // reprobe slow 2377 | _z = zOutput.format(-(cycle.clearance + cycle.probeOvertravel)); 2378 | _f = feedOutput.format(PRB.feedProbeMeasure); 2379 | writeBlock(gProbeModal.format(38.2), _z, _f, " ; probe slow"); 2380 | // set WCS 2381 | _p = pWord.format(PRB.probe_output_work_offset); 2382 | writeBlock(gMotionModal.format(10), "L20", _p, zOutput.format(0)); 2383 | // raise Z relative 2384 | _z = zOutput.format(cycle.retract); 2385 | writeBlock(gMotionModal.format(0) , _z); 2386 | writeBlock(gAbsIncModal.format(90)); // absolute 2387 | _z = zOutput.format(cycle.clearance); 2388 | writeBlock(gMotionModal.format(0) , _z); 2389 | writeComment('probe Z end'); 2390 | } 2391 | 2392 | /* 2393 | handle sprobe operations since there are many of them and only some can be supported on BlackBox 4X 2394 | Remember to expand unsupported cycles 2395 | */ 2396 | function onCyclePoint(x, y, z) 2397 | { 2398 | if (debugMode) writeComment('onCyclePoint: ' + x + " " + y + " " + z); 2399 | switch (cycleType) 2400 | { 2401 | case "probing-x": 2402 | writeComment('probing-x'); 2403 | probeX(x,y,z); 2404 | break; 2405 | case "probing-y": 2406 | writeComment('probing-y'); 2407 | probeY(x,y,z); 2408 | break; 2409 | case "probing-z": 2410 | writeComment('probing-z'); 2411 | probeZ(x,y,z); 2412 | break; 2413 | case "probing-xy-outer-corner": 2414 | writeComment("probing-xy-outer-corner start"); 2415 | // do this by using probex and probey 2416 | // we are at -clearance,-clearance,clearance 2417 | // position for X probe 2418 | writeBlock(gMotionModal.format(0), yOutput.format(cycle.probeClearance)); 2419 | probeX(x,cycle.probeClearance,z); 2420 | invokeOnRapid(x,y,z); 2421 | // position for Y probe 2422 | writeBlock(gMotionModal.format(0), xOutput.format(cycle.probeClearance)); 2423 | probeY(cycle.probeClearance,y,z); 2424 | invokeOnRapid(x,y,cycle.clearance); 2425 | writeComment("probing-xy-outer-corner complete"); 2426 | break; 2427 | case "probing-xy-circular-boss": 2428 | writeComment('probing-xy-circular-boss'); 2429 | warning(cycleType + ' not supported in this version'); 2430 | break; 2431 | case "probing-xy-circular-hole": 2432 | writeComment('probing-xy-circular-hole'); 2433 | warning(cycleType + ' not supported in this version'); 2434 | break; 2435 | case "probing-xy-circular-partial-boss": 2436 | writeComment('probing-xy-circular-partial-boss'); 2437 | warning(cycleType + ' not supported in this version'); 2438 | break; 2439 | case "probing-xy-circular-partial-hole": 2440 | writeComment('probing-xy-circular-partial-hole'); 2441 | warning(cycleType + ' not supported in this version'); 2442 | break; 2443 | case "probing-xy-circular-hole-with-island": 2444 | writeComment('probing-xy-circular-hole-with-island'); 2445 | warning(cycleType + ' not supported in this version'); 2446 | break; 2447 | case "probing-xy-circular-partial-hole-with-island": 2448 | writeComment('probing-xy-circular-partial-hole-with-island'); 2449 | warning(cycleType + ' not supported in this version'); 2450 | break; 2451 | case "probing-xy-rectangular-boss": 2452 | writeComment('probing-xy-rectangular-boss'); 2453 | warning(cycleType + ' not supported in this version'); 2454 | break; 2455 | case "probing-xy-rectangular-hole": 2456 | writeComment('probing-xy-rectangular-hole'); 2457 | warning(cycleType + ' not supported in this version'); 2458 | break; 2459 | case "probing-xy-rectangular-hole-with-island": 2460 | writeComment('probing-xy-rectangular-hole-with-island'); 2461 | warning(cycleType + ' not supported in this version'); 2462 | break; 2463 | case "probing-x-wall": 2464 | writeComment('probing-x-wall'); 2465 | warning(cycleType + ' not supported in this version'); 2466 | break; 2467 | case "probing-x-channel": 2468 | writeComment('probing-x-channel'); 2469 | warning(cycleType + ' not supported in this version'); 2470 | break; 2471 | case "probing-x-channel-with-island": 2472 | writeComment('probing-x-channel-with-island'); 2473 | warning(cycleType + ' not supported in this version'); 2474 | break; 2475 | case "probing-y-wall": 2476 | writeComment('probing-y-wall'); 2477 | warning(cycleType + ' not supported in this version'); 2478 | break; 2479 | case "probing-y-channel": 2480 | writeComment('probing-y-channel'); 2481 | warning(cycleType + ' not supported in this version'); 2482 | break; 2483 | case "probing-y-channel-with-island": 2484 | writeComment('probing-y-channel-with-island'); 2485 | warning(cycleType + ' not supported in this version'); 2486 | break; 2487 | case "counter-boring" : // counterbore with dwell - the expansion does not print the P word with milliseconds 2488 | writeComment('Counterboring'); 2489 | var _x = xOutput.format(x); 2490 | var _y = yOutput.format(y); 2491 | zOutput.reset(); 2492 | var hclr = zOutput.format(cycle.clearance); // clearance height 2493 | var hret = zOutput.format(cycle.retract); // retract height 2494 | var _z = zOutput.format(z); // drill depth 2495 | var dwell = "P" + secFormat.format(cycle.dwell); // dwell length in seconds 2496 | var feed = feedOutput.format(cycle.feedrate); 2497 | if (debugMode) writeComment('counter-boring cycle '+_x+_y+_z + dwell+feed); 2498 | writeBlock(gMotionModal.format(0), _x,_y); // G0 to xy 2499 | writeBlock(gMotionModal.format(0), hret); // G0 to retractheight 2500 | writeBlock(gMotionModal.format(1),_z,feed); // G1 to drill depth 2501 | if (cycle.dwell > 0) 2502 | writeBlock(gFormat.format(4), dwell); // dwell 2503 | writeBlock(gMotionModal.format(0), hclr); // G0 to clearance height 2504 | break; 2505 | default: 2506 | if (debugMode) writeComment('Expanding cycle ' + cycleType); 2507 | expandCyclePoint(x, y, z); 2508 | return; 2509 | } 2510 | } 2511 | 2512 | -------------------------------------------------------------------------------- /OpenbuildsFusion360PostGrblX32-4thaxis-beta.cps: -------------------------------------------------------------------------------- 1 | /** 2 | Original sample post: 3 | Copyright (C) 2012-2022 by Autodesk, Inc. 4 | All rights reserved. 5 | 6 | RS-274D Multi-axis post processor configuration. 7 | The above post sample forms the basis for this post. 8 | 9 | $Revision: 44023 7d0062d6193198b074b1bb174154c949e72cb2df $ 10 | $Date: 2022-11-04 21:33:14 $ 11 | $Id$ 12 | 13 | FORKID {2EECF092-D7C3-4ACA-BFE6-377B72950FE9} 14 | 15 | This post: 16 | Additions based on the OpenBuildsFusion360PostGRBL.cps 17 | Custom Post-Processor for grblHAL based Openbuilds-style CNC machines 18 | For BlackboxX32 based on ESP32 for grblHAL with 4th axis 19 | 20 | DOES NOT DO LASER AND PLASMA - ONLY MILLING 21 | 22 | Made possible by 23 | Swarfer https://github.com/swarfer/GRBL-Post-Processor 24 | Sharmstr https://github.com/sharmstr/GRBL-Post-Processor 25 | Strooom https://github.com/Strooom/GRBL-Post-Processor 26 | This post-Processor should work on GRBLhal-based machines 27 | 28 | Changelog 29 | xx/Dec/2022 - V0.0.1 : Initial version (Swarfer) 30 | Jan 2024 - V0.0.3b : machine simulation 31 | MAr 2024 - V0.0.4 : remove alert() calls 32 | 33 | */ 34 | obversion = 'V0.0.3_beta'; 35 | debugMode = false; 36 | description = "OB BBx32 Multi-axis Post Processor Milling Only"; 37 | vendor = "Openbuilds"; 38 | vendorUrl = "http://www.openbuilds.com"; 39 | machineControl = "grblHAL 1.1 ESP32 / BlackBox X32 XYZA", 40 | legal = "Copyright (C) 2012-2023 by Autodesk, Inc. and OpenBuilds.com 2024"; 41 | model = "grblHAL"; 42 | certificationLevel = 2; 43 | minimumRevision = 45892; 44 | 45 | longDescription = "MultiAxis post for Blackbox X32 with single rotary axis A or plain XYZ - MILLING ONLY."; 46 | 47 | extension = "gcode"; 48 | setCodePage("ascii"); 49 | 50 | var permittedCommentChars = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,=_-*/\\:"; 51 | capabilities = CAPABILITY_MILLING | CAPABILITY_MACHINE_SIMULATION; 52 | tolerance = spatial(0.002, MM); 53 | 54 | minimumChordLength = spatial(0.25, MM); 55 | minimumCircularRadius = spatial(0.125, MM); // 0.125 56 | maximumCircularRadius = spatial(1000, MM); 57 | minimumCircularSweep = toRad(0.1); 58 | maximumCircularSweep = toRad(180); 59 | allowHelicalMoves = true; 60 | allowSpiralMoves = false; 61 | allowedCircularPlanes = (1 << PLANE_XY); // allow only XY plane 62 | // if you need vertical arcs then uncomment the line below 63 | allowedCircularPlanes = (1 << PLANE_XY) | (1 << PLANE_ZX) | (1 << PLANE_YZ); // allow all planes, recentering arcs solves YZ/XZ arcs 64 | // if you allow vertical arcs then be aware that ObCONTROL will not display the gcode correctly, but it WILL cut correctly. 65 | 66 | /* 67 | useMultiAxisFeatures: { // DTS remove this and make always false 68 | title : "Use G68.2", 69 | description: "Enable to output G68.2 blocks for 3+2 operations, disable to output rotary angles.", 70 | group : "multiAxis", 71 | scope : ["machine", "post"], 72 | type : "boolean", 73 | value : false 74 | }, 75 | */ 76 | 77 | var showSequenceNumbers = false; // DTS - never want line numbers 78 | var preloadTool = false; // DTS - never want to preload 79 | var forceCyclesOff = true; // DTS - wait until CONTROL can display cycles before enabling this 80 | 81 | // user-defined properties 82 | properties = 83 | { 84 | optionalStop: { 85 | title: "Optional stop", 86 | description: "Outputs optional stop code when necessary in the code.", 87 | group: "preferences", 88 | type: "boolean", 89 | value: true, 90 | scope: "post" 91 | }, 92 | useToolChange: { // replaces generateMultiple 93 | title: "Use Toolchange M6", 94 | description: "Use tool change codes (true) or use , file per tool output (false).", 95 | group: "preferences", 96 | type: "boolean", 97 | value: false, 98 | scope: "post" 99 | }, 100 | routerType: { 101 | group: "spindle", 102 | title: "SPINDLE Router type", 103 | description: "Select the type of spindle you have.", 104 | type: "enum", 105 | value: "other", 106 | values: [ 107 | { title: "Other", id: "other" }, 108 | { title: "Router11", id: "Router11" }, 109 | { title: "Makita RT0701", id: "Makita" }, 110 | { title: "Dewalt 611", id: "Dewalt" } 111 | ] 112 | }, 113 | spindleOnOffDelay: { 114 | group: "spindle", 115 | title: "SPINDLE on/off delay", 116 | description: "Time (in seconds) the spindle needs to get up to speed or stop", 117 | type: "number", 118 | value: 1.5 119 | }, 120 | 121 | /* 122 | preloadTool: { 123 | title : "Preload tool", 124 | description: "Preloads the next tool at a tool change (if any).", 125 | group : "preferences", 126 | type : "boolean", 127 | value : true, 128 | scope : "post" 129 | }, 130 | */ 131 | safePositionMethod: { 132 | title: "Safe Retracts", 133 | description: "Select your desired retract option. 'Clearance Height' retracts to the operation clearance height.", 134 | group: "startEndPos", 135 | type: "enum", 136 | values: [ 137 | //{title:"G28", id:"G28"}, 138 | { title: "G53", id: "G53" }, 139 | { title: "Clearance Height", id: "clearanceHeight" } 140 | ], 141 | value: "G53", 142 | scope: "post" 143 | }, 144 | gotoMCSatend: { 145 | group: "startEndPos", 146 | title: "EndPos: Use Machine Coordinates (G53) at end of job?", 147 | description: "Yes will do G53 G0 x{machinehomeX} y(machinehomeY) (Machine Coordinates), No will do G0 x(machinehomeX) y(machinehomeY) (Work Coordinates) at end of program", 148 | type: "boolean", 149 | scope: "post", 150 | value: false 151 | }, 152 | machineHomeX: { 153 | group: "startEndPos", 154 | title: "EndPos: End of job X position (MM).", 155 | description: "(G53 or G54) X position to move to in Millimeters", 156 | type: "spatial", 157 | scope: "post", 158 | value: toPreciseUnit(-10, MM) 159 | }, 160 | machineHomeY: { 161 | group: "startEndPos", 162 | title: "EndPos: End of job Y position (MM).", 163 | description: "(G53 or G54) Y position to move to in Millimeters.", 164 | type: "spatial", 165 | scope: "post", 166 | value: toPreciseUnit(-10, MM) 167 | }, 168 | machineHomeZ: { 169 | group: "startEndPos", 170 | title: "startEndPos: START and End of job Z position (MCS Only) (MM)", 171 | description: "G53 Z position to move to in Millimeters, normally negative. Moves to this distance below Z home.", 172 | type: "spatial", 173 | scope: "post", 174 | value: toPreciseUnit(-10, MM) 175 | }, 176 | 177 | safeRetractDistance: { 178 | title: "Safe retract distance for rewinds", 179 | description: "Specifies the distance to add to retract distance when rewinding rotary axes.", 180 | group: "multiAxis", 181 | type: "spatial", 182 | value: 0, 183 | scope: "post" 184 | }, 185 | useABCPrepositioning: { 186 | title: "Preposition rotaries", 187 | description: "Enable to preposition rotary axes prior to G68.2 blocks.", 188 | group: "multiAxis", 189 | scope: ["machine", "post"], 190 | type: "boolean", 191 | value: true 192 | }, 193 | /* 194 | showSequenceNumbers: { 195 | title : "Use sequence numbers", 196 | description: "'Yes' outputs sequence numbers on each block, 'Only on tool change' outputs sequence numbers on tool change blocks only, and 'No' disables the output of sequence numbers.", 197 | group : "formats", 198 | type : "enum", 199 | values : [ 200 | {title:"Yes", id:"true"}, 201 | {title:"No", id:"false"}, 202 | {title:"Only on tool change", id:"toolChange"} 203 | ], 204 | value: "false", 205 | scope: "post" 206 | }, 207 | sequenceNumberStart: { 208 | title : "Start sequence number", 209 | description: "The number at which to start the sequence numbers.", 210 | group : "formats", 211 | type : "integer", 212 | value : 10, 213 | scope : "post" 214 | }, 215 | sequenceNumberIncrement: { 216 | title : "Sequence number increment", 217 | description: "The amount by which the sequence number is incremented by in each block.", 218 | group : "formats", 219 | type : "integer", 220 | value : 5, 221 | scope : "post" 222 | }, 223 | */ 224 | separateWordsWithSpace: { 225 | title: "Separate words with space", 226 | description: "Adds spaces between words if 'yes' is selected.", 227 | group: "formats", 228 | type: "boolean", 229 | value: true, 230 | scope: "post" 231 | }, 232 | showNotes: { 233 | title: "Show notes", 234 | description: "Writes setup and operation notes as comments in the output code.", 235 | group: "formats", 236 | type: "boolean", 237 | value: true, 238 | scope: "post" 239 | }, 240 | writeMachine: { 241 | title: "Write machine", 242 | description: "Output the machine settings in the header of the code.", 243 | group: "formats", 244 | type: "boolean", 245 | value: true, 246 | scope: "post" 247 | }, 248 | writeTools: { 249 | title: "Write tool list", 250 | description: "Output a tool list in the header of the code.", 251 | group: "formats", 252 | type: "boolean", 253 | value: true, 254 | scope: "post" 255 | } 256 | }; 257 | 258 | // define the order of display 259 | groupDefinitions = 260 | { 261 | spindle: { title:"Spindle Options", description:"Options for spindle control", collapsed: false, order: 5}, 262 | startEndPos: { title:"Start and End positions", description:"Set options for start and end safety positioning", collapsed: false, order: 7}, 263 | } 264 | 265 | var numberOfToolSlots = 9999; 266 | var numberOfSections = 0; 267 | 268 | var wcsDefinitions = 269 | { 270 | useZeroOffset: false, // set to 'true' to allow for workoffset 0, 'false' treats 0 as 1 271 | wcs: [ 272 | { name: "Standard", format: "G", range: [54, 59] }, // standard WCS, output as G54-G59 273 | { name: "Extended", format: "G59.#", range: [1, 3] } // extended WCS, output as G59.7, etc. 274 | // {name:"Extended", format:"G54 P#", range:[1, 64]} // extended WCS, output as G54 P7, etc. 275 | ] 276 | }; 277 | 278 | var singleLineCoolant = false; // specifies to output multiple coolant codes in one line rather than in separate lines 279 | // samples: 280 | // {id: COOLANT_THROUGH_TOOL, on: 88, off: 89} 281 | // {id: COOLANT_THROUGH_TOOL, on: [8, 88], off: [9, 89]} 282 | // {id: COOLANT_THROUGH_TOOL, on: "M88 P3 (myComment)", off: "M89"} 283 | var coolants = [ 284 | { id: COOLANT_FLOOD, on: 8 }, 285 | { id: COOLANT_MIST }, // not supported by X32 286 | { id: COOLANT_THROUGH_TOOL }, 287 | { id: COOLANT_AIR }, 288 | { id: COOLANT_AIR_THROUGH_TOOL }, 289 | { id: COOLANT_SUCTION }, 290 | { id: COOLANT_FLOOD_MIST }, 291 | { id: COOLANT_FLOOD_THROUGH_TOOL }, 292 | { id: COOLANT_OFF, off: 9 } 293 | ]; 294 | 295 | var gFormat = createFormat({ prefix: "G", decimals: 1 }); 296 | var mFormat = createFormat({ prefix: "M", decimals: 0 }); 297 | var hFormat = createFormat({ prefix: "H", decimals: 0 }); 298 | var dFormat = createFormat({ prefix: "D", decimals: 0 }); 299 | 300 | var xyzFormat = createFormat({ decimals: (unit == MM ? 3 : 4), type: FORMAT_REAL, minDigitsRight: 1 }); 301 | //var abcFormat = createFormat({decimals:3, type:FORMAT_REAL, scale:DEG}); 302 | var abcFormat = createFormat({ decimals: 3, type: FORMAT_REAL, scale: DEG, minDigitsRight: 1 }); 303 | var feedFormat = createFormat({ decimals: (unit == MM ? 1 : 2) }); 304 | var inverseTimeFormat = createFormat({ decimals: 3, type: FORMAT_REAL }); 305 | var toolFormat = createFormat({ decimals: 0 }); 306 | var rpmFormat = createFormat({ decimals: 0 }); 307 | var secFormat = createFormat({ decimals: 3, type: FORMAT_REAL }); // seconds - range 0.001-1000 308 | var taperFormat = createFormat({ decimals: 1, scale: DEG }); 309 | 310 | var xOutput = createOutputVariable({ prefix: "X" }, xyzFormat); 311 | var yOutput = createOutputVariable({ prefix: "Y" }, xyzFormat); 312 | var zOutput = createOutputVariable({ onchange: function () 313 | { 314 | retracted = false; 315 | }, prefix: "Z" 316 | }, xyzFormat); 317 | var aOutput = createOutputVariable({ prefix: "A" }, abcFormat); 318 | var bOutput = createOutputVariable({ prefix: "B" }, abcFormat); 319 | var cOutput = createOutputVariable({ prefix: "C" }, abcFormat); 320 | var feedOutput = createOutputVariable({ prefix: "F" }, feedFormat); 321 | var inverseTimeOutput = createOutputVariable({ prefix: "F", control: CONTROL_FORCE }, inverseTimeFormat); 322 | var sOutput = createOutputVariable({ prefix: "S", control: CONTROL_FORCE }, rpmFormat); 323 | var dOutput = createOutputVariable({}, dFormat); 324 | 325 | // circular output 326 | var iOutput = createOutputVariable({ prefix: "I", control: CONTROL_FORCE }, xyzFormat); 327 | var jOutput = createOutputVariable({ prefix: "J", control: CONTROL_FORCE }, xyzFormat); 328 | var kOutput = createOutputVariable({ prefix: "K", control: CONTROL_FORCE }, xyzFormat); 329 | 330 | var gMotionModal = createOutputVariable({}, gFormat); // modal group 1 // G0-G3, ... 331 | var gPlaneModal = createOutputVariable({ onchange: function () 332 | { 333 | gMotionModal.reset(); 334 | } 335 | }, gFormat); // modal group 2 // G17-19 336 | var gAbsIncModal = createOutputVariable({}, gFormat); // modal group 3 // G90-91 337 | var gFeedModeModal = createOutputVariable({}, gFormat); // modal group 5 // G93-94 338 | var gUnitModal = createOutputVariable({}, gFormat); // modal group 6 // G20-21 339 | var gCycleModal = createOutputVariable({}, gFormat); // modal group 9 // G81, ... 340 | var gRetractModal = createOutputVariable({}, gFormat); // modal group 10 // G98-99 341 | var gRotationModal = createOutputVariable({}, gFormat); // modal group 16 // G68-G69 342 | 343 | // settings 344 | 345 | var WARNING_WORK_OFFSET = 0; 346 | 347 | // collected state 348 | var fileSequenceNumber = 1; // DTS multifile naming 349 | var currentworkOffset = 54; // the current WCS in use, so we can retract Z between sections if needed 350 | 351 | var NsequenceNumber; 352 | var retracted = false; // specifies that the tool has been retracted to the safe plane 353 | var firstNote = true; // handles output of notes from multiple setups 354 | var forceSpindleSpeed = false; 355 | // from BB post - multifile output variables 356 | var filesToGenerate = 1; //used to figure out how many files will be generated so we can diplay in header 357 | var fileIndexFormat = createFormat({ width: 2, zeropad: true, decimals: 0 }); 358 | var isNewfile = false; // set true when a new file has just been started 359 | var numberOfSections = 0; 360 | var isLaser = false; // todo - laser and plasma 361 | var isPlasma = false; 362 | 363 | var haveRapid = false; // assume no rapid moves 364 | var linmove = 1; // linear move mode 365 | var retractHeight = 1; // will be set by onParameter and used in onLinear to detect rapids 366 | 367 | var linearizeSmallArcs = false; // arcs with radius < toolRadius have radius errors, linearize instead? 368 | var toolRadius = toPreciseUnit(1, MM); 369 | var lengthCompensated = false; // true if length compensation is on 370 | 371 | /** 372 | Writes the specified block. 373 | */ 374 | function writeBlock() 375 | { 376 | if (!formatWords(arguments)) 377 | { 378 | return; 379 | } 380 | if (showSequenceNumbers == true) 381 | { 382 | writeWords2("N" + NsequenceNumber, arguments); 383 | NsequenceNumber += getProperty("sequenceNumberIncrement", 1); 384 | } 385 | else 386 | { 387 | writeWords(arguments); 388 | } 389 | } 390 | 391 | function formatComment(text, indent ) 392 | { 393 | indent = String(indent); 394 | //return "(" + String(text).replace(/[()]/g, "") + ")"; 395 | return ("(" + indent + filterText(String(text), permittedCommentChars) + ")"); 396 | } 397 | 398 | /** 399 | Writes the specified block - used for tool changes only. 400 | */ 401 | function writeToolBlock() 402 | { 403 | if (getProperty("useToolChange", false)) 404 | { 405 | writeComment("writeToolBock"); 406 | //var show = getProperty("showSequenceNumbers",false); 407 | //setProperty("showSequenceNumbers", (show == "true" || show == "toolChange") ? "true" : "false"); 408 | //todo - DTS - make tool calls optional 409 | writeBlock(arguments); 410 | //setProperty("showSequenceNumbers", show); 411 | } 412 | else 413 | { 414 | writeComment("Tool change avoided, see other file"); 415 | } 416 | } 417 | 418 | /** 419 | Output a comment. 420 | DTS - use multilines if needed 421 | */ 422 | function writeComment(text) 423 | { 424 | // split the line so no comment is longer than 70 chars 425 | text = filterText(text.trim(), permittedCommentChars); 426 | var indent = ''; 427 | if (text.length > 70) 428 | { 429 | //text = String(text).replace( /[^a-zA-Z\d:=,.]+/g, " "); // remove illegal chars 430 | var bits = text.split(" "); // get all the words 431 | var out = ''; 432 | for (i = 0; i < bits.length; i++) 433 | { 434 | out += bits[i] + " "; // additional space after first line 435 | if (out.length > 60) // a long word on the end can take us to 80 chars! 436 | { 437 | writeln(formatComment(out.trim(), indent)); 438 | out = ""; 439 | indent = ' '; 440 | } 441 | } 442 | if (out.length > 0) 443 | writeln(formatComment(out.trim(),indent)); 444 | } 445 | else 446 | writeln(formatComment(text,'')); 447 | } 448 | 449 | // Start of machine configuration logic 450 | var compensateToolLength = false; // add the tool length to the pivot distance for nonTCP rotary heads 451 | var useMultiAxisFeatures = false; // not for grblHAL, enable to use control enabled tilted plane, can be overridden with a property 452 | var useABCPrepositioning = false; // enable to preposition rotary axes prior to tilted plane output, can be overridden with a property 453 | var forceMultiAxisIndexing = false; // force multi-axis indexing for 3D programs 454 | var eulerConvention = EULER_ZXZ_R; // euler angle convention for 3+2 operations 455 | 456 | // internal variables, do not change 457 | var receivedMachineConfiguration; 458 | var operationSupportsTCP; 459 | var multiAxisFeedrate; 460 | 461 | /** 462 | Activates the machine configuration (both from CAM and hardcoded) 463 | */ 464 | function activateMachine() 465 | { 466 | if (debugMode) writeComment("DEBUG activateMachine"); 467 | // disable unsupported rotary axes output 468 | if (!machineConfiguration.isMachineCoordinate(0) && (typeof aOutput != "undefined")) 469 | { 470 | if (debugMode) writeComment("DEBUG activateMachine A disable"); 471 | aOutput.disable(); 472 | } 473 | if (!machineConfiguration.isMachineCoordinate(1) && (typeof bOutput != "undefined")) 474 | { 475 | if (debugMode) writeComment("DEBUG activateMachine B disable"); 476 | bOutput.disable(); 477 | } 478 | if (!machineConfiguration.isMachineCoordinate(2) && (typeof cOutput != "undefined")) 479 | { 480 | if (debugMode) writeComment("DEBUG activateMachine C disable"); 481 | cOutput.disable(); 482 | } 483 | 484 | // setup usage of multiAxisFeatures 485 | useMultiAxisFeatures = getProperty("useMultiAxisFeatures") != undefined ? getProperty("useMultiAxisFeatures") : 486 | (typeof useMultiAxisFeatures != "undefined" ? useMultiAxisFeatures : false); 487 | useABCPrepositioning = getProperty("useABCPrepositioning") != undefined ? getProperty("useABCPrepositioning") : 488 | (typeof useABCPrepositioning != "undefined" ? useABCPrepositioning : false); 489 | if (debugMode) writeComment("DEBUG useMultiAxisFeatures " + useMultiAxisFeatures); 490 | if (debugMode) writeComment("DEBUG useABCPrepositioning " + useABCPrepositioning); 491 | // don't need to modify any settings if 3-axis machine 492 | if (!machineConfiguration.isMultiAxisConfiguration()) 493 | { 494 | return; 495 | } 496 | 497 | // save multi-axis feedrate settings from machine configuration 498 | var mode = machineConfiguration.getMultiAxisFeedrateMode(); 499 | var type = mode == FEED_INVERSE_TIME ? machineConfiguration.getMultiAxisFeedrateInverseTimeUnits() : 500 | (mode == FEED_DPM ? machineConfiguration.getMultiAxisFeedrateDPMType() : DPM_STANDARD); 501 | multiAxisFeedrate = 502 | { 503 | mode: mode, 504 | maximum: machineConfiguration.getMultiAxisFeedrateMaximum(), 505 | type: type, 506 | tolerance: mode == FEED_DPM ? machineConfiguration.getMultiAxisFeedrateOutputTolerance() : 0, 507 | bpwRatio : mode == FEED_DPM ? machineConfiguration.getMultiAxisFeedrateBpwRatio() : 1 508 | }; 509 | 510 | // setup of retract/reconfigure TAG: Only needed until post kernel supports these machine config settings 511 | if (receivedMachineConfiguration && machineConfiguration.performRewinds()) 512 | { 513 | safeRetractDistance = machineConfiguration.getSafeRetractDistance(); 514 | safePlungeFeed = machineConfiguration.getSafePlungeFeedrate(); 515 | safeRetractFeed = machineConfiguration.getSafeRetractFeedrate(); 516 | } 517 | if (typeof safeRetractDistance == "number" && getProperty("safeRetractDistance") != undefined && getProperty("safeRetractDistance") != 0) 518 | { 519 | safeRetractDistance = getProperty("safeRetractDistance"); 520 | } 521 | 522 | // setup for head configurations 523 | if (machineConfiguration.isHeadConfiguration()) 524 | { 525 | compensateToolLength = typeof compensateToolLength == "undefined" ? false : compensateToolLength; 526 | } 527 | 528 | // calculate the ABC angles and adjust the points for multi-axis operations 529 | // rotary heads may require the tool length be added to the pivot length 530 | // so we need to optimize each section individually 531 | if (machineConfiguration.isHeadConfiguration() && compensateToolLength) 532 | { 533 | writeComment('compensating') ; 534 | for (var i = 0; i < getNumberOfSections(); ++i) 535 | { 536 | var section = getSection(i); 537 | if (section.isMultiAxis()) 538 | { 539 | machineConfiguration.setToolLength(section.getTool().overallLength); // define the tool length for head adjustments 540 | section.optimizeMachineAnglesByMachine(machineConfiguration, OPTIMIZE_AXIS); 541 | } 542 | } 543 | } 544 | else // tables and rotary heads with TCP support can be optimized with a single call 545 | { 546 | if (debugMode) writeComment('optimizing machine angles') ; 547 | optimizeMachineAngles2(OPTIMIZE_AXIS); 548 | } 549 | } 550 | 551 | /** 552 | Defines a hardcoded machine configuration 553 | */ 554 | function defineMachine() 555 | { 556 | if (debugMode) writeComment("DEBUG defineMachine"); 557 | if (!receivedMachineConfiguration) // CAM provided machine configuration takes precedence 558 | { 559 | writeComment("Using hardcoded machine XYZ - if you want A-axis then define a suitable machine in Fusion360"); 560 | // if (true) { // hardcoded machine configuration takes precedence 561 | // define machine kinematics 562 | var useTCP = false; 563 | // todo - allow user to choose axis direction 564 | //var aAxis = createAxis({coordinate:X, table:true, axis:[1, 0, 0], offset:[0, 0, 0], range:[0,360], cyclic:true, preference:-1, tcp:useTCP}); 565 | //machineConfiguration = new MachineConfiguration(aAxis); 566 | machineConfiguration = new MachineConfiguration(); 567 | machineConfiguration.setVendor("OpenBuilds"); 568 | machineConfiguration.setModel("BBx32"); 569 | machineConfiguration.setDescription(description); 570 | 571 | // multiaxis settings 572 | if (machineConfiguration.isHeadConfiguration()) 573 | { 574 | machineConfiguration.setVirtualTooltip(false); // translate the pivot point to the virtual tool tip for nonTCP rotary heads 575 | } 576 | 577 | // retract / reconfigure 578 | var performRewinds = false; // set to true to enable the retract/reconfigure logic 579 | if (performRewinds) 580 | { 581 | machineConfiguration.enableMachineRewinds(); // enables the retract/reconfigure logic 582 | safeRetractDistance = (unit == IN) ? 1 : 25; // additional distance to retract out of stock, can be overridden with a property 583 | safeRetractFeed = (unit == IN) ? 20 : 500; // retract feed rate 584 | safePlungeFeed = (unit == IN) ? 10 : 250; // plunge feed rate 585 | machineConfiguration.setSafeRetractDistance(safeRetractDistance); 586 | machineConfiguration.setSafeRetractFeedrate(safeRetractFeed); 587 | machineConfiguration.setSafePlungeFeedrate(safePlungeFeed); 588 | var stockExpansion = new Vector(toPreciseUnit(0.1, IN), toPreciseUnit(0.1, IN), toPreciseUnit(0.1, IN)); // expand stock XYZ values 589 | machineConfiguration.setRewindStockExpansion(stockExpansion); 590 | } 591 | 592 | // multi-axis feedrates 593 | if (machineConfiguration.isMultiAxisConfiguration()) 594 | { 595 | machineConfiguration.setMultiAxisFeedrate( 596 | useTCP ? FEED_FPM : getProperty("useDPMFeeds") ? FEED_DPM : FEED_INVERSE_TIME, 597 | 9999.99, // maximum output value for inverse time feed rates 598 | getProperty("useDPMFeeds") ? DPM_COMBINATION : INVERSE_MINUTES, // INVERSE_MINUTES/INVERSE_SECONDS or DPM_COMBINATION/DPM_STANDARD 599 | 0.5, // tolerance to determine when the DPM feed has changed 600 | 1.0 // ratio of rotary accuracy to linear accuracy for DPM calculations 601 | ); 602 | } 603 | 604 | /* home positions */ 605 | // machineConfiguration.setHomePositionX(toPreciseUnit(0, IN)); 606 | // machineConfiguration.setHomePositionY(toPreciseUnit(0, IN)); 607 | // machineConfiguration.setRetractPlane(toPreciseUnit(0, IN)); 608 | 609 | // define the machine configuration 610 | setMachineConfiguration(machineConfiguration); // inform post kernel of hardcoded machine configuration 611 | if (receivedMachineConfiguration) 612 | { 613 | warning(localize("The provided CAM machine configuration is overwritten by the postprocessor.")); 614 | receivedMachineConfiguration = false; // CAM provided machine configuration is overwritten 615 | } 616 | } 617 | } 618 | // End of machine configuration logic 619 | 620 | function onOpen() 621 | { 622 | if (debugMode) 623 | { 624 | warning("debugMode is true"); 625 | } 626 | //setWriteInvocations(debugMode); 627 | // define and enable machine configuration 628 | receivedMachineConfiguration = machineConfiguration.isReceived(); 629 | if (typeof defineMachine == "function") 630 | { 631 | defineMachine(); // hardcoded machine configuration 632 | } 633 | activateMachine(); // enable the machine optimizations and settings 634 | 635 | gRotationModal.format(69); // Default to G69 Rotation Off 636 | 637 | if (!getProperty("separateWordsWithSpace")) 638 | { 639 | setWordSeparator(""); 640 | } 641 | 642 | showSequenceNumbers = getProperty("showSequenceNumbers", false); 643 | NsequenceNumber = getProperty("sequenceNumberStart", 1); 644 | preloadTool = getProperty("preloadTool", false); 645 | numberOfSections = getNumberOfSections(); 646 | 647 | numberOfSections = getNumberOfSections(); 648 | checkforDuplicatetools(); // sets filesToGenerate 649 | writeHeader(0); 650 | 651 | if (programName) 652 | { 653 | writeComment(programName); 654 | } 655 | if (programComment) 656 | { 657 | writeComment(programComment); 658 | } 659 | 660 | // dump machine configuration 661 | var vendor = machineConfiguration.getVendor(); 662 | var model = machineConfiguration.getModel(); 663 | var description = machineConfiguration.getDescription(); 664 | 665 | if (getProperty("writeMachine") && (vendor || model || description)) 666 | { 667 | writeComment(localize("Machine")); 668 | if (vendor) 669 | { 670 | writeComment(" " + localize("vendor") + ": " + vendor); 671 | } 672 | if (model) 673 | { 674 | writeComment(" " + localize("model") + ": " + model); 675 | } 676 | if (description) 677 | { 678 | writeComment(" " + localize("description") + ": " + description); 679 | } 680 | } 681 | 682 | // dump tool information 683 | if (getProperty("writeTools")) 684 | { 685 | var zRanges = {}; 686 | if (is3D()) 687 | { 688 | var numberOfSections = getNumberOfSections(); 689 | for (var i = 0; i < numberOfSections; ++i) 690 | { 691 | var section = getSection(i); 692 | var zRange = section.getGlobalZRange(); 693 | var tool = section.getTool(); 694 | if (zRanges[tool.number]) 695 | { 696 | zRanges[tool.number].expandToRange(zRange); 697 | } 698 | else 699 | { 700 | zRanges[tool.number] = zRange; 701 | } 702 | } 703 | } 704 | 705 | var tools = getToolTable(); 706 | if (tools.getNumberOfTools() > 0) 707 | { 708 | for (var i = 0; i < tools.getNumberOfTools(); ++i) 709 | { 710 | var tool = tools.getTool(i); 711 | var comment = "T" + toolFormat.format(tool.number) + " " + 712 | "D=" + xyzFormat.format(tool.diameter) + " " + 713 | localize("CR") + "=" + xyzFormat.format(tool.cornerRadius); 714 | if ((tool.taperAngle > 0) && (tool.taperAngle < Math.PI)) 715 | { 716 | comment += " " + localize("TAPER") + "=" + taperFormat.format(tool.taperAngle) + localize("deg"); 717 | } 718 | if (zRanges[tool.number]) 719 | { 720 | comment += " - " + localize("ZMIN") + "=" + xyzFormat.format(zRanges[tool.number].getMinimum()); 721 | } 722 | comment += " - " + getToolTypeName(tool.type); 723 | writeComment(comment); 724 | } 725 | } 726 | } 727 | 728 | // output setup notes 729 | if (getProperty("showNotes")) 730 | { 731 | writeSetupNotes(); 732 | } 733 | 734 | if ((getNumberOfSections() > 0) && (getSection(0).workOffset == 0)) 735 | { 736 | for (var i = 0; i < getNumberOfSections(); ++i) 737 | { 738 | if (getSection(i).workOffset > 0) 739 | { 740 | error(localize("Using multiple work offsets is not possible if the initial work offset is 0.")); 741 | return; 742 | } 743 | } 744 | } 745 | 746 | // absolute coordinates and feed per min 747 | //writeBlock(gAbsIncModal.format(90), gFeedModeModal.format(94), gFeedModeModal.format(49)); 748 | writeBlock(gAbsIncModal.format(90), gFeedModeModal.format(94), writeBlock(gPlaneModal.format(17)) ); 749 | 750 | switch (unit) 751 | { 752 | case IN: 753 | writeBlock(gUnitModal.format(20)); 754 | break; 755 | case MM: 756 | writeBlock(gUnitModal.format(21)); 757 | break; 758 | } 759 | } 760 | 761 | function onComment(message) 762 | { 763 | writeComment(message); 764 | } 765 | 766 | /** Force output of X, Y, and Z. */ 767 | function forceXYZ() 768 | { 769 | xOutput.reset(); 770 | yOutput.reset(); 771 | zOutput.reset(); 772 | } 773 | 774 | /** Force output of A, B, and C. */ 775 | function forceABC() 776 | { 777 | aOutput.reset(); 778 | bOutput.reset(); 779 | cOutput.reset(); 780 | } 781 | 782 | /** Force output of X, Y, Z, A, B, C, and F on next output. */ 783 | function forceAny() 784 | { 785 | forceXYZ(); 786 | forceABC(); 787 | feedOutput.reset(); 788 | } 789 | 790 | var lengthCompensationActive = false; 791 | /** Disables length compensation if currently active or if forced. */ 792 | function disableLengthCompensation(force) 793 | { 794 | if (lengthCompensationActive || force) 795 | { 796 | if (debugMode) writeComment('DEBUG disableLengthCompensation'); 797 | validate(retracted, "Cannot cancel length compensation if the machine is not fully retracted."); 798 | writeBlock(gFormat.format(49)); 799 | lengthCompensationActive = false; 800 | } 801 | } 802 | 803 | var currentWorkPlaneABC = undefined; 804 | 805 | function forceWorkPlane() 806 | { 807 | currentWorkPlaneABC = undefined; 808 | } 809 | 810 | function defineWorkPlane(_section, _setWorkPlane) 811 | { 812 | var abc = new Vector(0, 0, 0); 813 | if (forceMultiAxisIndexing || !is3D() || machineConfiguration.isMultiAxisConfiguration()) // use 5-axis indexing for multi-axis mode 814 | { 815 | // set working plane after datum shift 816 | 817 | if (_section.isMultiAxis()) 818 | { 819 | cancelTransformation(); 820 | if (_setWorkPlane) 821 | { 822 | forceWorkPlane(); 823 | } 824 | if (machineConfiguration.isMultiAxisConfiguration()) 825 | { 826 | abc = _section.getInitialToolAxisABC(); 827 | if (_setWorkPlane) 828 | { 829 | onCommand(COMMAND_UNLOCK_MULTI_AXIS); 830 | positionABC(abc, true); 831 | } 832 | } 833 | else 834 | { 835 | if (_setWorkPlane) 836 | { 837 | var d = _section.getGlobalInitialToolAxis(); 838 | // position 839 | writeBlock( 840 | gAbsIncModal.format(90), 841 | gMotionModal.format(0), 842 | "I" + xyzFormat.format(d.x), "J" + xyzFormat.format(d.y), "K" + xyzFormat.format(d.z) 843 | ); 844 | } 845 | } 846 | } 847 | else 848 | { 849 | if (useMultiAxisFeatures) 850 | { 851 | abc = _section.workPlane.getEuler2(eulerConvention); 852 | cancelTransformation(); 853 | } 854 | else 855 | { 856 | abc = getWorkPlaneMachineABC(_section.workPlane, true); 857 | } 858 | if (_setWorkPlane) 859 | { 860 | setWorkPlane(abc); 861 | } 862 | } 863 | } 864 | else // pure 3D 865 | { 866 | var remaining = _section.workPlane; 867 | if (!isSameDirection(remaining.forward, new Vector(0, 0, 1))) 868 | { 869 | error(localize("Tool orientation is not supported.")); 870 | return abc; 871 | } 872 | setRotation(remaining); 873 | } 874 | if (currentSection && (currentSection.getId() == _section.getId())) 875 | { 876 | operationSupportsTCP = (_section.isMultiAxis() || !useMultiAxisFeatures) && _section.getOptimizedTCPMode() == OPTIMIZE_NONE; 877 | } 878 | return abc; 879 | } 880 | 881 | function cancelWorkPlane() 882 | { 883 | writeBlock(gRotationModal.format(69)); // cancel frame 884 | forceWorkPlane(); 885 | } 886 | 887 | function setWorkPlane(abc) 888 | { 889 | if (is3D() && !machineConfiguration.isMultiAxisConfiguration()) 890 | { 891 | return; // ignore 892 | } 893 | if (!((currentWorkPlaneABC == undefined) || 894 | abcFormat.areDifferent(abc.x, currentWorkPlaneABC.x) || 895 | abcFormat.areDifferent(abc.y, currentWorkPlaneABC.y) || 896 | abcFormat.areDifferent(abc.z, currentWorkPlaneABC.z))) 897 | { 898 | return; // no change 899 | } 900 | onCommand(COMMAND_UNLOCK_MULTI_AXIS); 901 | 902 | if (!retracted) 903 | { 904 | writeRetract(Z); 905 | } 906 | 907 | if (useMultiAxisFeatures) 908 | { 909 | cancelWorkPlane(); 910 | if (machineConfiguration.isMultiAxisConfiguration()) 911 | { 912 | var machineABC = abc.isNonZero() ? getWorkPlaneMachineABC(currentSection.workPlane, false) : abc; 913 | if (useABCPrepositioning || abc.isZero()) 914 | { 915 | positionABC(machineABC, true); 916 | } 917 | setCurrentABC(machineABC); // required for machine simulation 918 | } 919 | if (abc.isNonZero()) 920 | { 921 | gRotationModal.reset(); 922 | writeBlock(gRotationModal.format(68.2), "X" + xyzFormat.format(0), "Y" + xyzFormat.format(0), "Z" + xyzFormat.format(0), "I" + abcFormat.format(abc.x), "J" + abcFormat.format(abc.y), "K" + abcFormat.format(abc.z)); // set frame 923 | writeBlock(gFormat.format(53.1)); // turn machine 924 | } 925 | } 926 | else 927 | { 928 | positionABC(abc, true); 929 | } 930 | onCommand(COMMAND_LOCK_MULTI_AXIS); 931 | 932 | currentWorkPlaneABC = abc; 933 | } 934 | 935 | function getWorkPlaneMachineABC(workPlane, rotate) 936 | { 937 | var W = workPlane; // map to global frame 938 | 939 | var currentABC = isFirstSection() ? new Vector(0, 0, 0) : getCurrentDirection(); 940 | var abc = machineConfiguration.getABCByPreference(W, currentABC, ABC, PREFER_PREFERENCE, ENABLE_ALL); 941 | 942 | var direction = machineConfiguration.getDirection(abc); 943 | if (!isSameDirection(direction, W.forward)) 944 | { 945 | error(localize("Orientation not supported.")); 946 | } 947 | 948 | if (rotate && !currentSection.isOptimizedForMachine()) 949 | { 950 | machineConfiguration.setToolLength(compensateToolLength ? currentSection.getTool().overallLength : 0); // define the tool length for head adjustments 951 | currentSection.optimize3DPositionsByMachine(machineConfiguration, abc, OPTIMIZE_AXIS); 952 | } 953 | return abc; 954 | } 955 | 956 | function positionABC(abc, force) 957 | { 958 | if (typeof unwindABC == "function") 959 | { 960 | unwindABC(abc, false); 961 | } 962 | if (force) 963 | { 964 | forceABC(); 965 | } 966 | var a = aOutput.format(abc.x); 967 | var b = bOutput.format(abc.y); 968 | var c = cOutput.format(abc.z); 969 | if (a || b || c) 970 | { 971 | if (!retracted) 972 | { 973 | if (typeof moveToSafeRetractPosition == "function") 974 | { 975 | moveToSafeRetractPosition(); 976 | } 977 | else 978 | { 979 | writeRetract(Z); 980 | } 981 | } 982 | onCommand(COMMAND_UNLOCK_MULTI_AXIS); 983 | gMotionModal.reset(); 984 | writeBlock(gMotionModal.format(0), a, b, c); 985 | setCurrentABC(abc); // required for machine simulation 986 | } 987 | } 988 | 989 | function onPassThrough(text) 990 | { 991 | writeNotes(text); 992 | } 993 | 994 | function onParameter(name, value) 995 | { 996 | switch (name) 997 | { 998 | case "job-notes": // write setup notes when multiple setups are used 999 | if (!firstNote) 1000 | { 1001 | writeNotes(value, true); 1002 | } 1003 | firstNote = false; 1004 | break; 1005 | } 1006 | name = name.replace(" ", "_"); // dratted indexOF cannot have spaces in it! 1007 | if ( (name.indexOf("retractHeight_value") >= 0 ) ) // == "operation:retractHeight value") 1008 | { 1009 | retractHeight = value; 1010 | if (debugMode) writeComment("DEBUG onParameter:retractHeight = " + retractHeight); 1011 | } 1012 | } 1013 | 1014 | function writeNotes(text, asComment) 1015 | { 1016 | if (text) 1017 | { 1018 | var lines = String(text).split("\n"); 1019 | var r2 = new RegExp("[\\s]+$", "g"); 1020 | for (line in lines) 1021 | { 1022 | var comment = lines[line].replace(r2, ""); 1023 | if (comment) 1024 | { 1025 | if (asComment) 1026 | { 1027 | onComment(comment); 1028 | } 1029 | else 1030 | { 1031 | writeln(comment); 1032 | } 1033 | } 1034 | } 1035 | } 1036 | } 1037 | 1038 | function onSection() 1039 | { 1040 | var nmbrOfSections = getNumberOfSections(); // how many operations are there in total 1041 | var sectionId = getCurrentSectionId(); // what is the number of this operation (starts from 0) 1042 | var section = getSection(sectionId); // what is the section-object for this operation 1043 | var tool = section.getTool(); 1044 | //var maxfeedrate = section.getMaximumFeedrate(); 1045 | haveRapid = false; // drilling sections will have rapids even when other ops do not 1046 | 1047 | onRadiusCompensation(); // must check every section 1048 | toolRadius = tool.diameter / 2.0; 1049 | 1050 | var insertToolCall = isToolChangeNeeded("number"); 1051 | 1052 | var splitHere = !getProperty("useToolChange") && insertToolCall && !isFirstSection(); 1053 | 1054 | var newWorkOffset = isNewWorkOffset(); 1055 | var newWorkPlane = isNewWorkPlane(); 1056 | 1057 | // cleanup before tool change 1058 | if (insertToolCall || newWorkOffset || newWorkPlane) 1059 | { 1060 | // stop spindle before retract during tool change 1061 | if (insertToolCall && !isFirstSection()) 1062 | { 1063 | onCommand(COMMAND_STOP_SPINDLE); 1064 | } 1065 | // retract to safe plane 1066 | if (!is3D() && insertToolCall) //DTS - dont really want to retract all the way for plain XYZ operations 1067 | { 1068 | zOutput.reset(); 1069 | writeRetract(Z); 1070 | } 1071 | // cancel tool length compensation 1072 | if (insertToolCall && !isFirstSection()) 1073 | { 1074 | if (!retracted) 1075 | writeRetract(Z); 1076 | disableLengthCompensation(false); 1077 | } 1078 | } 1079 | 1080 | if (splitHere) 1081 | { 1082 | writeComment("splitting"); 1083 | //writeBlock(mFormat.format(30)); // stop program 1084 | // todo -bug here, first file has null gcode 1085 | debug("splitting file " + fileSequenceNumber); 1086 | fileSequenceNumber++; 1087 | //var fileIndexFormat = createFormat({width:3, zeropad: true, decimals:0}); 1088 | //var path = FileSystem.replaceExtension(getOutputPath(), fileIndexFormat.format(fileSequenceNumber) + "of" + filesToGenerate + "." + extension); 1089 | var path = makeFileName(fileSequenceNumber); 1090 | if (isRedirecting()) 1091 | { 1092 | warning('still redirecting in onsection'); 1093 | closeRedirection(); 1094 | } 1095 | redirectToFile(path); 1096 | forceAny(); 1097 | writeHeader(getCurrentSectionId()); 1098 | isNewfile = true; // trigger a spindleondelay 1099 | } 1100 | // DTS below this goes into new file 1101 | writeln(""); 1102 | if (debugMode) writeComment("DEBUG onSection " + (sectionId + 1)); 1103 | // Insert a small comment section to identify the related G-Code in a large multi-operations file 1104 | var comment = "Operation " + (sectionId + 1) + " of " + nmbrOfSections; 1105 | if (hasParameter("operation-comment")) 1106 | { 1107 | comment = comment + " : " + getParameter("operation-comment"); 1108 | } 1109 | writeComment(comment); 1110 | if (debugMode) 1111 | writeComment("DEBUG retractHeight = " + retractHeight); 1112 | 1113 | // output section notes 1114 | if (getProperty("showNotes")) 1115 | { 1116 | writeSectionNotes(); 1117 | } 1118 | 1119 | if (insertToolCall && getProperty("useToolChange")) 1120 | { 1121 | if (debugMode) writeComment('DEBUG insert tool call'); 1122 | forceWorkPlane(); 1123 | 1124 | setCoolant(COOLANT_OFF); 1125 | 1126 | if (!isFirstSection() && getProperty("optionalStop")) 1127 | { 1128 | onCommand(COMMAND_OPTIONAL_STOP); 1129 | } 1130 | 1131 | if (tool.number > numberOfToolSlots) 1132 | { 1133 | warning(localize("Tool number exceeds maximum value.")); 1134 | } 1135 | 1136 | disableLengthCompensation(false); 1137 | writeToolBlock("T" + toolFormat.format(tool.number), mFormat.format(6)); 1138 | if (tool.comment) 1139 | { 1140 | writeComment(tool.comment); 1141 | } 1142 | var showToolZMin = false; 1143 | if (showToolZMin) 1144 | { 1145 | if (is3D()) 1146 | { 1147 | var zRange = toolZRange(); 1148 | writeComment(localize("ZMIN") + "=" + zRange.getMinimum()); 1149 | } 1150 | } 1151 | 1152 | if (preloadTool) // DTS always false 1153 | { 1154 | var nextTool = getNextTool(tool.number); 1155 | if (nextTool) 1156 | { 1157 | writeBlock("T" + toolFormat.format(nextTool.number)); 1158 | } 1159 | else 1160 | { 1161 | // preload first tool 1162 | var firstToolNumber = getFirstTool().number; 1163 | if (tool.number != firstToolNumber) 1164 | { 1165 | writeBlock("T" + toolFormat.format(firstToolNumber)); 1166 | } 1167 | } 1168 | } 1169 | } 1170 | 1171 | var spindleChanged = tool.type != TOOL_PROBE && 1172 | (insertToolCall || forceSpindleSpeed || isFirstSection() || 1173 | (rpmFormat.areDifferent(spindleSpeed, sOutput.getCurrent())) || 1174 | (tool.clockwise != getPreviousSection().getTool().clockwise)); 1175 | if (spindleChanged) 1176 | { 1177 | forceSpindleSpeed = false; 1178 | if (spindleSpeed < 1) 1179 | { 1180 | error(localize("Spindle speed out of range.")); 1181 | return; 1182 | } 1183 | if (spindleSpeed > 99999) 1184 | { 1185 | warning(localize("Spindle speed exceeds maximum value.")); 1186 | } 1187 | var rpmchanged = !isFirstSection() && rpmFormat.areDifferent(spindleSpeed, sOutput.getCurrent()) 1188 | if (debugMode) writeComment('DEBUG rpmchanged ' + rpmchanged); 1189 | s = sOutput.format(spindleSpeed); 1190 | if (s) 1191 | mFormat.format(1); // always output M if S changed 1192 | m = mFormat.format(tool.clockwise ? 3 : 4) 1193 | writeBlock(m, s); 1194 | //if (s && !m) // means a speed change, spindle was already on, delay half the time 1195 | if ( rpmchanged ) 1196 | onDwell(getProperty('spindleOnOffDelay') / 2); 1197 | else 1198 | // spindle on delay if needed 1199 | if (m && (isFirstSection() || isNewfile)) 1200 | onDwell( getProperty('spindleOnOffDelay') ); 1201 | } 1202 | 1203 | // wcs 1204 | if (insertToolCall) // force work offset when changing tool 1205 | { 1206 | currentWorkOffset = undefined; 1207 | } 1208 | 1209 | if (currentSection.workOffset != currentWorkOffset) 1210 | { 1211 | writeBlock(currentSection.wcs); 1212 | currentWorkOffset = currentSection.workOffset; 1213 | forceWorkPlane(); 1214 | } 1215 | 1216 | forceXYZ(); 1217 | 1218 | // position rotary axes for multi-axis and 3+2 operations 1219 | var abc = defineWorkPlane(currentSection, true); 1220 | 1221 | // set coolant after we have positioned at Z 1222 | setCoolant(tool.coolant); 1223 | 1224 | forceAny(); 1225 | 1226 | var initialPosition = getFramePosition(currentSection.getInitialPosition()); 1227 | if (!retracted && !insertToolCall) 1228 | { 1229 | if (getCurrentPosition().z < initialPosition.z) 1230 | { 1231 | writeBlock(gMotionModal.format(0), zOutput.format(initialPosition.z)); 1232 | } 1233 | } 1234 | 1235 | if (insertToolCall || !lengthCompensationActive || retracted || (!isFirstSection() && getPreviousSection().isMultiAxis())) 1236 | { 1237 | //if (debugMode) writeComment("1146"); 1238 | var lengthOffset = tool.lengthOffset; 1239 | //if (debugMode) writeComment("lengthoffset " + zOutput.format(lengthOffset)); 1240 | if (lengthOffset > numberOfToolSlots) 1241 | { 1242 | error(localize("Length offset out of range.")); 1243 | return; 1244 | } 1245 | 1246 | gMotionModal.reset(); 1247 | writeBlock(gPlaneModal.format(17)); 1248 | 1249 | // cancel compensation prior to enabling it, required when switching G43/G43.4 modes 1250 | disableLengthCompensation(false); 1251 | 1252 | if (!machineConfiguration.isHeadConfiguration()) 1253 | { 1254 | //if (debugMode) writeComment("1162 start"); 1255 | writeBlock( 1256 | gAbsIncModal.format(90), 1257 | gMotionModal.format(0), xOutput.format(initialPosition.x), yOutput.format(initialPosition.y) 1258 | ); 1259 | //writeBlock(gMotionModal.format(0), gFormat.format(getOffsetCode()), zOutput.format(initialPosition.z), hFormat.format(lengthOffset)); 1260 | //writeBlock(gMotionModal.format(0), gFormat.format(getOffsetCode()), zOutput.format(initialPosition.z), ' ; 1253'); 1261 | //if (debugMode) writeComment("1162 end"); 1262 | } 1263 | else 1264 | { 1265 | //if (debugMode) writeComment('1172'); 1266 | writeBlock( 1267 | gAbsIncModal.format(90), 1268 | gMotionModal.format(0), 1269 | gFormat.format(getOffsetCode()), xOutput.format(initialPosition.x), 1270 | yOutput.format(initialPosition.y), 1271 | zOutput.format(initialPosition.z), hFormat.format(lengthOffset), ' ; 1264'); 1272 | } 1273 | lengthCompensationActive = false; // DTS force false 1274 | } 1275 | else 1276 | { 1277 | if (debugMode) writeComment('DEBUG 1185'); 1278 | writeBlock( 1279 | gAbsIncModal.format(90), 1280 | gMotionModal.format(0), 1281 | xOutput.format(initialPosition.x), 1282 | yOutput.format(initialPosition.y) 1283 | ); 1284 | } 1285 | 1286 | //validate(lengthCompensationActive, "Length compensation is not active."); 1287 | } 1288 | 1289 | function onSectionEnd() 1290 | { 1291 | if (debugMode) writeComment("DEBUG onSectionEnd begin " + getCurrentSectionId() + 1) ; 1292 | writeBlock(gPlaneModal.format(17)); 1293 | if (!isLastSection() && (getNextSection().getTool().coolant != tool.coolant)) 1294 | { 1295 | setCoolant(COOLANT_OFF); 1296 | } 1297 | writeBlock(gFeedModeModal.format(94)); 1298 | 1299 | if (isRedirecting()) 1300 | { 1301 | if (isLastSection() || ( tool.number != getNextSection().getTool().number ) ) 1302 | { 1303 | writeln(""); 1304 | onCommand(COMMAND_STOP_SPINDLE); 1305 | if (debugMode) writeComment('DEBUG onsectionend calling onclose'); 1306 | onClose(); 1307 | closeRedirection(); 1308 | } 1309 | } 1310 | forceAny(); 1311 | if (debugMode) writeComment("DEBUG onSectionEnd end " + getCurrentSectionId() + 1) ; 1312 | } 1313 | 1314 | function onDwell(seconds) 1315 | { 1316 | if (seconds > 99999.999) 1317 | { 1318 | warning(localize("Dwelling time is out of range.")); 1319 | } 1320 | seconds = clamp(0.001, seconds, 99999.999); 1321 | writeBlock(gFormat.format(4), "P" + secFormat.format(seconds)); 1322 | } 1323 | 1324 | function onSpindleSpeed(spindleSpeed) 1325 | { 1326 | writeBlock(sOutput.format(spindleSpeed)); 1327 | gMotionModal.reset(); // force a G word after a spindle speed change to keep CONTROL happy 1328 | } 1329 | 1330 | function onCycle() 1331 | { 1332 | writeBlock(gPlaneModal.format(17)); 1333 | } 1334 | 1335 | // return formatted x,y,z,R for drill cycles 1336 | function getCommonCycle(x, y, z, r) 1337 | { 1338 | forceXYZ(); 1339 | return [xOutput.format(x), yOutput.format(y), 1340 | zOutput.format(z), 1341 | "R" + xyzFormat.format(r)]; 1342 | } 1343 | 1344 | // DTS - some of these not supported - turn off until CONTROL support cycle display 1345 | function onCyclePoint(x, y, z) 1346 | { 1347 | if (forceCyclesOff) 1348 | { 1349 | expandCyclePoint(x, y, z); 1350 | return; 1351 | } 1352 | var forward; 1353 | if (currentSection.isOptimizedForMachine()) 1354 | { 1355 | forward = machineConfiguration.getOptimizedDirection(currentSection.workPlane.forward, getCurrentDirection(), false, false); 1356 | } 1357 | else 1358 | { 1359 | forward = getRotation().forward; 1360 | } 1361 | // if not in XY plane then expand 1362 | if (!isSameDirection(forward, new Vector(0, 0, 1))) 1363 | { 1364 | expandCyclePoint(x, y, z); 1365 | return; 1366 | } 1367 | if (isFirstCyclePoint()) 1368 | { 1369 | repositionToCycleClearance(cycle, x, y, z); 1370 | 1371 | var F = cycle.feedrate; 1372 | var P = !cycle.dwell ? 0 : clamp(0.001, cycle.dwell, 99999.999); // in seconds 1373 | var Q = !cycle.incrementalDepth ? cycle.depth / 2 : clamp(0.01, cycle.incrementalDepth, cycle.depth); 1374 | 1375 | switch (cycleType) 1376 | { 1377 | case "drilling": 1378 | writeComment("drilling"); 1379 | writeBlock( 1380 | gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(81), 1381 | getCommonCycle(x, y, z, cycle.retract), 1382 | feedOutput.format(F) 1383 | ); 1384 | break; 1385 | case "counter-boring": 1386 | writeComment("counter-boring"); // also drill with dwell 1387 | if (P > 0) 1388 | { 1389 | writeBlock( 1390 | gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(82), 1391 | getCommonCycle(x, y, z, cycle.retract), 1392 | "P" + secFormat.format(P), // not optional 1393 | feedOutput.format(F) 1394 | ); 1395 | } 1396 | else 1397 | { 1398 | writeComment("no P given"); 1399 | writeBlock( 1400 | gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(82), 1401 | getCommonCycle(x, y, z, cycle.retract), 1402 | "P" + secFormat.format(0.1), // not optional 1403 | feedOutput.format(F) 1404 | ); 1405 | } 1406 | break; 1407 | case "chip-breaking": 1408 | writeComment("chipbreak"); 1409 | // expandCyclePoint(x, y, z); 1410 | writeBlock( 1411 | gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(73), 1412 | getCommonCycle(x, y, z, cycle.retract), 1413 | "Q" + xyzFormat.format(Q), 1414 | feedOutput.format(F) 1415 | ); 1416 | break; 1417 | case "deep-drilling": 1418 | writeComment("deep drill"); 1419 | if (P > 0) 1420 | { 1421 | expandCyclePoint(x, y, z); 1422 | } 1423 | else 1424 | { 1425 | writeBlock( 1426 | gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(83), 1427 | getCommonCycle(x, y, z, cycle.retract), 1428 | "Q" + xyzFormat.format(Q), 1429 | feedOutput.format(F) 1430 | ); 1431 | } 1432 | break; 1433 | case "tapping": 1434 | writeComment("tapping"); //todo unsupport 1435 | if (!F) 1436 | { 1437 | F = tool.getTappingFeedrate(); 1438 | } 1439 | writeBlock( 1440 | gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format((tool.type == TOOL_TAP_LEFT_HAND) ? 74 : 84), 1441 | getCommonCycle(x, y, z, cycle.retract), 1442 | feedOutput.format(F) 1443 | ); 1444 | break; 1445 | case "left-tapping": // todo unsupport 1446 | writeComment("left tapping"); 1447 | if (!F) 1448 | { 1449 | F = tool.getTappingFeedrate(); 1450 | } 1451 | writeBlock( 1452 | gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(74), 1453 | getCommonCycle(x, y, z, cycle.retract), 1454 | feedOutput.format(F) 1455 | ); 1456 | break; 1457 | case "right-tapping": 1458 | writeComment("right tapping"); //todo unsupport 1459 | if (!F) 1460 | { 1461 | F = tool.getTappingFeedrate(); 1462 | } 1463 | writeBlock( 1464 | gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(84), 1465 | getCommonCycle(x, y, z, cycle.retract), 1466 | feedOutput.format(F) 1467 | ); 1468 | break; 1469 | case "fine-boring": // not supported 1470 | expandCyclePoint(x, y, z); 1471 | break; 1472 | case "back-boring": // todo unsupport 1473 | writeComment("back boring"); 1474 | if (P > 0) 1475 | { 1476 | expandCyclePoint(x, y, z); 1477 | } 1478 | else 1479 | { 1480 | var I = cycle.shift * 1; 1481 | var J = cycle.shift * 0; 1482 | writeBlock( 1483 | gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(87), 1484 | getCommonCycle(x, y, z, cycle.retract), 1485 | "I" + xyzFormat.format(I), 1486 | "J" + xyzFormat.format(J), 1487 | "K" + xyzFormat.format(cycle.bottom - cycle.backBoreDistance), 1488 | feedOutput.format(F) 1489 | ); 1490 | } 1491 | break; 1492 | case "reaming": // todo unsupport 1493 | writeComment("reaming"); 1494 | if (feedFormat.getResultingValue(cycle.feedrate) != feedFormat.getResultingValue(cycle.retractFeedrate)) 1495 | { 1496 | expandCyclePoint(x, y, z); 1497 | break; 1498 | } 1499 | writeBlock( 1500 | gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(85), 1501 | getCommonCycle(x, y, z, cycle.retract), 1502 | feedOutput.format(F) 1503 | ); 1504 | break; 1505 | case "stop-boring": // todo unsupport 1506 | writeComment("stop boring"); 1507 | writeBlock( 1508 | gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(86), 1509 | getCommonCycle(x, y, z, cycle.retract), 1510 | feedOutput.format(F), 1511 | // conditional(P > 0, "P" + secFormat.format(P)), 1512 | "P" + secFormat.format(P) // not optional 1513 | ); 1514 | break; 1515 | case "manual-boring": // todo unsupport 1516 | writeComment("manual boring"); 1517 | writeBlock( 1518 | gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(88), 1519 | getCommonCycle(x, y, z, cycle.retract), 1520 | "P" + secFormat.format(P), // not optional 1521 | feedOutput.format(F) 1522 | ); 1523 | break; 1524 | case "boring": // todo unsupport 1525 | writeComment("boring"); 1526 | if (feedFormat.getResultingValue(cycle.feedrate) != feedFormat.getResultingValue(cycle.retractFeedrate)) 1527 | { 1528 | expandCyclePoint(x, y, z); 1529 | break; 1530 | } 1531 | if (P > 0) 1532 | { 1533 | writeBlock( 1534 | gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(89), 1535 | getCommonCycle(x, y, z, cycle.retract), 1536 | "P" + secFormat.format(P), // not optional 1537 | feedOutput.format(F) 1538 | ); 1539 | } 1540 | else 1541 | { 1542 | writeBlock( 1543 | gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(85), 1544 | getCommonCycle(x, y, z, cycle.retract), 1545 | feedOutput.format(F) 1546 | ); 1547 | } 1548 | break; 1549 | default: 1550 | expandCyclePoint(x, y, z); 1551 | } 1552 | } 1553 | else 1554 | { 1555 | if (cycleExpanded) 1556 | { 1557 | expandCyclePoint(x, y, z); 1558 | } 1559 | else 1560 | { 1561 | var _x = xOutput.format(x); 1562 | var _y = yOutput.format(y); 1563 | if (!_x && !_y) 1564 | { 1565 | xOutput.reset(); // at least one axis is required 1566 | _x = xOutput.format(x); 1567 | } 1568 | writeBlock(" ", _x, _y); 1569 | } 1570 | } 1571 | } 1572 | 1573 | function onCycleEnd() 1574 | { 1575 | if (!cycleExpanded) 1576 | { 1577 | writeBlock(gCycleModal.format(80)); 1578 | zOutput.reset(); 1579 | } 1580 | } 1581 | 1582 | var pendingRadiusCompensation = -1; 1583 | // DTS - no radius comp in grblHAL 1584 | function onRadiusCompensation() 1585 | { 1586 | //pendingRadiusCompensation = radiusCompensation; 1587 | pendingRadiusCompensation = -1; // always off - warn too? 1588 | if (radiusCompensation > 0) 1589 | error('Radius compensation not supported, set it to "in computer"'); 1590 | } 1591 | 1592 | // DTS - do we need rapid detect? 1593 | // probably, even if the user license cannot do multiaxis we must still do something for 3axis 1594 | function onRapid(_x, _y, _z) 1595 | { 1596 | haveRapid = true; 1597 | var x = xOutput.format(_x); 1598 | var y = yOutput.format(_y); 1599 | var z = zOutput.format(_z); 1600 | if (x || y || z) 1601 | { 1602 | if (pendingRadiusCompensation >= 0) 1603 | { 1604 | error(localize("Radius compensation mode cannot be changed at rapid traversal.")); 1605 | return; 1606 | } 1607 | writeBlock(gMotionModal.format(0), x, y, z); 1608 | feedOutput.reset(); 1609 | } 1610 | } 1611 | 1612 | function onLinear(_x, _y, _z, feed) 1613 | { 1614 | // at least one axis is required 1615 | if (haveRapid || (pendingRadiusCompensation >= 0) ) 1616 | { 1617 | // ensure that we end at desired position when compensation is turned off 1618 | // and always output X and Y after a rapid else arcs may go mad 1619 | xOutput.reset(); 1620 | yOutput.reset(); 1621 | } 1622 | var x = xOutput.format(_x); 1623 | var y = yOutput.format(_y); 1624 | var z = zOutput.format(_z); 1625 | var f = feedOutput.format(feed); 1626 | if (x || y || z) 1627 | { 1628 | linmove = 1; // have to have a default! 1629 | if (!haveRapid) // if z is changing 1630 | { 1631 | if (_z < retractHeight) // compare it to retractHeight, below that is G1, >= is G0 1632 | linmove = 1; 1633 | else 1634 | linmove = 0; 1635 | if (debugMode && (linmove == 0)) writeComment("DEBUG NOrapid " + _z + ' ' + retractHeight); 1636 | } 1637 | writeBlock(gMotionModal.format(linmove), x, y, z, f); 1638 | } 1639 | else 1640 | if (f) 1641 | { 1642 | if (getNextRecord().isMotion()) // try not to output feed without motion 1643 | { 1644 | if (debugMode) writeComment('DEBUG onlinear feedoutput reset') ; 1645 | feedOutput.reset(); // force feed on next line 1646 | } 1647 | else 1648 | { 1649 | if (debugMode) writeComment('DEBUG onLinear feedoutput') ; 1650 | writeBlock(gMotionModal.format(1), f); 1651 | } 1652 | } 1653 | } 1654 | 1655 | // DTS - might get tricky here if no rapids coming from engine 1656 | function onRapid5D(_x, _y, _z, _a, _b, _c) 1657 | { 1658 | haveRapid = true; 1659 | if (!currentSection.isOptimizedForMachine()) 1660 | { 1661 | error(localize("This post configuration has not been customized for 5-axis simultaneous toolpath.")); 1662 | return; 1663 | } 1664 | if (pendingRadiusCompensation >= 0) 1665 | { 1666 | error(localize("Radius compensation mode cannot be changed at rapid traversal.")); 1667 | return; 1668 | } 1669 | var x = xOutput.format(_x); 1670 | var y = yOutput.format(_y); 1671 | var z = zOutput.format(_z); 1672 | var a = aOutput.format(_a); 1673 | var b = bOutput.format(_b); 1674 | var c = cOutput.format(_c); 1675 | if (x || y || z || a || b || c) 1676 | { 1677 | writeBlock(gMotionModal.format(0), x, y, z, a, b, c); 1678 | feedOutput.reset(); 1679 | } 1680 | } 1681 | 1682 | function onLinear5D(_x, _y, _z, _a, _b, _c, feed, feedMode) 1683 | { 1684 | if (!currentSection.isOptimizedForMachine()) 1685 | { 1686 | error(localize("This post configuration has not been customized for 5-axis simultaneous toolpath.")); 1687 | return; 1688 | } 1689 | // at least one axis is required 1690 | if (pendingRadiusCompensation >= 0) 1691 | { 1692 | error(localize("Radius compensation cannot be activated/deactivated for 5-axis move.")); 1693 | return; 1694 | } 1695 | var x = xOutput.format(_x); 1696 | var y = yOutput.format(_y); 1697 | var z = zOutput.format(_z); 1698 | var a = aOutput.format(_a); 1699 | var b = bOutput.format(_b); 1700 | var c = cOutput.format(_c); 1701 | 1702 | // get feedrate number 1703 | if (feedMode == FEED_INVERSE_TIME) 1704 | { 1705 | feedOutput.reset(); 1706 | } 1707 | var fMode = feedMode == FEED_INVERSE_TIME ? 93 : 94; 1708 | var f = feedMode == FEED_INVERSE_TIME ? inverseTimeOutput.format(feed) : feedOutput.format(feed); 1709 | 1710 | if (x || y || z || a || b || c) 1711 | { 1712 | writeBlock(gFeedModeModal.format(fMode), gMotionModal.format(1), x, y, z, a, b, c, f); 1713 | } 1714 | else 1715 | if (f) 1716 | { 1717 | if (getNextRecord().isMotion()) // try not to output feed without motion 1718 | { 1719 | feedOutput.reset(); // force feed on next line 1720 | } 1721 | else 1722 | { 1723 | writeBlock(gFeedModeModal.format(fMode), gMotionModal.format(1), f); 1724 | } 1725 | } 1726 | } 1727 | 1728 | // this code was generated with the help of ChatGPT AI 1729 | // calculate the centers for the 2 circles passing through both points at the given radius 1730 | // if error then returns -9.9375 for all coordinates 1731 | // define points as var point1 = { x: 0, y: 0 }; 1732 | // returns an array of 2 of those things 1733 | function calculateCircleCenters(point1, point2, radius) 1734 | { 1735 | // Calculate the distance between the points 1736 | var distance = Math.sqrt( Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2) ); 1737 | if (distance > (radius * 2)) 1738 | { 1739 | //-9.9375 is perfectly stored by doubles and singles and will pass an equality test 1740 | center1X = center1Y = center2X = center2Y = -9.9375; 1741 | } 1742 | else 1743 | { 1744 | // Calculate the midpoint between the points 1745 | var midpointX = (point1.x + point2.x) / 2; 1746 | var midpointY = (point1.y + point2.y) / 2; 1747 | 1748 | // Calculate the angle between the line connecting the points and the x-axis 1749 | var angle = Math.atan2(point2.y - point1.y, point2.x - point1.x); 1750 | 1751 | // Calculate the distance from the midpoint to the center of each circle 1752 | var halfChordLength = Math.sqrt(Math.pow(radius, 2) - Math.pow(distance / 2, 2)); 1753 | 1754 | // Calculate the centers of the circles 1755 | var center1X = midpointX + halfChordLength * Math.cos(angle + Math.PI / 2); 1756 | var center1Y = midpointY + halfChordLength * Math.sin(angle + Math.PI / 2); 1757 | 1758 | var center2X = midpointX + halfChordLength * Math.cos(angle - Math.PI / 2); 1759 | var center2Y = midpointY + halfChordLength * Math.sin(angle - Math.PI / 2); 1760 | } 1761 | 1762 | // Return the centers of the circles as an array of objects 1763 | return [ 1764 | { x: center1X, y: center1Y }, 1765 | { x: center2X, y: center2Y } ]; 1766 | } 1767 | 1768 | // given the 2 points and existing center, find a new, more accurate center 1769 | // only works in x,y 1770 | // point parameters are Vectors 1771 | // returns a Vector point with the revised center values in x,y, ignore Z 1772 | function newCenter(p1, p2, oldcenter, radius) 1773 | { 1774 | // inputs are vectors, convert 1775 | var point1 = { x: p1.x, y: p1.y }; 1776 | var point2 = { x: p2.x, y: p2.y }; 1777 | 1778 | var newcenters = calculateCircleCenters(point1, point2, radius); 1779 | if ((newcenters[0].x == newcenters[1].x) && (newcenters[0].y == -9.9375)) 1780 | { 1781 | // error in calculation, distance between points > diameter 1782 | return oldcenter; 1783 | } 1784 | // now find the new center that is closest to the old center 1785 | //writeComment("nc1 " + newcenters[0].x + " " + newcenters[0].y); 1786 | nc1 = new Vector(newcenters[0].x, newcenters[0].y, 0); // note Z is not valid 1787 | //writeComment("nc2 " + newcenters[1].x + " " + newcenters[1].y); 1788 | nc2 = new Vector(newcenters[1].x, newcenters[1].y, 0); 1789 | d1 = Vector.diff(oldcenter, nc1).length; 1790 | d2 = Vector.diff(oldcenter, nc2).length; 1791 | if (d1 < d2) 1792 | return nc1; 1793 | else 1794 | return nc2; 1795 | } 1796 | 1797 | /* 1798 | helper for on Circular - calculates a new center for arcs with differing radii 1799 | returns the revised center vector 1800 | */ 1801 | function ReCenter(start, end, center, radius, cp) 1802 | { 1803 | var r1,r2,diff,pdiff; 1804 | 1805 | switch (cp) 1806 | { 1807 | case PLANE_XY: 1808 | writeComment('recenter XY'); 1809 | var nCenter = newCenter(start, end, center, radius ); 1810 | // writeComment("old center " + center.x + " , " + center.y); 1811 | // writeComment("new center " + nCenter.x + " , " + nCenter.y); 1812 | center.x = nCenter.x; 1813 | center.y = nCenter.y; 1814 | center.z = (start.z + end.z) / 2.0; 1815 | 1816 | r1 = Vector.diff(start, center).length; 1817 | r2 = Vector.diff(end, center).length; 1818 | if (r1 != r2) 1819 | { 1820 | diff = r1 - r2; 1821 | pdiff = Math.abs(diff / r1 * 100); 1822 | if (pdiff > 0.01) 1823 | { 1824 | if (debugMode) writeComment("DEBUG Recenter R1 " + r1 + " r2 " + r2 + " d " + (r1 - r2) + " pdoff " + pdiff ); 1825 | } 1826 | } 1827 | break; 1828 | case PLANE_ZX: 1829 | writeComment('recenter ZX'); 1830 | // generate fake x,y vectors 1831 | var st = new Vector( start.x, start.z, 0); 1832 | var ed = new Vector(end.x, end.z, 0) 1833 | var ct = new Vector(center.x, center.z, 0); 1834 | var nCenter = newCenter( st, ed, ct, radius); 1835 | // translate fake x,y values 1836 | center.x = nCenter.x; 1837 | center.z = nCenter.y; 1838 | r1 = Vector.diff(start, center).length; 1839 | r2 = Vector.diff(end, center).length; 1840 | if (r1 != r2) 1841 | { 1842 | diff = r1 - r2; 1843 | pdiff = Math.abs(diff / r1 * 100); 1844 | if (pdiff > 0.01) 1845 | { 1846 | if (debugMode) writeComment("DEBUG ZX R1 " + r1 + " r2 " + r2 + " d " + (r1 - r2) + " pdoff " + pdiff ); 1847 | } 1848 | } 1849 | break; 1850 | case PLANE_YZ: 1851 | writeComment('recenter YZ'); 1852 | var st = new Vector(start.z, start.y, 0); 1853 | var ed = new Vector(end.z, end.y, 0) 1854 | var ct = new Vector(center.z, center.y, 0); 1855 | var nCenter = newCenter(st, ed, ct, radius); 1856 | center.y = nCenter.y; 1857 | center.z = nCenter.x; 1858 | r1 = Vector.diff(start, center).length; 1859 | r2 = Vector.diff(end, center).length; 1860 | if (r1 != r2) 1861 | { 1862 | diff = r1 - r2; 1863 | pdiff = Math.abs(diff / r1 * 100); 1864 | if (pdiff > 0.01) 1865 | { 1866 | if (debugMode) writeComment("DEBUG YZ R1 " + r1 + " r2 " + r2 + " d " + (r1 - r2) + " pdoff " + pdiff ); 1867 | } 1868 | } 1869 | break; 1870 | } 1871 | return center; 1872 | } 1873 | 1874 | function onCircular(clockwise, cx, cy, cz, x, y, z, feed) 1875 | { 1876 | // one of X/Y and I/J are required 1877 | // DTS nix that, at least XY and IJ always required for grbl in XY plane 1878 | if (pendingRadiusCompensation >= 0) 1879 | { 1880 | error(localize("Radius compensation cannot be activated/deactivated for a circular move.")); 1881 | return; 1882 | } 1883 | 1884 | var start = getCurrentPosition(); 1885 | var center = new Vector(cx, cy, cz); 1886 | var end = new Vector(x, y, z); 1887 | var cp = getCircularPlane(); 1888 | //writeComment("cp " + cp); 1889 | 1890 | if (isFullCircle()) 1891 | { 1892 | writeComment("full circle"); 1893 | linearize(tolerance); 1894 | return; 1895 | } 1896 | 1897 | // first fix the center 'height' 1898 | // for an XY plane, fix Z to be between start.z and end.z 1899 | switch (cp) 1900 | { 1901 | case PLANE_XY: 1902 | center.z = (start.z + end.z) / 2.0; // doing this fixes most arc radius lengths 1903 | break; 1904 | case PLANE_YZ: 1905 | // fix X 1906 | center.x = (start.x + end.x) / 2.0; 1907 | break; 1908 | case PLANE_ZX: 1909 | // fix Y 1910 | center.y = (start.y + end.y) / 2.0; 1911 | break; 1912 | default: 1913 | writeComment("no plane"); 1914 | } 1915 | // check for differing radii 1916 | var r1 = Vector.diff(start, center).length; 1917 | var r2 = Vector.diff(end, center).length; 1918 | // if linearizing and this is small, don't bother to recenter 1919 | //if ( !(properties.linearizeSmallArcs && (r1 < toolRadius)) ) 1920 | 1921 | if ( (r1 < toolRadius) && (r1 != r2) ) // always recenter small arcs 1922 | { 1923 | var diff = r1 - r2; 1924 | var pdiff = Math.abs(diff / r1 * 100); 1925 | // if percentage difference too great 1926 | if (pdiff > 0.01) 1927 | { 1928 | //writeComment("recenter"); 1929 | // adjust center to make radii equal 1930 | if (debugMode) writeComment("DEBUG r1 " + r1 + " r2 " + r2 + " d " + (r1 - r2) + " pdoff " + pdiff ); 1931 | center = ReCenter(start, end, center, (r1 + r2) /2, cp); 1932 | } 1933 | } 1934 | 1935 | // DTS - arcs smaller than bitradius always have significant radius errors, so get radius and linearize them 1936 | // (because we cannot change minimumCircularRadius here) 1937 | // note that larger arcs still have radius errors, but they are a much smaller percentage of the radius 1938 | // todo - grblHAL yet to be tested thoroughly for arc limits 1939 | var rad = Vector.diff(start,center).length; 1940 | 1941 | if (rad < toPreciseUnit(2, MM)) 1942 | if (linearizeSmallArcs && (rad < toolRadius)) 1943 | { 1944 | debugMode = true; 1945 | if (debugMode) writeComment("DEBUG linearizing arc radius " + round(rad, 4) + " toolRadius " + round(toolRadius, 3) + ' plane=' + cp); 1946 | linearize(tolerance); 1947 | if (debugMode) writeComment("DEBUG done"); 1948 | debugMode = false; 1949 | return; 1950 | } 1951 | 1952 | switch (cp) 1953 | { 1954 | case PLANE_XY: 1955 | xOutput.reset(); // always have X and Y, Z will output if it changed 1956 | yOutput.reset(); 1957 | iOutput.reset(); // always have ijk as needed 1958 | jOutput.reset(); 1959 | writeBlock(gPlaneModal.format(17), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), iOutput.format(center.x - start.x), jOutput.format(center.y - start.y), feedOutput.format(feed)); 1960 | break; 1961 | case PLANE_ZX: 1962 | xOutput.reset(); // always have X and Z, Y will output if it changed 1963 | zOutput.reset(); 1964 | iOutput.reset(); // always have ijk as needed 1965 | kOutput.reset(); 1966 | writeBlock(gPlaneModal.format(18), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), iOutput.format(center.x - start.x), kOutput.format(center.z - start.z), feedOutput.format(feed)); 1967 | break; 1968 | case PLANE_YZ: 1969 | zOutput.reset(); // always have Z and Y, X will output if it changed 1970 | yOutput.reset(); 1971 | jOutput.reset(); 1972 | kOutput.reset(); 1973 | writeBlock(gPlaneModal.format(19), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), jOutput.format(center.y - start.y), kOutput.format(center.z - start.z), feedOutput.format(feed)); 1974 | break; 1975 | default: 1976 | linearize(tolerance); 1977 | } 1978 | } 1979 | 1980 | var currentCoolantMode = COOLANT_OFF; 1981 | var coolantOff = undefined; 1982 | var forceCoolant = false; 1983 | 1984 | function setCoolant(coolant) 1985 | { 1986 | if (debugMode) writeComment('DEBUG setCoolant ' + coolant); 1987 | var coolantCodes = getCoolantCodes(coolant); 1988 | if (Array.isArray(coolantCodes)) 1989 | { 1990 | if (singleLineCoolant) 1991 | { 1992 | writeBlock(coolantCodes.join(getWordSeparator())); 1993 | } 1994 | else 1995 | { 1996 | for (var c in coolantCodes) 1997 | { 1998 | writeBlock(coolantCodes[c]); 1999 | } 2000 | } 2001 | return undefined; 2002 | } 2003 | return coolantCodes; 2004 | } 2005 | 2006 | function getCoolantCodes(coolant) 2007 | { 2008 | var multipleCoolantBlocks = new Array(); // create a formatted array to be passed into the outputted line 2009 | if (!coolants) 2010 | { 2011 | error(localize("Coolants have not been defined.")); 2012 | } 2013 | if (tool.type == TOOL_PROBE) // avoid coolant output for probing 2014 | { 2015 | coolant = COOLANT_OFF; 2016 | } 2017 | if (coolant == currentCoolantMode && (!forceCoolant || coolant == COOLANT_OFF)) 2018 | { 2019 | return undefined; // coolant is already active 2020 | } 2021 | if ((coolant != COOLANT_OFF) && (currentCoolantMode != COOLANT_OFF) && (coolantOff != undefined) && !forceCoolant) 2022 | { 2023 | if (Array.isArray(coolantOff)) 2024 | { 2025 | for (var i in coolantOff) 2026 | { 2027 | multipleCoolantBlocks.push(coolantOff[i]); 2028 | } 2029 | } 2030 | else 2031 | { 2032 | multipleCoolantBlocks.push(coolantOff); 2033 | } 2034 | } 2035 | forceCoolant = false; 2036 | 2037 | var m; 2038 | var coolantCodes = {}; 2039 | for (var c in coolants) // find required coolant codes into the coolants array 2040 | { 2041 | if (coolants[c].id == coolant) 2042 | { 2043 | coolantCodes.on = coolants[c].on; 2044 | if (coolants[c].off != undefined) 2045 | { 2046 | coolantCodes.off = coolants[c].off; 2047 | break; 2048 | } 2049 | else 2050 | { 2051 | for (var i in coolants) 2052 | { 2053 | if (coolants[i].id == COOLANT_OFF) 2054 | { 2055 | coolantCodes.off = coolants[i].off; 2056 | break; 2057 | } 2058 | } 2059 | } 2060 | } 2061 | } 2062 | if (coolant == COOLANT_OFF) 2063 | { 2064 | m = !coolantOff ? coolantCodes.off : coolantOff; // use the default coolant off command when an 'off' value is not specified 2065 | } 2066 | else 2067 | { 2068 | coolantOff = coolantCodes.off; 2069 | m = coolantCodes.on; 2070 | } 2071 | 2072 | if (!m) 2073 | { 2074 | onUnsupportedCoolant(coolant); 2075 | m = 9; 2076 | } 2077 | else 2078 | { 2079 | if (Array.isArray(m)) 2080 | { 2081 | for (var i in m) 2082 | { 2083 | multipleCoolantBlocks.push(m[i]); 2084 | } 2085 | } 2086 | else 2087 | { 2088 | multipleCoolantBlocks.push(m); 2089 | } 2090 | currentCoolantMode = coolant; 2091 | for (var i in multipleCoolantBlocks) 2092 | { 2093 | if (typeof multipleCoolantBlocks[i] == "number") 2094 | { 2095 | multipleCoolantBlocks[i] = mFormat.format(multipleCoolantBlocks[i]); 2096 | } 2097 | } 2098 | return multipleCoolantBlocks; // return the single formatted coolant value 2099 | } 2100 | return undefined; 2101 | } 2102 | 2103 | var mapCommand = 2104 | { 2105 | COMMAND_END: 2, 2106 | COMMAND_SPINDLE_CLOCKWISE: 3, 2107 | COMMAND_SPINDLE_COUNTERCLOCKWISE: 4, 2108 | COMMAND_STOP_SPINDLE: 5, 2109 | COMMAND_ORIENTATE_SPINDLE: 19, // DTS - what is this? 2110 | COMMAND_LOAD_TOOL: 6 2111 | }; 2112 | 2113 | function onCommand(command) 2114 | { 2115 | //if (debugMode) writeComment('oncommand ' + command); 2116 | switch (command) 2117 | { 2118 | case COMMAND_STOP: 2119 | if (debugMode) writeComment('DEBUG oncommand stop' + command); 2120 | writeBlock(mFormat.format(0)); 2121 | forceSpindleSpeed = true; 2122 | forceCoolant = true; 2123 | return; 2124 | case COMMAND_OPTIONAL_STOP: 2125 | if (debugMode) writeComment('DEBUG oncommand optstop' + command); 2126 | writeBlock(mFormat.format(1)); 2127 | forceSpindleSpeed = true; 2128 | forceCoolant = true; 2129 | return; 2130 | case COMMAND_COOLANT_ON: 2131 | if (debugMode) writeComment('DEBUG oncommand coolon' + command); 2132 | setCoolant(COOLANT_FLOOD); 2133 | return; 2134 | case COMMAND_COOLANT_OFF: 2135 | if (debugMode) writeComment('DEBUG oncommand cooloff ' + command); 2136 | setCoolant(COOLANT_OFF); 2137 | return; 2138 | case COMMAND_START_SPINDLE: 2139 | if (debugMode) writeComment('DEBUG oncommand start ' + command); 2140 | onCommand(tool.clockwise ? COMMAND_SPINDLE_CLOCKWISE : COMMAND_SPINDLE_COUNTERCLOCKWISE); 2141 | return; 2142 | case COMMAND_LOCK_MULTI_AXIS: 2143 | //writeComment('oncommand lock ' + command); 2144 | return; 2145 | case COMMAND_UNLOCK_MULTI_AXIS: 2146 | //writeComment('oncommand unlock ' + command); 2147 | return; 2148 | case COMMAND_BREAK_CONTROL: 2149 | writeComment('oncommand break ' + command); 2150 | return; 2151 | case COMMAND_TOOL_MEASURE: 2152 | writeComment('oncommand toolmeas ' + command); 2153 | return; 2154 | } 2155 | 2156 | var stringId = getCommandStringId(command); 2157 | var mcode = mapCommand[stringId]; 2158 | //writeComment('oncommand mcode ' + mcode + " " + stringId); 2159 | if (mcode != undefined) 2160 | { 2161 | if (debugMode) writeComment('DEBUG oncommand mapped ' + stringId + "=" + mcode); 2162 | writeBlock(mFormat.format(mcode)); 2163 | } 2164 | else 2165 | { 2166 | onUnsupportedCommand(command); 2167 | } 2168 | } 2169 | 2170 | 2171 | /** Output block to do safe retract and/or move to home position. */ 2172 | function writeRetract() 2173 | { 2174 | if (debugMode) writeComment('DEBUG writeRetract start'); 2175 | var words = []; // store all retracted axes in an array 2176 | var retractAxes = new Array(false, false, false); 2177 | var method = getProperty("safePositionMethod"); 2178 | if (method == "clearanceHeight") 2179 | { 2180 | if (!is3D()) 2181 | { 2182 | error(localize("Safe retract option 'Clearance Height' is only supported when all operations are along the setup Z-axis.")); 2183 | return; 2184 | } 2185 | } 2186 | validate(arguments.length != 0, "No axis specified for writeRetract()."); 2187 | 2188 | for (i in arguments) 2189 | { 2190 | //writeComment("argument " + i + " " + arguments[i]) ; 2191 | retractAxes[arguments[i]] = true; 2192 | } 2193 | if ((retractAxes[0] || retractAxes[1]) && !retracted) // retract Z first before moving to X/Y home 2194 | { 2195 | error(localize("Retracting in X/Y is not possible without being retracted in Z.")); 2196 | return; 2197 | } 2198 | // special conditions 2199 | /* 2200 | if (retractAxes[2]) { // Z doesn't use G53 2201 | method = "G28"; 2202 | } 2203 | */ 2204 | 2205 | // define home positions 2206 | var _xHome; 2207 | var _yHome; 2208 | var _zHome; 2209 | if (method == "G28") // DTS - want G53 retracts 2210 | { 2211 | _xHome = toPreciseUnit(0, MM); 2212 | _yHome = toPreciseUnit(0, MM); 2213 | _zHome = toPreciseUnit(0, MM); 2214 | } 2215 | else 2216 | { 2217 | if (getProperty("gotoMCSatend", false)) 2218 | { 2219 | _xHome = toPreciseUnit(getProperty('machineHomeX'), MM); 2220 | _yHome = toPreciseUnit(getProperty('machineHomeY'), MM); 2221 | } 2222 | else 2223 | { 2224 | _xHome = machineConfiguration.hasHomePositionX() ? machineConfiguration.getHomePositionX() : toPreciseUnit(0, MM); 2225 | _yHome = machineConfiguration.hasHomePositionY() ? machineConfiguration.getHomePositionY() : toPreciseUnit(0, MM); 2226 | } 2227 | if (method == "clearanceHeight") 2228 | { 2229 | if (debugMode) writeComment('DEBUG Retract to initial.z'); 2230 | var section = getSection(0); // what is the section-object for this operation 2231 | var initialPosition = getFramePosition(section.getInitialPosition()); 2232 | _zHome = initialPosition.z; 2233 | } 2234 | else 2235 | _zHome = machineConfiguration.getRetractPlane() != 0 ? machineConfiguration.getRetractPlane() : toPreciseUnit(getProperty('machineHomeZ'), MM); 2236 | } 2237 | 2238 | for (var i = 0; i < arguments.length; ++i) 2239 | { 2240 | switch (arguments[i]) 2241 | { 2242 | case X: 2243 | words.push("X" + xyzFormat.format(_xHome)); 2244 | xOutput.reset(); 2245 | break; 2246 | case Y: 2247 | words.push("Y" + xyzFormat.format(_yHome)); 2248 | yOutput.reset(); 2249 | break; 2250 | case Z: 2251 | words.push("Z" + xyzFormat.format(_zHome)); 2252 | zOutput.reset(); 2253 | retracted = true; 2254 | break; 2255 | default: 2256 | error(localize("Unsupported axis specified for writeRetract().")); 2257 | return; 2258 | } 2259 | } 2260 | if (words.length > 0) 2261 | { 2262 | switch (method) 2263 | { 2264 | case "G28": 2265 | gMotionModal.reset(); 2266 | gAbsIncModal.reset(); 2267 | writeBlock(gFormat.format(28), gAbsIncModal.format(91), words); 2268 | writeBlock(gAbsIncModal.format(90)); 2269 | break; 2270 | case "G53": 2271 | gMotionModal.reset(); 2272 | if (getProperty('gotoMCSatend')) 2273 | writeBlock(gAbsIncModal.format(90), gFormat.format(53), gMotionModal.format(0), words); 2274 | else 2275 | { 2276 | if (retractAxes[2]) //todo should probably check that x and y are absent 2277 | { 2278 | writeln("(This relies on homing, see https://openbuilds.com/search/127200199/?q=G53+fusion )"); 2279 | writeBlock(gAbsIncModal.format(90), gFormat.format(53), gMotionModal.format(0), words); 2280 | } 2281 | else 2282 | writeBlock(gAbsIncModal.format(90), gMotionModal.format(0), words); 2283 | } 2284 | break; 2285 | case "clearanceHeight": 2286 | writeBlock(gAbsIncModal.format(90), gMotionModal.format(0), words); 2287 | break; 2288 | default: 2289 | error(localize("Unsupported safe position method.")); 2290 | return; 2291 | } 2292 | } 2293 | if (debugMode) writeComment('DEBUG writeRetract end'); 2294 | } 2295 | 2296 | // Start of onRewindMachine logic 2297 | /** Allow user to override the onRewind logic. */ 2298 | function onRewindMachineEntry(_a, _b, _c) 2299 | { 2300 | return false; 2301 | } 2302 | 2303 | /** Retract to safe position before indexing rotaries. */ 2304 | function onMoveToSafeRetractPosition() 2305 | { 2306 | writeRetract(Z); // retract to home position 2307 | // cancel TCP so that tool doesn't follow rotaries 2308 | if (currentSection.isMultiAxis() && operationSupportsTCP) 2309 | { 2310 | disableLengthCompensation(false); 2311 | } 2312 | // DTS - may need a property for this so user can select 2313 | if (false) // enable to move to safe position in X & Y 2314 | { 2315 | writeRetract(X, Y); 2316 | } 2317 | } 2318 | 2319 | /** Rotate axes to new position above reentry position */ 2320 | function onRotateAxes(_x, _y, _z, _a, _b, _c) 2321 | { 2322 | // position rotary axes 2323 | xOutput.disable(); 2324 | yOutput.disable(); 2325 | zOutput.disable(); 2326 | invokeOnRapid5D(_x, _y, _z, _a, _b, _c); 2327 | setCurrentABC(new Vector(_a, _b, _c)); 2328 | xOutput.enable(); 2329 | yOutput.enable(); 2330 | zOutput.enable(); 2331 | } 2332 | 2333 | /** Return from safe position after indexing rotaries. */ 2334 | function onReturnFromSafeRetractPosition(_x, _y, _z) 2335 | { 2336 | // reinstate TCP / tool length compensation 2337 | if (!lengthCompensationActive) 2338 | { 2339 | writeComment('DEBUG tool offset 2334'); 2340 | writeBlock(gFormat.format(getOffsetCode()), hFormat.format(tool.lengthOffset), ' ; 2331'); 2341 | lengthCompensationActive = true; 2342 | } 2343 | 2344 | // position in XY 2345 | forceXYZ(); 2346 | xOutput.reset(); 2347 | yOutput.reset(); 2348 | zOutput.disable(); 2349 | invokeOnRapid(_x, _y, _z); 2350 | 2351 | // position in Z 2352 | zOutput.enable(); 2353 | invokeOnRapid(_x, _y, _z); 2354 | } 2355 | // End of onRewindMachine logic 2356 | 2357 | function getOffsetCode() 2358 | { 2359 | //var offsetCode = 43.1; 2360 | var offsetCode = 43.1; 2361 | /* DTS grblHAL has no offset code 2362 | if (currentSection.isMultiAxis() || (!useMultiAxisFeatures && !currentSection.isZOriented())) 2363 | { 2364 | if (machineConfiguration.isMultiAxisConfiguration() && operationSupportsTCP) 2365 | { 2366 | offsetCode = 43.4; 2367 | } 2368 | else if (!machineConfiguration.isMultiAxisConfiguration()) 2369 | { 2370 | offsetCode = 43.5; 2371 | } 2372 | } 2373 | */ 2374 | return offsetCode; 2375 | } 2376 | 2377 | function onClose() 2378 | { 2379 | if (debugMode) writeComment('DEBUG onclose'); 2380 | setCoolant(COOLANT_OFF); 2381 | 2382 | writeRetract(Z); 2383 | disableLengthCompensation(true); 2384 | 2385 | setWorkPlane(new Vector(0, 0, 0)); // reset working plane 2386 | 2387 | writeRetract(X, Y); 2388 | 2389 | onImpliedCommand(COMMAND_END); 2390 | onCommand(COMMAND_STOP_SPINDLE); 2391 | writeBlock(mFormat.format(30)); // stop program, spindle stop, coolant off 2392 | if (debugMode) writeComment('DEBUG onclose end'); 2393 | } 2394 | 2395 | function writeHeader(secID) 2396 | { 2397 | if (debugMode) writeComment("DEBUG Header start " + secID + 1); 2398 | 2399 | numberOfSections = getNumberOfSections(); 2400 | 2401 | var productName = getProduct(); 2402 | writeComment("Made in : " + productName); 2403 | writeComment("G-Code optimized for " + machineControl + " controller"); 2404 | writeComment(description); 2405 | cpsname = FileSystem.getFilename(getConfigurationPath()); 2406 | writeComment("Post-Processor : " + cpsname + " " + obversion); 2407 | var unitstr = (unit == MM) ? 'mm' : 'inch'; 2408 | writeComment("Units = " + unitstr); 2409 | if (isJet()) 2410 | { 2411 | error("laser and plasma not implemented in this post") 2412 | //writeComment("Laser UseZ = " + properties.UseZ); 2413 | //writeComment("Laser UsePierce = " + properties.UsePierce); 2414 | } 2415 | 2416 | if (allowedCircularPlanes == 1) 2417 | { 2418 | writeln(""); 2419 | writeComment("Arcs are limited to the XY plane: if you want vertical arcs then edit allowedCircularPlanes in the CPS file"); 2420 | } 2421 | else 2422 | { 2423 | writeln(""); 2424 | writeComment("Arcs can occur on XY,YZ,ZX planes: CONTROL may not display them correctly but they will cut correctly"); 2425 | } 2426 | 2427 | writeln(""); 2428 | if (hasGlobalParameter("document-path")) 2429 | { 2430 | var path = getGlobalParameter("document-path"); 2431 | if (path) 2432 | { 2433 | writeComment("Drawing name : " + path); 2434 | } 2435 | } 2436 | 2437 | if (programName) 2438 | { 2439 | writeComment("Program Name : " + programName); 2440 | } 2441 | if (programComment) 2442 | { 2443 | writeComment("Program Comments : " + programComment); 2444 | } 2445 | writeln(""); 2446 | 2447 | if (!getProperty("useToolChange") && filesToGenerate > 1) 2448 | { 2449 | //if (debugMode) writeComment("not useToolChange 2168"); 2450 | writeComment(numberOfSections + " Operation" + ((numberOfSections == 1) ? "" : "s") + " in " + filesToGenerate + " files."); 2451 | writeComment("File List:"); 2452 | //writeComment(" " + FileSystem.getFilename(getOutputPath())); 2453 | for (var i = 0; i < filesToGenerate; ++i) 2454 | { 2455 | //filenamePath = FileSystem.replaceExtension(getOutputPath(), fileIndexFormat.format(i + 1) + "of" + filesToGenerate + "." + extension); 2456 | //filename = FileSystem.getFilename(filenamePath); 2457 | filename = makeFileName(i + 1); 2458 | writeComment(" " + filename); 2459 | } 2460 | writeln(""); 2461 | writeComment("This is file: " + fileSequenceNumber + " of " + filesToGenerate); 2462 | writeln(""); 2463 | writeComment("This file contains the following operations: "); 2464 | } 2465 | else 2466 | { 2467 | if (debugMode) writeComment("DEBUG generate single with toolchanges 2465"); 2468 | writeComment(numberOfSections + " Operation" + ((numberOfSections == 1) ? "" : "s") + " : in 1 file"); 2469 | } 2470 | 2471 | for (var i = secID; i < numberOfSections; ++i) 2472 | { 2473 | var section = getSection(i); 2474 | var tool = section.getTool(); 2475 | var rpm = section.getMaximumSpindleSpeed(); 2476 | 2477 | if (section.hasParameter("operation-comment")) 2478 | { 2479 | writeComment((i + 1) + " : " + section.getParameter("operation-comment")); 2480 | var op = section.getParameter("operation-comment") 2481 | } 2482 | else 2483 | { 2484 | writeComment(i + 1); 2485 | var op = i + 1; 2486 | } 2487 | if (section.workOffset > 0) 2488 | { 2489 | writeComment(" Work Coordinate System : G" + (section.workOffset + 53)); 2490 | } 2491 | writeComment(" Tool #" + tool.number + ": " + toTitleCase(getToolTypeName(tool.type)) + " " + tool.numberOfFlutes + " Flutes, Diam = " + xyzFormat.format(tool.diameter) + unitstr + ", Len = " + tool.fluteLength.toFixed(2) + unitstr); 2492 | if (getProperty("routerType") != "other") 2493 | { 2494 | writeComment(" Spindle : RPM = " + round(rpm, 0) + ", set router dial to " + rpm2dial(rpm, op) + " for " + getProperty('routerType')); 2495 | } 2496 | else 2497 | { 2498 | writeComment(" Spindle : RPM = " + round(rpm, 0)); 2499 | } 2500 | 2501 | var machineTimeInSeconds = section.getCycleTime(); 2502 | var machineTimeHours = Math.floor(machineTimeInSeconds / 3600); 2503 | machineTimeInSeconds = machineTimeInSeconds % 3600; 2504 | var machineTimeMinutes = Math.floor(machineTimeInSeconds / 60); 2505 | var machineTimeSeconds = Math.floor(machineTimeInSeconds % 60); 2506 | var machineTimeText = " Machining time : "; 2507 | if (machineTimeHours > 0) 2508 | { 2509 | machineTimeText = machineTimeText + machineTimeHours + " hours " + machineTimeMinutes + " min "; 2510 | } 2511 | else 2512 | if (machineTimeMinutes > 0) 2513 | { 2514 | machineTimeText = machineTimeText + machineTimeMinutes + " min "; 2515 | } 2516 | machineTimeText = machineTimeText + machineTimeSeconds + " sec"; 2517 | writeComment(machineTimeText); 2518 | 2519 | if (!getProperty("useToolChange") && (i + 1 < numberOfSections)) 2520 | { 2521 | if (tool.number != getSection(i + 1).getTool().number) 2522 | { 2523 | writeln(""); 2524 | writeComment("Remaining operations located in additional files."); 2525 | break; 2526 | } 2527 | } 2528 | } 2529 | writeln(""); 2530 | 2531 | gAbsIncModal.reset(); 2532 | gFeedModeModal.reset(); 2533 | gPlaneModal.reset(); 2534 | writeBlock(gAbsIncModal.format(90), gFeedModeModal.format(94), gPlaneModal.format(17)); 2535 | gUnitModal.reset(); // always output this 2536 | switch (unit) 2537 | { 2538 | case IN: 2539 | writeBlock(gUnitModal.format(20)); 2540 | break; 2541 | case MM: 2542 | writeBlock(gUnitModal.format(21)); 2543 | break; 2544 | } 2545 | writeRetract(Z); 2546 | if (debugMode) writeComment("DEBUG Header end"); 2547 | writeln(""); 2548 | } 2549 | 2550 | /** 2551 | check for duplicate tool numbers 2552 | sets filesToGenerate 2553 | @returns nothing 2554 | */ 2555 | function checkforDuplicatetools() 2556 | { 2557 | filesToGenerate = 1; 2558 | for (var i = 0; i < getNumberOfSections(); ++i) 2559 | { 2560 | var sectioni = getSection(i); 2561 | var tooli = sectioni.getTool(); 2562 | if (i < (getNumberOfSections() - 1) && (tooli.number != getSection(i + 1).getTool().number)) 2563 | { 2564 | filesToGenerate++; 2565 | } 2566 | for (var j = i + 1; j < getNumberOfSections(); ++j) 2567 | { 2568 | var sectionj = getSection(j); 2569 | var toolj = sectionj.getTool(); 2570 | if (tooli.number == toolj.number) 2571 | { 2572 | if (xyzFormat.areDifferent(tooli.diameter, toolj.diameter) || 2573 | xyzFormat.areDifferent(tooli.cornerRadius, toolj.cornerRadius) || 2574 | abcFormat.areDifferent(tooli.taperAngle, toolj.taperAngle) || 2575 | (tooli.numberOfFlutes != toolj.numberOfFlutes)) 2576 | { 2577 | error( 2578 | subst( 2579 | localize("Using the same tool number for different cutter geometry for operation '%1' and '%2'."), 2580 | sectioni.hasParameter("operation-comment") ? sectioni.getParameter("operation-comment") : ("#" + (i + 1)), 2581 | sectionj.hasParameter("operation-comment") ? sectionj.getParameter("operation-comment") : ("#" + (j + 1)) 2582 | ) 2583 | ); 2584 | return; 2585 | } 2586 | } 2587 | // else 2588 | // { 2589 | // if (properties.generateMultiple == false) 2590 | // { 2591 | // multipleToolError = true; 2592 | // } 2593 | // } 2594 | } 2595 | } 2596 | if (getProperty('useToolChange')) 2597 | filesToGenerate = 1; 2598 | if (debugMode) 2599 | writeComment("DEBUG files to Generate = " + filesToGenerate); 2600 | } 2601 | 2602 | function rpm2dial(rpm, op) 2603 | { 2604 | // translates an RPM for the spindle into a dial value, eg. for the Makita RT0700 and Dewalt 611 routers 2605 | // additionally, check that spindle rpm is between minimum and maximum of what our spindle can do 2606 | // array which maps spindle speeds to router dial settings, 2607 | // according to Makita RT0700 Manual : 1=10000, 2=12000, 3=17000, 4=22000, 5=27000, 6=30000 2608 | // according to Dewalt 611 Manual : 1=16000, 2=18200, 3=20400, 4=22600, 5=24800, 6=27000 2609 | var routerType = getProperty("routerType"); 2610 | if (routerType == "Dewalt") 2611 | { 2612 | var speeds = [0, 16000, 18200, 20400, 22600, 24800, 27000]; 2613 | } 2614 | else 2615 | if (routerType == "Router11") 2616 | { 2617 | var speeds = [0, 10000, 14000, 18000, 23000, 27000, 32000]; 2618 | } 2619 | else 2620 | { 2621 | // this is Makita R0701 2622 | var speeds = [0, 10000, 12000, 17000, 22000, 27000, 30000]; 2623 | } 2624 | if (rpm < speeds[1]) 2625 | { 2626 | var msg = "Warning " + rpm + " rpm is below minimum spindle RPM of " + speeds[1] + " rpm in the " + op + " operation."; 2627 | warning(msg); 2628 | writeComment(msg); 2629 | return 1; 2630 | } 2631 | 2632 | if (rpm > speeds[speeds.length - 1]) 2633 | { 2634 | var alert = "Warning " + rpm + " rpm is above maximum spindle RPM of " + speeds[speeds.length - 1] + " rpm in the " + op + " operation."; 2635 | warning(alert); 2636 | writeComment(alert); 2637 | return (speeds.length - 1); 2638 | } 2639 | 2640 | var i; 2641 | for (i = 1; i < (speeds.length - 1); i++) 2642 | { 2643 | if ((rpm >= speeds[i]) && (rpm <= speeds[i + 1])) 2644 | { 2645 | return (((rpm - speeds[i]) / (speeds[i + 1] - speeds[i])) + i).toFixed(1); 2646 | } 2647 | } 2648 | 2649 | error("Fatal Error calculating router speed dial."); 2650 | return 0; 2651 | } 2652 | 2653 | function toTitleCase(str) 2654 | { 2655 | // function to reformat a string to 'title case' 2656 | return str.replace( /\w\S*/g, function (txt) 2657 | {// /\w\S*/g after an astyle run, fix this format 2658 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); 2659 | }); 2660 | } 2661 | 2662 | function round(num, digits) 2663 | { 2664 | return toFixedNumber(num, digits, 10) 2665 | } 2666 | 2667 | function toFixedNumber(num, digits, base) 2668 | { 2669 | var pow = Math.pow(base || 10, digits); // cleverness found on web 2670 | return Math.round(num * pow) / pow; 2671 | } 2672 | 2673 | function onTerminate() 2674 | { 2675 | // If we are generating multiple files, copy first file to add # of # 2676 | // Then remove first file and recreate with file list - sharmstr 2677 | if (filesToGenerate > 1) 2678 | { 2679 | //if (isRedirecting()) 2680 | // closeRedirection(); 2681 | var outputPath = getOutputPath(); 2682 | var outputFolder = FileSystem.getFolderPath(getOutputPath()); 2683 | var programFilename = FileSystem.getFilename(outputPath); 2684 | var destfile = makeFileName(1); 2685 | FileSystem.copyFile(outputPath, destfile ); 2686 | FileSystem.remove(outputPath); 2687 | var file = new TextFile(outputFolder + "\\" + programFilename, true, "ansi"); 2688 | file.writeln("The following gcode files were created: "); 2689 | for (var i = 0; i < filesToGenerate; ++i) 2690 | { 2691 | destfile = makeFileName(i + 1); 2692 | file.writeln(destfile); 2693 | } 2694 | file.close(); 2695 | } 2696 | } 2697 | 2698 | /** 2699 | make a numbered filename 2700 | @param index the number of the file, from 1 2701 | */ 2702 | function makeFileName(index) 2703 | { 2704 | var fullname = getOutputPath(); 2705 | debug(fullname); 2706 | fullname = fullname.replace(' ', '_'); 2707 | var filenamePath = FileSystem.replaceExtension(fullname, fileIndexFormat.format(index) + "of" + filesToGenerate + "." + extension); 2708 | var filename = FileSystem.getFilename(filenamePath); 2709 | debug(filename); 2710 | return filenamePath; 2711 | } 2712 | -------------------------------------------------------------------------------- /README-plasma.md: -------------------------------------------------------------------------------- 1 | # How to use torch height probing with plasma cutting in Fusion360 2 | 3 | ## Things to do in Fusion360 4 | * Select a plasma tool and adjust the kerf width to suite your machine. 5 | * Make sure you set the CutHeight, PierceHeight and PierceDelay for the tool to match your plasma cutter. 6 | * Make sure that the stock is the same thickness as the model, make sure no stock is added on top of the model. 7 | * On all operations select either: 8 | *. Top Height as 'Stock Top' and enter the cutting head height for normal cutting (like 0.8mm). 9 | or 10 | *. Select Top Height as 'Stock Top' and set to *0* and then the tools *cutHeight* will be used for cutting. 11 | * Make sure that the tools *pierceHeight* is greater than the cutting height (like 1.5mm) (topheight plus offset or tool.cutHeight). 12 | * Under Passes | Compensation Type select 'In computer'. 13 | 14 | ## Things to do in the post options: 15 | * Set 'Use Z touchoff probe routine' to Yes 16 | * Set 'Plasma touch probe offset' to the difference between where the torch tip (the probe) touches the material and where the probe triggers. 17 | * This is always in millimeters. 18 | * So if your probe triggers 5.3mm after the probe touches the material, enter 5.3, (always a positive number). 19 | * Set 'Spindle on/off/ delay' to the desired Pierce delay in seconds or to Zero, if it is zero then the *tools* PierceDelay will be used. 20 | 21 | ## Things you can adjust by editing the post: 22 | * Open the .cps file in Notepad 23 | * Search for 'USER ADJUST' 24 | * You can change the probe distance and probe feedrate to suite your machine. 25 | * Feedrate cannot be lower than 50mm/min, this is a GRBL internal limit. 26 | * Note that changing the probe feedrate will change the point at which the probe triggers, so once you have figured out the probe offset 27 | you should NOT change the probe feedrate. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenBuilds Fusion360 Postprocessor 2 | 3 | Creates .gcode files optimized for GRBL/grblHAL based Openbuilds-style machines. 4 | Supports router, laser and plasma operations. 5 | 6 | V1.0.43 7 | 1. Plasma: if tool setting for pierceTime is set AND spindleonoffdelay is 0 then tool.pierceTime will be used. 8 | 1. Plasma: if tool setting for PierceHeight is set and pierceHeightoverride is false then tool value will be used. 9 | 1. Plasma: if tool setting for cutHeight is set and topHeight is 0 then tool.cutHeight will be used. 10 | DO read the plasma [instructions](https://github.com/OpenBuilds/OpenBuilds-Fusion360-Postprocessor/blob/master/README-plasma.md) ! 11 | 1. Fix failure to convert some properties to float values by using parseFloat, seems to be needed due to recent upgrades to Fusion360. 12 | 13 | V1.0.42 14 | 1. postprocessor.alert() method has disappeared - replaced with warning(msg) and writeComment(msg). 15 | 1. moved more stuff into OB. and SPL. to keep it private. 16 | 17 | V1.0.41 18 | 1. fixes namespace collision with 'power' variable that is now a readonly property of the postprocessor, affects plasma cutting. 19 | 20 | V1.0.40 21 | 1. force G0 position after plasma probe 22 | 1. fix plasma linearization of small arcs to avoid GRBL bug in arc after probe 23 | 1. fix pierceClearance and pierceHeight 24 | 1. fix plasma kerfWidth to toolRadius calculation 25 | 26 | V1.0.39 27 | 1. fix missing drill cycles 28 | 29 | V1.0.38 and V0.0.2_beta 30 | 1. Main post : Simple probing, each axis on its own, and XY corner, for BB4x with 3D probe. 31 | 1. Main post : machine simulation enabled. 32 | 1. X32 4th axis beta post: machine simulation enabled. 33 | 34 | V1.0.37 35 | 1. Tape splitting - allows setting a line count after which the gcode is split into a new file, see option 36 | _Split on line count (0 for none)_ 37 | in the Toolchange section of the post options. 38 | (It will also split on toolchanges if both options are selected) 39 | 40 | V1.0.36 41 | 1. code to recenter arcs with bad radii - this enables use of vertical arcs in lead-in/lead-out moves (you must also enable verticla arcs in the post). 42 | 43 | V1.0.35 44 | 1. plasma pierce height override, spindle speed change always with an M3, version number display 45 | 46 | V1.0.34 47 | 1. move coolant code to the spindle control line to help with restarts in OpenBuildsCONTROL 48 | 49 | V1.0.32 50 | 1. fix long comments that were getting extra brackets 51 | 52 | V1.0.31 53 | 1. improved laser and plasma paths, esp when 'stay down' is selected 54 | 1. laser pierce delay option when through cutting is selected 55 | 1. Select 'LASER: use Z motions at start and end' to have full Z movement with laser and plasma cuts 56 | 57 | V1.0.25 supports plasma torch touchoff probing. 58 | * Read the [instructions](https://github.com/OpenBuilds/OpenBuilds-Fusion360-Postprocessor/blob/master/README-plasma.md) 59 | 60 | V1.0.21 now supports plasma cutting 61 | 62 | V1.0.20 supports the Personal license restrictions and ultra long comments 63 | 64 | V1.0.18 now includes laser operations. 65 | 1. Laser mode supports lasers with and without Z motions. 66 | 1. It is left to the operator to correctly set GRBL parameter $32 as needed on a machine that combines a router and laser head. 67 | 1. The laser is regarded as an extra tool so when posting multiple operations the 68 | router code and laser code will be in seperate output .gcode files 69 | (exactly as for multiple tool outputs, each tool in its own file). 70 | 1. Laser power is scaled between 0 and 1000 (GRBL spindle RPM defaults). 71 | You can edit this post to cater for non-default settings. Refer to the 'calcPower' function. 72 | 73 | ### Credits ### 74 | 75 | 1. @swarfer David the Swarfer (lead maintainer). 76 | 1. @sharmstr - multifile output. 77 | 1. @Strooom - Initial work. 78 | 1. @AutoDesk - for the example posts and excellent Fusion360 software. 79 | --------------------------------------------------------------------------------