/* https://github.com/guffy1234/mpcnc_posts_processor MPCNC posts processor for milling and laser/plasma cutting. */ description = "MPCNC Milling/Laser - Marlin 2.0, Grbl 1.1, RepRap"; vendor = "flyfisher604"; vendorUrl = "https://github.com/flyfisher604/mpcnc_post_processor"; // Internal properties certificationLevel = 2; extension = "gcode"; setCodePage("ascii"); capabilities = CAPABILITY_MILLING | CAPABILITY_JET; machineMode = undefined; //TYPE_MILLING, TYPE_JET var eFirmware = { MARLIN: 0, GRBL: 1, REPRAP: 2, prop: { 0: {name: "Marlin 2.x", value: 0}, 1: {name: "Grbl 1.1", value: 1}, 2: {name: "RepRap", value: 2} } }; var fw = eFirmware.MARLIN; var eComment = { Off: 0, Important: 1, Info: 2, Debug: 3, prop: { 0: {name: "Off", value: 0}, 1: {name: "Important", value: 1}, 2: {name: "Info", value: 2}, 3: {name: "Debug", value: 3} } }; var eCoolant = { Off: 0, Flood: 1, Mist: 2, ThroughTool: 3, Air: 4, AirThroughTool: 5, Suction: 6, FloodMist: 7, FloodThroughTool: 8, prop: { 0: {name: "Off", value: 0}, 1: {name: "Flood", value: 1}, 2: {name: "Mist", value: 2}, 3: {name: "ThroughTool", value: 3}, 4: {name: "Air", value: 4}, 5: {name: "AirThroughTool", value: 5}, 6: {name: "Suction", value: 6}, 7: {name: "Flood and Mist", value: 7}, 8: {name: "Flood and ThroughTool", value: 8}, } }; // user-defined properties properties = { job0_SelectedFirmware : fw, // Firmware to use in special cases job1_SetOriginOnStart: true, // Set current position as 0,0,0 on start (G92) job2_ManualSpindlePowerControl: true, // Spindle motor is controlled by manual switch job3_CommentLevel: eComment.Info, // The level of comments included job4_UseArcs: true, // Produce G2/G3 for arcs job5_SequenceNumbers: false, // show sequence numbers job6_SequenceNumberStart: 10, // first sequence number job7_SequenceNumberIncrement: 1, // increment for sequence numbers job8_SeparateWordsWithSpace: true, // specifies that the words should be separated with a white space job9_GoOriginOnFinish: true, // Go X0 Y0 current Z at end fr0_TravelSpeedXY: 2500, // High speed for travel movements X & Y (mm/min) fr1_TravelSpeedZ: 300, // High speed for travel movements Z (mm/min) fr2_EnforceFeedrate: true, // Add feedrate to each movement line frA_ScaleFeedrate: false, // Will feedrated be scaled frB_MaxCutSpeedXY: 900, // Max speed for cut movements X & Y (mm/min) frC_MaxCutSpeedZ: 180, // Max speed for cut movements Z (mm/min) frD_MaxCutSpeedXYZ: 1000, // Max feedrate after scaling mapD_RestoreFirstRapids: false, // Map first G01 --> G00 mapE_RestoreRapids: false, // Map G01 --> G00 for SafeTravelsAboveZ mapF_SafeZ: "Retract:15", // G01 mapped to G00 if Z is >= jobSafeZRapid mapG_AllowRapidZ: false, // Allow G01 --> G00 for vertical retracts and Z descents above safe toolChange0_Enabled: false, // Enable tool change code (bultin tool change requires LCD display) toolChange1_X: 0, // X position for builtin tool change toolChange2_Y: 0, // Y position for builtin tool change toolChange3_Z: 40, // Z position for builtin tool change toolChange4_DisableZStepper: false, // disable Z stepper when change a tool probe1_OnStart: false, // Execute probe gcode to align tool probe2_OnToolChange: false, // Z probe after tool change probe3_Thickness: 0.8, // plate thickness probe4_UseHomeZ: true, // use G28 or G38 for probing probe5_G38Target: -10, // probing up to pos probe6_G38Speed: 30, // probing with speed gcodeStartFile: "", // File with custom Gcode for header/start (in nc folder) gcodeStopFile: "", // File with custom Gcode for footer/end (in nc folder) gcodeToolFile: "", // File with custom Gcode for tool change (in nc folder) gcodeProbeFile: "", // File with custom Gcode for tool probe (in nc folder) cutter1_OnVaporize: 100, // Percentage of power to turn on the laser/plasma cutter in vaporize mode cutter2_OnThrough: 80, // Percentage of power to turn on the laser/plasma cutter in through mode cutter3_OnEtch: 40, // Percentage of power to turn on the laser/plasma cutter in etch mode cutter4_MarlinMode: 106, // Marlin mode laser/plasma cutter cutter5_MarlinPin: 4, // Marlin laser/plasma cutter pin for M42 cutter6_GrblMode: 4, // GRBL mode laser/plasma cutter cutter7_Coolant: eCoolant.Off, // Use this coolant. F360 doesn't define a coolant for cutters cl0_coolantA_Mode: eCoolant.Off, // Enable issuing g-codes for control Coolant channel A cl1_coolantB_Mode: eCoolant.Off, // Use issuing g-codes for control Coolant channel B cl2_coolantAOn: "M42 P6 S255", // GCode command to turn on Coolant channel A cl3_coolantAOff: "M42 P6 S0", // Gcode command to turn off Coolant channel A cl4_coolantBOn: "M42 P11 S255", // GCode command to turn on Coolant channel B cl5_coolantBOff: "M42 P11 S0", // Gcode command to turn off Coolant channel B cl6_cust_coolantAOn: "", // Custom GCode command to turn on Coolant channel A cl7_cust_coolantAOff: "", // Custom Gcode command to turn off Coolant channel A cl8_cust_coolantBOn: "", // Custom GCode command to turn on Coolant channel B cl9_cust_coolantBOff: "", // Custom Gcode command to turn off Coolant channel B DuetMillingMode: "M453 P2 I0 R30000 F200", // GCode command to setup Duet3d milling mode DuetLaserMode: "M452 P2 I0 R255 F200", // GCode command to setup Duet3d laser mode }; propertyDefinitions = { job0_SelectedFirmware: { title: "Job: CNC Firmware", description: "Dialect of GCode to create", group: 1, type: "integer", default_mm: eFirmware.MARLIN, default_in: eFirmware.MARLIN, values: [ { title: eFirmware.prop[eFirmware.MARLIN].name, id: eFirmware.MARLIN }, { title: eFirmware.prop[eFirmware.GRBL].name, id: eFirmware.GRBL }, { title: eFirmware.prop[eFirmware.REPRAP].name, id: eFirmware.REPRAP }, ] }, job1_SetOriginOnStart: { title: "Job: Zero Starting Location (G92)", description: "On start set the current location as 0,0,0 (G92)", group: 1, type: "boolean", default_mm: true, default_in: true }, job2_ManualSpindlePowerControl: { title: "Job: Manual Spindle On/Off", description: "Enable to manually turn spindle motor on/off", group: 1, type: "boolean", default_mm: true, default_in: true }, job3_CommentLevel: { title: "Job: Comment Level", description: "Controls the comments include", group: 1, type: "integer", default_mm: eComment.Info, default_in: eComment.Info, values: [ { title: eComment.prop[eComment.Off].name, id: eComment.Off }, { title: eComment.prop[eComment.Important].name, id: eComment.Important }, { title: eComment.prop[eComment.Info].name, id: eComment.Info }, { title: eComment.prop[eComment.Debug].name, id: eComment.Debug }, ] }, job4_UseArcs: { title: "Job: Use Arcs", description: "Use G2/G3 g-codes fo circular movements", group: 1, type: "boolean", default_mm: true, default_in: true }, job5_SequenceNumbers: { title: "Job: Enable Line #s", description: "Show sequence numbers", group: 1, type: "boolean", default_mm: false, default_in: false }, job6_SequenceNumberStart: { title: "Job: First Line #", description: "First sequence number", group: 1, type: "integer", default_mm: 10, default_in: 10 }, job7_SequenceNumberIncrement: { title: "Job: Line # Increment", description: "Sequence number increment", group: 1, type: "integer", default_mm: 1, default_in: 1 }, job8_SeparateWordsWithSpace: { title: "Job: Include Whitespace", description: "Includes whitespace seperation between text", group: 1, type: "boolean", default_mm: true, default_in: true }, job9_GoOriginOnFinish: { title: "Job: At end go to 0,0", description: "Go to X0 Y0 at gcode end, Z remains unchanged", group: 1, type: "boolean", default_mm: true, default_in: true }, fr0_TravelSpeedXY: { title: "Feed: Travel speed X/Y", description: "High speed for Rapid movements X & Y (mm/min; in/min)", group: 2, type: "spatial", default_mm: 2500, default_in: 100 }, fr1_TravelSpeedZ: { title: "Feed: Travel Speed Z", description: "High speed for Rapid movements z (mm/min; in/min)", group: 2, type: "spatial", default_mm: 300, default_in: 12 }, fr2_EnforceFeedrate: { title: "Feed: Enforce Feedrate", description: "Add feedrate to each movement g-code", group: 2, type: "boolean", default_mm: true, default_in: true }, frA_ScaleFeedrate: { title: "Feed: Scale Feedrate", description: "Scale feedrate based on X, Y, Z axis maximums", group: 2, type: "boolean", default_mm: false, default_in: false }, frB_MaxCutSpeedXY: { title: "Feed: Max XY Cut Speed", description: "Maximum X or Y axis cut speed (mm/min; in/min)", group: 2, type: "spatial", default_mm: 900, default_in: 35.43 }, frC_MaxCutSpeedZ: { title: "Feed: Max Z Cut Speed", description: "Maximum Z axis cut speed (mm/min; in/min)", group: 2, type: "spatial", default_mm: 180, default_in: 7.08 }, frD_MaxCutSpeedXYZ: { title: "Feed: Max Toolpath Speed", description: "Maximum scaled feedrate for toolpath (mm/min; in/min)", group: 2, type: "spatial", default_mm: 1000, default_in: 39.37 }, mapD_RestoreFirstRapids: { title: "Map: First G1 -> G0 Rapid", description: "Ensure move to start of a cut is with a G0 Rapid", group: 3, type: "boolean", default_mm: false, default_in: false }, mapE_RestoreRapids: { title: "Map: G1s -> G0 Rapids", description: "Enable to convert G1s to G0 Rapids when safe", group: 3, type: "boolean", default_mm: false, default_in: false }, mapF_SafeZ: { title: "Map: Safe Z to Rapid", description: "Must be above or equal to this value to map G1s --> G0s; constant or keyword (see docs)", group: 3, type: "string", default_mm: "Retract:15", default_in: "Retract:15" }, mapG_AllowRapidZ: { title: "Map: Allow Rapid Z", description: "Enable to include vertical retracts and safe descents", group: 3, type: "boolean", default_mm: false, default_in: false }, toolChange0_Enabled: { title: "Tool Change: Enable", description: "Include tool change code when tool changes (bultin tool change requires LCD display)", group: 4, type: "boolean", default_mm: false, default_in: false }, toolChange1_X: { title: "Tool Change: X", description: "X location for tool change", group: 4, type: "spatial", default_mm: 0, default_in: 0 }, toolChange2_Y: { title: "Tool Change: Y", description: "Y location for tool change", group: 4, type: "spatial", default_mm: 0, default_in: 0 }, toolChange3_Z: { title: "Tool Change: Z ", description: "Z location for tool change", group: 4, type: "spatial", default_mm: 40, default_in: 1.6 }, toolChange4_DisableZStepper: { title: "Tool Change: Disable Z stepper", description: "Disable Z stepper after reaching tool change location", group: 4, type: "boolean", default_mm: false, default_in: false }, probe1_OnStart: { title: "Probe: On job start", description: "Execute probe gcode on job start", group: 5, type: "boolean", default_mm: false, default_in: false }, probe2_OnToolChange: { title: "Probe: After Tool Change", description: "After tool change, probe Z at the current location", group: 5, type: "boolean", default_mm: false, default_in: false }, probe3_Thickness: { title: "Probe: Plate thickness", description: "Plate thickness", group: 5, type: "spatial", default_mm: 0.8, default_in: 0.032 }, probe4_UseHomeZ: { title: "Probe: Use Home Z (G28)", description: "Probe with G28 (Yes) or G38 (No)", group: 5, type: "boolean", default_mm: true, default_in: true }, probe5_G38Target: { title: "Probe: G38 target", description: "G38 Probing's furthest Z position", group: 5, type: "spatial", default_mm: -10, default_in: -0.5 }, probe6_G38Speed: { title: "Probe: G38 speed", description: "G38 Probing's speed (mm/min; in/min)", group: 5, type: "spatial", default_mm: 30, default_in: 1.2 }, cutter1_OnVaporize: { title: "Laser: On - Vaporize", description: "Persent of power to turn on the laser/plasma cutter in vaporize mode", group: 6, type: "number", default_mm: 100, default_in: 100 }, cutter2_OnThrough: { title: "Laser: On - Through", description: "Persent of power to turn on the laser/plasma cutter in through mode", group: 6, type: "number", default_mm: 80, default_in: 80 }, cutter3_OnEtch: { title: "Laser: On - Etch", description: "Persent of power to on the laser/plasma cutter in etch mode", group: 6, type: "number", default_mm: 40, default_in: 40 }, cutter4_MarlinMode: { title: "Laser: Marlin/Reprap Mode", description: "Marlin/Reprap mode of the laser/plasma cutter", group: 6, type: "integer", default_mm: 106, default_in: 106, values: [ { title: "Fan - M106 S{PWM}/M107", id: 106 }, { title: "Spindle - M3 O{PWM}/M5", id: 3 }, { title: "Pin - M42 P{pin} S{PWM}", id: 42 }, ] }, cutter5_MarlinPin: { title: "Laser: Marlin M42 Pin", description: "Marlin custom pin number for the laser/plasma cutter", group: 6, type: "integer", default_mm: 4, default_in: 4 }, cutter6_GrblMode: { title: "Laser: GRBL Mode", description: "GRBL mode of the laser/plasma cutter", group: 6, type: "integer", default_mm: 4, default_in: 4, values: [ { title: "M4 S{PWM}/M5 dynamic power", id: 4 }, { title: "M3 S{PWM}/M5 static power", id: 3 }, ] }, cutter7_Coolant: { title: "Laser: Coolant", description: "Force a coolant to be used", group: 6, type: "integer", default_mm: eCoolant.Off, default_in: eCoolant.Off, values: [ { title: eCoolant.prop[eCoolant.Off].name, id: eCoolant.Off }, { title: eCoolant.prop[eCoolant.Flood].name, id: eCoolant.Flood }, { title: eCoolant.prop[eCoolant.Mist].name, id: eCoolant.Mist }, { title: eCoolant.prop[eCoolant.ThroughTool].name, id: eCoolant.ThroughTool }, { title: eCoolant.prop[eCoolant.Air].name, id: eCoolant.Air }, { title: eCoolant.prop[eCoolant.AirThroughTool].name, id: eCoolant.AirThroughTool }, { title: eCoolant.prop[eCoolant.Suction].name, id: eCoolant.Suction }, { title: eCoolant.prop[eCoolant.FloodMist].name, id: eCoolant.FloodMist }, { title: eCoolant.prop[eCoolant.FloodThroughTool].name, id: eCoolant.FloodThroughTool } ] }, gcodeStartFile: { title: "Extern: Start File", description: "File with custom Gcode for header/start (in nc folder)", group: 7, type: "file", default_mm: "", default_in: "" }, gcodeStopFile: { title: "Extern: Stop File", description: "File with custom Gcode for footer/end (in nc folder)", group: 7, type: "file", default_mm: "", default_in: "" }, gcodeToolFile: { title: "Extern: Tool File", description: "File with custom Gcode for tool change (in nc folder)", group: 7, type: "file", default_mm: "", default_in: "" }, gcodeProbeFile: { title: "Extern: Probe File", description: "File with custom Gcode for tool probe (in nc folder)", group: 7, type: "file", default_mm: "", default_in: "" }, // Coolant cl0_coolantA_Mode: { title: "Coolant: A Mode", description: "Enable channel A when tool is set this coolant", group: 8, type: "integer", default_mm: 0, default_in: 0, values: [ { title: eCoolant.prop[eCoolant.Off].name, id: eCoolant.Off }, { title: eCoolant.prop[eCoolant.Flood].name, id: eCoolant.Flood }, { title: eCoolant.prop[eCoolant.Mist].name, id: eCoolant.Mist }, { title: eCoolant.prop[eCoolant.ThroughTool].name, id: eCoolant.ThroughTool }, { title: eCoolant.prop[eCoolant.Air].name, id: eCoolant.Air }, { title: eCoolant.prop[eCoolant.AirThroughTool].name, id: eCoolant.AirThroughTool }, { title: eCoolant.prop[eCoolant.Suction].name, id: eCoolant.Suction }, { title: eCoolant.prop[eCoolant.FloodMist].name, id: eCoolant.FloodMist }, { title: eCoolant.prop[eCoolant.FloodThroughTool].name, id: eCoolant.FloodThroughTool } ] }, cl1_coolantB_Mode: { title: "Coolant: B Mode", description: "Enable channel B when tool is set this coolant", group: 8, type: "integer", default_mm: 0, default_in: 0, values: [ { title: eCoolant.prop[eCoolant.Off].name, id: eCoolant.Off }, { title: eCoolant.prop[eCoolant.Flood].name, id: eCoolant.Flood }, { title: eCoolant.prop[eCoolant.Mist].name, id: eCoolant.Mist }, { title: eCoolant.prop[eCoolant.ThroughTool].name, id: eCoolant.ThroughTool }, { title: eCoolant.prop[eCoolant.Air].name, id: eCoolant.Air }, { title: eCoolant.prop[eCoolant.AirThroughTool].name, id: eCoolant.AirThroughTool }, { title: eCoolant.prop[eCoolant.Suction].name, id: eCoolant.Suction }, { title: eCoolant.prop[eCoolant.FloodMist].name, id: eCoolant.FloodMist }, { title: eCoolant.prop[eCoolant.FloodThroughTool].name, id: eCoolant.FloodThroughTool } ] }, cl2_coolantAOn: { title: "Coolant: A Enable", description: "GCode to turn On coolant channel A", group: 8, type: "enum", default_mm: "M42 P6 S255", default_in: "M42 P6 S255", values: [ { title: "Mrln: M42 P6 S255", id: "M42 P6 S255" }, { title: "Mrln: M42 P11 S255", id: "M42 P11 S255" }, { title: "Grbl: M7 (mist)", id: "M7" }, { title: "Grbl: M8 (flood)", id: "M8" }, { title: "Use custom", id: "Use custom" } ] }, cl3_coolantAOff: { title: "Coolant: A Disable", description: "Gcode to turn Off coolant A", group: 8, type: "enum", default_mm: "M42 P6 S0", default_in: "M42 P6 S0", values: [ { title: "Mrln: M42 P6 S0", id: "M42 P6 S0" }, { title: "Mrln: M42 P11 S0", id: "M42 P11 S0" }, { title: "Grbl: M9 (off)", id: "M9" }, { title: "Use custom", id: "Use custom" } ] }, cl4_coolantBOn: { title: "Coolant: B Enable", description: "GCode to turn On coolant channel B", group: 8, type: "enum", default_mm: "M42 P11 S255", default_in: "M42 P11 S255", values: [ { title: "Mrln: M42 P11 S255", id: "M42 P11 S255" }, { title: "Mrln: M42 P6 S255", id: "M42 P6 S255" }, { title: "Grbl: M7 (mist)", id: "M7" }, { title: "Grbl: M8 (flood)", id: "M8" }, { title: "Use custom", id: "Use custom" } ] }, cl5_coolantBOff: { title: "Coolant: B Disable", description: "Gcode to turn Off coolant B", group: 8, type: "enum", default_mm: "M42 P11 S0", default_in: "M42 P11 S0", values: [ { title: "Mrln: M42 P11 S0", id: "M42 P11 S0" }, { title: "Mrln: M42 P6 S0", id: "M42 P6 S0" }, { title: "Grbl: M9 (off)", id: "M9" }, { title: "Use custom", id: "Use custom" } ] }, cl6_cust_coolantAOn: { title: "Coolant: Custom A Enable", description: "Custom GCode to turn On coolant channel A", group: 8, type: "string", default_mm: "", default_in: "", }, cl7_cust_coolantAOff: { title: "Coolant: Custom A Disable", description: "Custom Gcode to turn Off coolant A", group: 8, type: "string", default_mm: "", default_in: "", }, cl8_cust_coolantBOn: { title: "Coolant: Custom B Enable", description: "Custom GCode to turn On coolant channel B", group: 8, type: "string", default_mm: "", default_in: "", }, cl9_cust_coolantBOff: { title: "Coolant: Custom B Disable", description: "Custom Gcode to turn Off coolant B", group: 8, type: "string", default_mm: "", default_in: "", }, DuetMillingMode: { title: "Duet: Milling mode", description: "GCode command to setup Duet3d milling mode", group: 9, type: "string", default_mm: "M453 P2 I0 R30000 F200", default_in: "M453 P2 I0 R30000 F200" }, DuetLaserMode: { title: "Duet: Laser mode", description: "GCode command to setup Duet3d laser mode", group: 9, type: "string", default_mm: "M452 P2 I0 R255 F200", default_in: "M452 P2 I0 R255 F200" }, }; var sequenceNumber; // Formats var gFormat = createFormat({ prefix: "G", decimals: 1 }); var mFormat = createFormat({ prefix: "M", decimals: 0 }); var xyzFormat = createFormat({ decimals: (unit == MM ? 3 : 4) }); var xFormat = createFormat({ prefix: "X", decimals: (unit == MM ? 3 : 4) }); var yFormat = createFormat({ prefix: "Y", decimals: (unit == MM ? 3 : 4) }); var zFormat = createFormat({ prefix: "Z", decimals: (unit == MM ? 3 : 4) }); var iFormat = createFormat({ prefix: "I", decimals: (unit == MM ? 3 : 4) }); var jFormat = createFormat({ prefix: "J", decimals: (unit == MM ? 3 : 4) }); var kFormat = createFormat({ prefix: "K", decimals: (unit == MM ? 3 : 4) }); var speedFormat = createFormat({ decimals: 0 }); var sFormat = createFormat({ prefix: "S", decimals: 0 }); var pFormat = createFormat({ prefix: "P", decimals: 0 }); var oFormat = createFormat({ prefix: "O", decimals: 0 }); var feedFormat = createFormat({ decimals: (unit == MM ? 0 : 2) }); var fFormat = createFormat({ prefix: "F", decimals: (unit == MM ? 0 : 2) }); var toolFormat = createFormat({ decimals: 0 }); var tFormat = createFormat({ prefix: "T", decimals: 0 }); var taperFormat = createFormat({ decimals: 1, scale: DEG }); var secFormat = createFormat({ decimals: 3, forceDecimal: true }); // seconds - range 0.001-1000 // Linear outputs var xOutput = createVariable({}, xFormat); var yOutput = createVariable({}, yFormat); var zOutput = createVariable({}, zFormat); var fOutput = createVariable({ force: false }, fFormat); var sOutput = createVariable({ force: true }, sFormat); // Circular outputs var iOutput = createReferenceVariable({}, iFormat); var jOutput = createReferenceVariable({}, jFormat); var kOutput = createReferenceVariable({}, kFormat); // Modals var gMotionModal = createModal({}, gFormat); // modal group 1 // G0-G3, ... var gPlaneModal = createModal({ onchange: function () { gMotionModal.reset(); } }, gFormat); // modal group 2 // G17-19 var gAbsIncModal = createModal({}, gFormat); // modal group 3 // G90-91 var gFeedModeModal = createModal({}, gFormat); // modal group 5 // G93-94 var gUnitModal = createModal({}, gFormat); // modal group 6 // G20-21 // Arc support variables minimumChordLength = spatial(0.01, MM); minimumCircularRadius = spatial(0.01, MM); maximumCircularRadius = spatial(1000, MM); minimumCircularSweep = toRad(0.01); maximumCircularSweep = toRad(180); allowHelicalMoves = false; allowedCircularPlanes = undefined; // Writes the specified block. function writeBlock() { if (properties.job5_SequenceNumbers) { writeWords2("N" + sequenceNumber, arguments); sequenceNumber += properties.job7_SequenceNumberIncrement; } else { writeWords(arguments); } } function flushMotions() { if (fw == eFirmware.GRBL) { } // Default else { writeBlock(mFormat.format(400)); } } //---------------- Safe Rapids ---------------- var eSafeZ = { CONST: 0, FEED: 1, RETRACT: 2, CLEARANCE: 3, ERROR: 4, prop: { 0: {name: "Const", regex: /^\d+\.?\d*$/, numRegEx: /^(\d+\.?\d*)$/, value: 0}, 1: {name: "Feed", regex: /^Feed:/i, numRegEx: /:(\d+\.?\d*)$/, value: 1}, 2: {name: "Retract", regex: /^Retract:/i, numRegEx: /:(\d+\.?\d*)$/, alue: 2}, 3: {name: "Clearance", regex: /^Clearance:/i, numRegEx: /:(\d+\.?\d*)$/, value: 3}, 4: {name: "Error", regex: /^$/, numRegEx: /^$/, value: 4} } }; var safeZMode = eSafeZ.CONST; var safeZHeightDefault = 15; var safeZHeight; function parseSafeZProperty() { var str = properties.mapF_SafeZ; // Look for either a number by itself or 'Feed:', 'Retract:' or 'Clearance:' for (safeZMode = eSafeZ.CONST; safeZMode < eSafeZ.ERROR; safeZMode++) { if (str.search(eSafeZ.prop[safeZMode].regex) == 0) { break; } } // If it was not an error then get the number if (safeZMode != eSafeZ.ERROR) { safeZHeightDefault = str.match(eSafeZ.prop[safeZMode].numRegEx); if ((safeZHeightDefault == null) || (safeZHeightDefault.length !=2)) { writeComment(eComment.Debug, " parseSafeZProperty: " + safeZHeightDefault); writeComment(eComment.Debug, " parseSafeZProperty.length: " + (safeZHeightDefault != null? safeZHeightDefault.length : "na")); writeComment(eComment.Debug, " parseSafeZProperty: Couldn't find number"); safeZMode = eSafeZ.ERROR; safeZHeightDefault = 15; } else { safeZHeightDefault = safeZHeightDefault[1]; } } writeComment(eComment.Debug, " parseSafeZProperty: safeZMode = '" + eSafeZ.prop[safeZMode].name + "'"); writeComment(eComment.Debug, " parseSafeZProperty: safeZHeightDefault = " + safeZHeightDefault); } function safeZforSection(_section) { if (properties.mapE_RestoreRapids) { switch (safeZMode) { case eSafeZ.CONST: safeZHeight = safeZHeightDefault; writeComment(eComment.Important, " SafeZ using const: " + safeZHeight); break; case eSafeZ.FEED: if (hasParameter("operation:feedHeight_value") && hasParameter("operation:feedHeight_absolute")) { let feed = _section.getParameter("operation:feedHeight_value"); let abs = _section.getParameter("operation:feedHeight_absolute"); if (abs == 1) { safeZHeight = feed; writeComment(eComment.Info, " SafeZ feed level: " + safeZHeight); } else { safeZHeight = safeZHeightDefault; writeComment(eComment.Important, " SafeZ feed level not abs: " + safeZHeight); } } else { safeZHeight = safeZHeightDefault; writeComment(eComment.Important, " SafeZ feed level not defined: " + safeZHeight); } break; case eSafeZ.RETRACT: if (hasParameter("operation:retractHeight_value") && hasParameter("operation:retractHeight_absolute")) { let retract = _section.getParameter("operation:retractHeight_value"); let abs = _section.getParameter("operation:retractHeight_absolute"); if (abs == 1) { safeZHeight = retract; writeComment(eComment.Info, " SafeZ retract level: " + safeZHeight); } else { safeZHeight = safeZHeightDefault; writeComment(eComment.Important, " SafeZ retract level not abs: " + safeZHeight); } } else { safeZHeight = safeZHeightDefault; writeComment(eComment.Important, " SafeZ: retract level not defined: " + safeZHeight); } break; case eSafeZ.CLEARANCE: if (hasParameter("operation:clearanceHeight_value") && hasParameter("operation:clearanceHeight_absolute")) { var clearance = _section.getParameter("operation:clearanceHeight_value"); let abs = _section.getParameter("operation:clearanceHeight_absolute"); if (abs == 1) { safeZHeight = clearance; writeComment(eComment.Info, " SafeZ clearance level: " + safeZHeight); } else { safeZHeight = safeZHeightDefault; writeComment(eComment.Important, " SafeZ clearance level not abs: " + safeZHeight); } } else { safeZHeight = safeZHeightDefault; writeComment(eComment.Important, " SafeZ clearance level not defined: " + safeZHeight); } break; case eSafeZ.ERROR: safeZHeight = safeZHeightDefault; writeComment(eComment.Important, " >>> WARNING: " + propertyDefinitions.mapF_SafeZ.title + "format error: " + safeZHeight); break; } } } Number.prototype.round = function(places) { return +(Math.round(this + "e+" + places) + "e-" + places); } // Returns true if the rules to convert G1s to G0s are satisfied function isSafeToRapid(x, y, z) { if (properties.mapE_RestoreRapids) { // Calculat a z to 3 decimal places for zSafe comparison, every where else use z to avoid mixing rounded with unrounded var z_round = z.round(3); writeComment(eComment.Debug, "isSafeToRapid z: " + z + " z_round: " + z_round); let zSafe = (z_round >= safeZHeight); writeComment(eComment.Debug, "isSafeToRapid zSafe: " + zSafe + " z_round: " + z_round + " safeZHeight: " + safeZHeight); // Destination z must be in safe zone. if (zSafe) { let cur = getCurrentPosition(); let zConstant = (z == cur.z); let zUp = (z > cur.z); let xyConstant = ((x == cur.x) && (y == cur.y)); let curZSafe = (cur.z >= safeZHeight); writeComment(eComment.Debug, "isSafeToRapid curZSafe: " + curZSafe + " cur.z: " + cur.z); // Restore Rapids only when the target Z is safe and // Case 1: Z is not changing, but XY are // Case 2: Z is increasing, but XY constant // Z is not changing and we know we are in the safe zone if (zConstant) { return true; } // We include moves of Z up as long as xy are constant else if (properties.mapG_AllowRapidZ && zUp && xyConstant) { return true; } // We include moves of Z down as long as xy are constant and z always remains safe else if (properties.mapG_AllowRapidZ && (!zUp) && xyConstant && curZSafe) { return true; } } } return false; } //---------------- Coolant ---------------- function CoolantA(on) { var coolantText = on ? properties.cl2_coolantAOn : properties.cl3_coolantAOff; if (coolantText == "Use custom") { coolantText = on ? properties.cl6_cust_coolantAOn : properties.cl7_cust_coolantAOff; } writeBlock(coolantText); } function CoolantB(on) { var coolantText = on ? properties.cl4_coolantBOn : properties.cl5_coolantBOff; if (coolantText == "Use custom") { coolantText = on ? properties.cl8_cust_coolantBOn : properties.cl9_cust_coolantBOff; } writeBlock(coolantText); } // Manage two channels of coolant by tracking which coolant is being using for // a channel (0 = disabled). SetCoolant called with desired coolant to use or 0 to disable var curCoolant = eCoolant.Off; // The coolant requested by the tool var coolantChannelA = eCoolant.Off; // The coolant running in ChannelA var coolantChannelB = eCoolant.Off; // The coolant running in ChannelB function setCoolant(coolant) { writeComment(eComment.Debug, " ---- Coolant: " + coolant + " cur: " + curCoolant + " A: " + coolantChannelA + " B: " + coolantChannelB); // If the coolant for this tool is the same as the current coolant then there is nothing to do if (curCoolant == coolant) { return; } // We are changing coolant, so disable any active coolant channels // before we switch to the other coolant if (coolantChannelA != eCoolant.Off) { writeComment((coolant == eCoolant.Off) ? eComment.Important: eComment.Info, " >>> Coolant Channel A: " + eCoolant.prop[eCoolant.Off].name); coolantChannelA = eCoolant.Off; CoolantA(false); } if (coolantChannelB != eCoolant.Off) { writeComment((coolant == eCoolant.Off) ? eComment.Important: eComment.Info, " >>> Coolant Channel B: " + eCoolant.prop[eCoolant.Off].name); coolantChannelB = eCoolant.Off; CoolantB(false); } // At this point we know that all coolant is off so make that the current coolant curCoolant = eCoolant.Off; // As long as we are not disabling coolant (coolant = 0), then check if either coolant channel // matches the coolant requested. If neither do then issue an warning var warn = true; if (coolant != eCoolant.Off) { if (properties.cl0_coolantA_Mode == coolant) { writeComment(eComment.Important, " >>> Coolant Channel A: " + eCoolant.prop[coolant].name); coolantChannelA = coolant; curCoolant = coolant; warn = false; CoolantA(true); } if (properties.cl1_coolantB_Mode == coolant) { writeComment(eComment.Important, " >>> Coolant Channel B: " + eCoolant.prop[coolant].name); coolantChannelB = coolant; curCoolant = coolant; warn = false; CoolantB(true); } if (warn) { writeComment(eComment.Important, " >>> WARNING: No matching Coolant channel : " + ((coolant <= eCoolant.FloodThroughTool) ? eCoolant.prop[coolant].name : "unknown") + " requested"); } } } //---------------- Cutters - Waterjet/Laser/Plasma ---------------- var cutterOnCurrentPower; function laserOn(power) { // Firmware is Grbl if (fw == eFirmware.GRBL) { var laser_pwm = power * 10; writeBlock(mFormat.format(properties.cutter6_GrblMode), sFormat.format(laser_pwm)); } // Default firmware else { var laser_pwm = power / 100 * 255; switch (properties.cutter4_MarlinMode) { case 106: writeBlock(mFormat.format(106), sFormat.format(laser_pwm)); break; case 3: if (fw == eFirmware.REPRAP) { writeBlock(mFormat.format(3), sFormat.format(laser_pwm)); } else { writeBlock(mFormat.format(3), oFormat.format(laser_pwm)); } break; case 42: writeBlock(mFormat.format(42), pFormat.format(properties.cutter5_MarlinPin), sFormat.format(laser_pwm)); break; } } } function laserOff() { // Firmware is Grbl if (fw == eFirmware.GRBL) { writeBlock(mFormat.format(5)); } // Default else { switch (properties.cutter4_MarlinMode) { case 106: writeBlock(mFormat.format(107)); break; case 3: writeBlock(mFormat.format(5)); break; case 42: writeBlock(mFormat.format(42), pFormat.format(properties.cutter5_MarlinPin), sFormat.format(0)); break; } } } //---------------- on Entry Points ---------------- // Called in every new gcode file function onOpen() { fw = properties.job0_SelectedFirmware; // Output anything special to start the GCode if (fw == eFirmware.GRBL) { writeln("%"); } // Configure the GCode G commands if (fw == eFirmware.GRBL) { gMotionModal = createModal({}, gFormat); // modal group 1 // G0-G3, ... } else { gMotionModal = createModal({ force: true }, gFormat); // modal group 1 // G0-G3, ... } // Configure how the feedrate is formatted if (properties.fr2_EnforceFeedrate) { fOutput = createVariable({ force: true }, fFormat); } // Set the starting sequence number for line numbering sequenceNumber = properties.job6_SequenceNumberStart; // Set the seperator used between text if (!properties.job8_SeparateWordsWithSpace) { setWordSeparator(""); } // Determine the safeZHeight to do rapids parseSafeZProperty(); } // Called at end of gcode file function onClose() { writeComment(eComment.Important, " *** STOP begin ***"); flushMotions(); if (properties.gcodeStopFile == "") { onCommand(COMMAND_COOLANT_OFF); if (properties.job9_GoOriginOnFinish) { rapidMovementsXY(0, 0); } onCommand(COMMAND_STOP_SPINDLE); end(true); writeComment(eComment.Important, " *** STOP end ***"); } else { loadFile(properties.gcodeStopFile); } if (fw == eFirmware.GRBL) { writeln("%"); } } var forceSectionToStartWithRapid = false; function onSection() { // Every section needs to start with a Rapid to get to the initial location. // In the hobby version Rapids have been elliminated and the first command is // a onLinear not a onRapid command. This results in not current position being // that same as the cut to position which means wecan't determine the direction // of the move. Without a direction vector we can't scale the feedrate or convert // onLinear moves back into onRapids. By ensuring the first onLinear is treated as // a onRapid we have a currentPosition that is correct. forceSectionToStartWithRapid = true; // Write Start gcode of the documment (after the "onParameters" with the global info) if (isFirstSection()) { writeFirstSection(); } writeComment(eComment.Important, " *** SECTION begin ***"); // Print min/max boundaries for each section vectorX = new Vector(1, 0, 0); vectorY = new Vector(0, 1, 0); writeComment(eComment.Info, " X Min: " + xyzFormat.format(currentSection.getGlobalRange(vectorX).getMinimum()) + " - X Max: " + xyzFormat.format(currentSection.getGlobalRange(vectorX).getMaximum())); writeComment(eComment.Info, " Y Min: " + xyzFormat.format(currentSection.getGlobalRange(vectorY).getMinimum()) + " - Y Max: " + xyzFormat.format(currentSection.getGlobalRange(vectorY).getMaximum())); writeComment(eComment.Info, " Z Min: " + xyzFormat.format(currentSection.getGlobalZRange().getMinimum()) + " - Z Max: " + xyzFormat.format(currentSection.getGlobalZRange().getMaximum())); // Determine the Safe Z Height to map G1s to G0s safeZforSection(currentSection); // Do a tool change if tool changes are enabled and its not the first section and this section uses // a different tool then the previous section if (properties.toolChange0_Enabled && !isFirstSection() && tool.number != getPreviousSection().getTool().number) { if (properties.gcodeToolFile == "") { // Post Processor does the tool change writeComment(eComment.Important, " --- Tool Change Start") toolChange(); writeComment(eComment.Important, " --- Tool Change End") } else { // Users custom tool change gcode is used loadFile(properties.gcodeToolFile); } } // Machining type if (currentSection.type == TYPE_MILLING) { // Specific milling code writeComment(eComment.Info, " " + sectionComment + " - Milling - Tool: " + tool.number + " - " + tool.comment + " " + getToolTypeName(tool.type)); } else if (currentSection.type == TYPE_JET) { var jetModeStr; var warn = false; // Cutter mode used for different cutting power in PWM laser switch (currentSection.jetMode) { case JET_MODE_THROUGH: cutterOnCurrentPower = properties.cutter2_OnThrough; jetModeStr = "Through" break; case JET_MODE_ETCHING: cutterOnCurrentPower = properties.cutter3_OnEtch; jetModeStr = "Etching" break; case JET_MODE_VAPORIZE: jetModeStr = "Vaporize" cutterOnCurrentPower = properties.cutter1_OnVaporize; break; default: jetModeStr = "*** Unknown ***" warn = true; } if (warn) { writeComment(eComment.Info, " " + sectionComment + ", Laser/Plasma Cutting mode: " + getParameter("operation:cuttingMode") + ", jetMode: " + jetModeStr); writeComment(eComment.Important, "Selected cutting mode " + currentSection.jetMode + " not mapped to power level"); } else { writeComment(eComment.Info, " " + sectionComment + ", Laser/Plasma Cutting mode: " + getParameter("operation:cuttingMode") + ", jetMode: " + jetModeStr + ", power: " + cutterOnCurrentPower); } } // Adjust the mode if (fw == eFirmware.REPRAP) { if (machineMode != currentSection.type) { switch (currentSection.type) { case TYPE_MILLING: writeBlock(properties.DuetMillingMode); break; case TYPE_JET: writeBlock(properties.DuetLaserMode); break; } } } machineMode = currentSection.type; onCommand(COMMAND_START_SPINDLE); onCommand(COMMAND_COOLANT_ON); // Display section name in LCD display_text(" " + sectionComment); } // Called in every section end function onSectionEnd() { resetAll(); writeComment(eComment.Important, " *** SECTION end ***"); writeComment(eComment.Important, ""); } function onComment(message) { writeComment(eComment.Important, message); } var pendingRadiusCompensation = RADIUS_COMPENSATION_OFF; function onRadiusCompensation() { pendingRadiusCompensation = radiusCompensation; } // Rapid movements function onRapid(x, y, z) { forceSectionToStartWithRapid = false; rapidMovements(x, y, z); } // Feed movements function onLinear(x, y, z, feed) { // If we are allowing Rapids to be recovered from Linear (cut) moves, which is // only required when F360 Personal edition is used, then if this Linear (cut) // move is the first operationin a Section (milling operation) then convert it // to a Rapid. This is OK because Sections normally begin with a Rapid to move // to the first cutting location but these Rapids were changed to Linears by // the personal edition. If this Rapid is not recovered and feedrate scaling // is enabled then the first move to the start of a section will be at the // slowest cutting feedrate, generally Z's feedrate. if (properties.mapD_RestoreFirstRapids && (forceSectionToStartWithRapid == true)) { writeComment(eComment.Important, " First G1 --> G0"); forceSectionToStartWithRapid = false; onRapid(x, y, z); } else if (isSafeToRapid(x, y, z)) { writeComment(eComment.Important, " Safe G1 --> G0"); onRapid(x, y, z); } else { linearMovements(x, y, z, feed, true); } } function onRapid5D(_x, _y, _z, _a, _b, _c) { forceSectionToStartWithRapid = false; error(localize("Multi-axis motion is not supported.")); } function onLinear5D(_x, _y, _z, _a, _b, _c, feed) { forceSectionToStartWithRapid = false; error(localize("Multi-axis motion is not supported.")); } function onCircular(clockwise, cx, cy, cz, x, y, z, feed) { forceSectionToStartWithRapid = false; if (pendingRadiusCompensation != RADIUS_COMPENSATION_OFF) { error(localize("Radius compensation cannot be activated/deactivated for a circular move.")); return; } circular(clockwise, cx, cy, cz, x, y, z, feed) } // Called on waterjet/plasma/laser cuts var powerState = false; function onPower(power) { if (power != powerState) { if (power) { writeComment(eComment.Important, " >>> LASER Power ON"); laserOn(cutterOnCurrentPower); } else { writeComment(eComment.Important, " >>> LASER Power OFF"); laserOff(); } powerState = power; } } // Called on Dwell Manual NC invocation function onDwell(seconds) { writeComment(eComment.Important, " >>> Dwell"); if (seconds > 99999.999) { warning(localize("Dwelling time is out of range.")); } seconds = clamp(0.001, seconds, 99999.999); // Firmware is Grbl if (fw == eFirmware.GRBL) { writeBlock(gFormat.format(4), "P" + secFormat.format(seconds)); } // Default else { writeBlock(gFormat.format(4), "S" + secFormat.format(seconds)); } } // Called with every parameter in the documment/section function onParameter(name, value) { // Write gcode initial info // Product version if (name == "generated-by") { writeComment(eComment.Important, value); writeComment(eComment.Important, " Posts processor: " + FileSystem.getFilename(getConfigurationPath())); } // Date else if (name == "generated-at") { writeComment(eComment.Important, " Gcode generated: " + value + " GMT"); } // Document else if (name == "document-path") { writeComment(eComment.Important, " Document: " + value); } // Setup else if (name == "job-description") { writeComment(eComment.Important, " Setup: " + value); } // Get section comment else if (name == "operation-comment") { sectionComment = value; } else { writeComment(eComment.Debug, " param: " + name + " = " + value); } } function onMovement(movement) { var jet = tool.isJetTool && tool.isJetTool(); var id; switch (movement) { case MOVEMENT_RAPID: id = "MOVEMENT_RAPID"; break; case MOVEMENT_LEAD_IN: id = "MOVEMENT_LEAD_IN"; break; case MOVEMENT_CUTTING: id = "MOVEMENT_CUTTING"; break; case MOVEMENT_LEAD_OUT: id = "MOVEMENT_LEAD_OUT"; break; case MOVEMENT_LINK_TRANSITION: id = jet ? "MOVEMENT_BRIDGING" : "MOVEMENT_LINK_TRANSITION"; break; case MOVEMENT_LINK_DIRECT: id = "MOVEMENT_LINK_DIRECT"; break; case MOVEMENT_RAMP_HELIX: id = jet ? "MOVEMENT_PIERCE_CIRCULAR" : "MOVEMENT_RAMP_HELIX"; break; case MOVEMENT_RAMP_PROFILE: id = jet ? "MOVEMENT_PIERCE_PROFILE" : "MOVEMENT_RAMP_PROFILE"; break; case MOVEMENT_RAMP_ZIG_ZAG: id = jet ? "MOVEMENT_PIERCE_LINEAR" : "MOVEMENT_RAMP_ZIG_ZAG"; break; case MOVEMENT_RAMP: id = "MOVEMENT_RAMP"; break; case MOVEMENT_PLUNGE: id = jet ? "MOVEMENT_PIERCE" : "MOVEMENT_PLUNGE"; break; case MOVEMENT_PREDRILL: id = "MOVEMENT_PREDRILL"; break; case MOVEMENT_EXTENDED: id = "MOVEMENT_EXTENDED"; break; case MOVEMENT_REDUCED: id = "MOVEMENT_REDUCED"; break; case MOVEMENT_HIGH_FEED: id = "MOVEMENT_HIGH_FEED"; break; case MOVEMENT_FINISH_CUTTING: id = "MOVEMENT_FINISH_CUTTING"; break; } if (id == undefined) { id = String(movement); } writeComment(eComment.Info, " " + id); } var currentSpindleSpeed = 0; function setSpindeSpeed(_spindleSpeed, _clockwise) { if (currentSpindleSpeed != _spindleSpeed) { if (_spindleSpeed > 0) { spindleOn(_spindleSpeed, _clockwise); } else { spindleOff(); } currentSpindleSpeed = _spindleSpeed; } } function onSpindleSpeed(spindleSpeed) { setSpindeSpeed(spindleSpeed, tool.clockwise); } function onCommand(command) { writeComment(eComment.Info, " " + getCommandStringId(command)); switch (command) { case COMMAND_START_SPINDLE: onCommand(tool.clockwise ? COMMAND_SPINDLE_CLOCKWISE : COMMAND_SPINDLE_COUNTERCLOCKWISE); return; case COMMAND_SPINDLE_CLOCKWISE: if (!tool.isJetTool()) { setSpindeSpeed(spindleSpeed, true); } return; case COMMAND_SPINDLE_COUNTERCLOCKWISE: if (!tool.isJetTool()) { setSpindeSpeed(spindleSpeed, false); } return; case COMMAND_STOP_SPINDLE: if (!tool.isJetTool()) { setSpindeSpeed(0, true); } return; case COMMAND_COOLANT_ON: if (tool.isJetTool()) { // F360 doesn't support coolant with jet tools (water jet/laser/plasma) but we've // added a parameter to force a coolant to be selected for jet tool operations. Note: tool.coolant // is not used as F360 doesn't define it. if (properties.cutter7_Coolant != eCoolant.Off) { setCoolant(properties.cutter7_Coolant); } } else { setCoolant(tool.coolant); } return; case COMMAND_COOLANT_OFF: setCoolant(eCoolant.Off); //COOLANT_DISABLED return; case COMMAND_LOCK_MULTI_AXIS: return; case COMMAND_UNLOCK_MULTI_AXIS: return; case COMMAND_BREAK_CONTROL: return; case COMMAND_TOOL_MEASURE: if (!tool.isJetTool()) { probeTool(); } return; case COMMAND_STOP: writeBlock(mFormat.format(0)); return; } } function resetAll() { xOutput.reset(); yOutput.reset(); zOutput.reset(); fOutput.reset(); } function writeInformation() { // Calcualte the min/max ranges across all sections var toolZRanges = {}; var vectorX = new Vector(1, 0, 0); var vectorY = new Vector(0, 1, 0); var ranges = { x: { min: undefined, max: undefined }, y: { min: undefined, max: undefined }, z: { min: undefined, max: undefined }, }; var handleMinMax = function (pair, range) { var rmin = range.getMinimum(); var rmax = range.getMaximum(); if (pair.min == undefined || pair.min > rmin) { pair.min = rmin; } if (pair.max == undefined || pair.max < rmin) { // was pair.min - changed by DG 1/4/2021 pair.max = rmax; } } var numberOfSections = getNumberOfSections(); for (var i = 0; i < numberOfSections; ++i) { var section = getSection(i); var tool = section.getTool(); var zRange = section.getGlobalZRange(); var xRange = section.getGlobalRange(vectorX); var yRange = section.getGlobalRange(vectorY); handleMinMax(ranges.x, xRange); handleMinMax(ranges.y, yRange); handleMinMax(ranges.z, zRange); if (is3D()) { if (toolZRanges[tool.number]) { toolZRanges[tool.number].expandToRange(zRange); } else { toolZRanges[tool.number] = zRange; } } } // Display the Range Table writeComment(eComment.Info, " "); writeComment(eComment.Info, " Ranges Table:"); writeComment(eComment.Info, " X: Min=" + xyzFormat.format(ranges.x.min) + " Max=" + xyzFormat.format(ranges.x.max) + " Size=" + xyzFormat.format(ranges.x.max - ranges.x.min)); writeComment(eComment.Info, " Y: Min=" + xyzFormat.format(ranges.y.min) + " Max=" + xyzFormat.format(ranges.y.max) + " Size=" + xyzFormat.format(ranges.y.max - ranges.y.min)); writeComment(eComment.Info, " Z: Min=" + xyzFormat.format(ranges.z.min) + " Max=" + xyzFormat.format(ranges.z.max) + " Size=" + xyzFormat.format(ranges.z.max - ranges.z.min)); // Display the Tools Table writeComment(eComment.Info, " "); writeComment(eComment.Info, " Tools Table:"); var tools = getToolTable(); if (tools.getNumberOfTools() > 0) { for (var i = 0; i < tools.getNumberOfTools(); ++i) { var tool = tools.getTool(i); var comment = " T" + toolFormat.format(tool.number) + " D=" + xyzFormat.format(tool.diameter) + " CR=" + xyzFormat.format(tool.cornerRadius); if ((tool.taperAngle > 0) && (tool.taperAngle < Math.PI)) { comment += " TAPER=" + taperFormat.format(tool.taperAngle) + "deg"; } if (toolZRanges[tool.number]) { comment += " - ZMIN=" + xyzFormat.format(toolZRanges[tool.number].getMinimum()); } comment += " - " + getToolTypeName(tool.type) + " " + tool.comment; writeComment(eComment.Info, comment); } } // Display the Feedrate and Scaling Properties writeComment(eComment.Info, " "); writeComment(eComment.Info, " Feedrate and Scaling Properties:"); writeComment(eComment.Info, " Feed: Travel speed X/Y = " + properties.fr0_TravelSpeedXY); writeComment(eComment.Info, " Feed: Travel Speed Z = " + properties.fr1_TravelSpeedZ); writeComment(eComment.Info, " Feed: Enforce Feedrate = " + properties.fr2_EnforceFeedrate); writeComment(eComment.Info, " Feed: Scale Feedrate = " + properties.frA_ScaleFeedrate); writeComment(eComment.Info, " Feed: Max XY Cut Speed = " + properties.frB_MaxCutSpeedXY); writeComment(eComment.Info, " Feed: Max Z Cut Speed = " + properties.frC_MaxCutSpeedZ); writeComment(eComment.Info, " Feed: Max Toolpath Speed = " + properties.frD_MaxCutSpeedXYZ); // Display the G1->G0 Mapping Properties writeComment(eComment.Info, " "); writeComment(eComment.Info, " G1->G0 Mapping Properties:"); writeComment(eComment.Info, " Map: First G1 -> G0 Rapid = " + properties.mapD_RestoreFirstRapids); writeComment(eComment.Info, " Map: G1s -> G0 Rapids = " + properties.mapE_RestoreRapids); writeComment(eComment.Info, " Map: SafeZ Mode = " + eSafeZ.prop[safeZMode].name + " : default = " + safeZHeightDefault); writeComment(eComment.Info, " Map: Allow Rapid Z = " + properties.mapG_AllowRapidZ); writeComment(eComment.Info, " "); } function writeFirstSection() { // Write out the information block at the beginning of the file writeInformation(); writeComment(eComment.Important, " *** START begin ***"); if (properties.gcodeStartFile == "") { Start(); } else { loadFile(properties.gcodeStartFile); } writeComment(eComment.Important, " *** START end ***"); writeComment(eComment.Important, " "); } // Output a comment function writeComment(level, text) { if (level <= properties.job3_CommentLevel) { if (fw == eFirmware.GRBL) { writeln("(" + String(text).replace(/[\(\)]/g, "") + ")"); } else { writeln(";" + String(text).replace(/[\(\)]/g, "")); } } } // Rapid movements with G1 and differentiated travel speeds for XY // Changes F360 current XY. // No longer called for general Rapid only for probing, homing, etc. function rapidMovementsXY(_x, _y) { let x = xOutput.format(_x); let y = yOutput.format(_y); if (x || y) { if (pendingRadiusCompensation != RADIUS_COMPENSATION_OFF) { error(localize("Radius compensation mode cannot be changed at rapid traversal.")); } else { let f = fOutput.format(propertyMmToUnit(properties.fr0_TravelSpeedXY)); writeBlock(gMotionModal.format(0), x, y, f); } } } // Rapid movements with G1 and differentiated travel speeds for Z // Changes F360 current Z // No longer called for general Rapid only for probing, homing, etc. function rapidMovementsZ(_z) { let z = zOutput.format(_z); if (z) { if (pendingRadiusCompensation != RADIUS_COMPENSATION_OFF) { error(localize("Radius compensation mode cannot be changed at rapid traversal.")); } else { let f = fOutput.format(propertyMmToUnit(properties.fr1_TravelSpeedZ)); writeBlock(gMotionModal.format(0), z, f); } } } // Rapid movements with G1 uses the max travel rate (xy or z) and then relies on feedrate scaling function rapidMovements(_x, _y, _z) { rapidMovementsZ(_z); rapidMovementsXY(_x, _y); } // Calculate the feedX, feedY and feedZ components function limitFeedByXYZComponents(curPos, destPos, feed) { if (!properties.frA_ScaleFeedrate) return feed; var xyz = Vector.diff(destPos, curPos); // Translate the cut so curPos is at 0,0,0 var dir = xyz.getNormalized(); // Normalize vector to get a direction vector var xyzFeed = Vector.product(dir.abs, feed); // Determine the effective x,y,z speed on each axis // Get the max speed for each axis let xyLimit = propertyMmToUnit(properties.frB_MaxCutSpeedXY); let zLimit = propertyMmToUnit(properties.frC_MaxCutSpeedZ); // Normally F360 begins a Section (a milling operation) with a Rapid to move to the beginning of the cut. // Rapids use the defined Travel speed and the Post Processor does not depend on the current location. // This function must know the current location in order to calculate the actual vector traveled. Without // the first Rapid the current location is the same as the desination location, which creates a 0 length // vector. A zero length vector is unusable and so a instead the slowest of the xyLimit or zLimit is used. // // Note: if Map: G1 -> Rapid is enabled in the Properties then if the first operation in a Section is a // cut (which it should always be) then it will be converted to a Rapid. This prevents ever getting a zero // length vector. if (xyz.length == 0) { var lesserFeed = (xyLimit < zLimit) ? xyLimit : zLimit; return lesserFeed; } // Force the speed of each axis to be within limits if (xyzFeed.z > zLimit) { xyzFeed.multiply(zLimit / xyzFeed.z); } if (xyzFeed.x > xyLimit) { xyzFeed.multiply(xyLimit / xyzFeed.x); } if (xyzFeed.y > xyLimit) { xyzFeed.multiply(xyLimit / xyzFeed.y); } // Calculate the new feedrate based on the speed allowed on each axis: feedrate = sqrt(x^2 + y^2 + z^2) // xyzFeed.length is the same as Math.sqrt((xyzFeed.x * xyzFeed.x) + (xyzFeed.y * xyzFeed.y) + (xyzFeed.z * xyzFeed.z)) // Limit the new feedrate by the maximum allowable cut speed let xyzLimit = propertyMmToUnit(properties.frD_MaxCutSpeedXYZ); let newFeed = (xyzFeed.length > xyzLimit) ? xyzLimit : xyzFeed.length; if (Math.abs(newFeed - feed) > 0.01) { return newFeed; } else { return feed; } } // Linear movements function linearMovements(_x, _y, _z, _feed) { if (pendingRadiusCompensation != RADIUS_COMPENSATION_OFF) { // ensure that we end at desired position when compensation is turned off xOutput.reset(); yOutput.reset(); } // Force the feedrate to be scaled (if enabled). The feedrate is projected into the // x, y, and z axis and each axis is tested to see if it exceeds its defined max. If // it does then the speed in all 3 axis is scaled proportionately. The resulting feedrate // is then capped at the maximum defined cutrate. let feed = limitFeedByXYZComponents(getCurrentPosition(), new Vector(_x, _y, _z), _feed); let x = xOutput.format(_x); let y = yOutput.format(_y); let z = zOutput.format(_z); let f = fOutput.format(feed); if (x || y || z) { if (pendingRadiusCompensation != RADIUS_COMPENSATION_OFF) { error(localize("Radius compensation mode is not supported.")); } else { writeBlock(gMotionModal.format(1), x, y, z, f); } } else if (f) { if (getNextRecord().isMotion()) { // try not to output feed without motion fOutput.reset(); // force feed on next line } else { writeBlock(gMotionModal.format(1), f); } } } // Test if file exist/can read and load it function loadFile(_file) { var folder = FileSystem.getFolderPath(getOutputPath()) + PATH_SEPARATOR; if (FileSystem.isFile(folder + _file)) { var txt = loadText(folder + _file, "utf-8"); if (txt.length > 0) { writeComment(eComment.Info, " --- Start custom gcode " + folder + _file); write(txt); writeComment("eComment.Info, --- End custom gcode " + folder + _file); } } else { writeComment(eComment.Important, " Can't open file " + folder + _file); error("Can't open file " + folder + _file); } } function propertyMmToUnit(_v) { return (_v / (unit == IN ? 25.4 : 1)); } /* function mergeProperties(to, from) { for (var attrname in from) { to[attrname] = from[attrname]; } } function Firmware3dPrinterLike() { FirmwareBase.apply(this, arguments); this.spindleEnabled = false; } Firmware3dPrinterLike.prototype = Object.create(FirmwareBase.prototype); Firmware3dPrinterLike.prototype.constructor = Firmware3dPrinterLike; */ function Start() { // Is Grbl? if (fw == eFirmware.GRBL) { writeBlock(gAbsIncModal.format(90)); // Set to Absolute Positioning writeBlock(gFeedModeModal.format(94)); writeBlock(gPlaneModal.format(17)); writeBlock(gUnitModal.format(unit == IN ? 20 : 21)); } // Default else { writeComment(eComment.Info, " Set Absolute Positioning"); writeComment(eComment.Info, " Units = " + (unit == IN ? "inch" : "mm")); writeComment(eComment.Info, " Disable stepper timeout"); if (properties.job1_SetOriginOnStart) { writeComment(eComment.Info, " Set current position to 0,0,0"); } writeBlock(gAbsIncModal.format(90)); // Set to Absolute Positioning writeBlock(gUnitModal.format(unit == IN ? 20 : 21)); // Set the units writeBlock(mFormat.format(84), sFormat.format(0)); // Disable steppers timeout if (properties.job1_SetOriginOnStart) { writeBlock(gFormat.format(92), xFormat.format(0), yFormat.format(0), zFormat.format(0)); // Set origin to initial position } if (properties.probe1_OnStart && tool.number != 0 && !tool.isJetTool()) { onCommand(COMMAND_TOOL_MEASURE); } } } function end() { // Is Grbl? if (fw == eFirmware.GRBL) { writeBlock(mFormat.format(30)); } // Default else { display_text("Job end"); } } function spindleOn(_spindleSpeed, _clockwise) { // Is Grbl? if (fw == eFirmware.GRBL) { writeComment(eComment.Important, " >>> Spindle Speed " + speedFormat.format(_spindleSpeed)); writeBlock(mFormat.format(_clockwise ? 3 : 4), sOutput.format(spindleSpeed)); } // Default else { if (properties.job2_ManualSpindlePowerControl) { // For manual any positive input speed assumed as enabled. so it's just a flag if (!this.spindleEnabled) { writeComment(eComment.Important, " >>> Spindle Speed: Manual"); askUser("Turn ON " + speedFormat.format(_spindleSpeed) + "RPM", "Spindle", false); } } else { writeComment(eComment.Important, " >>> Spindle Speed " + speedFormat.format(_spindleSpeed)); writeBlock(mFormat.format(_clockwise ? 3 : 4), sOutput.format(spindleSpeed)); } this.spindleEnabled = true; } } function spindleOff() { // Is Grbl? if (fw == eFirmware.GRBL) { writeBlock(mFormat.format(5)); } //Default else { if (properties.job2_ManualSpindlePowerControl) { writeBlock(mFormat.format(300), sFormat.format(300), pFormat.format(3000)); askUser("Turn OFF spindle", "Spindle", false); } else { writeBlock(mFormat.format(5)); } this.spindleEnabled = false; } } function display_text(txt) { // Firmware is Grbl if (fw == eFirmware.GRBL) { // Don't display text } // Default else { writeBlock(mFormat.format(117), (properties.job8_SeparateWordsWithSpace ? "" : " ") + txt); } } function circular(clockwise, cx, cy, cz, x, y, z, feed) { if (!properties.job4_UseArcs) { linearize(tolerance); return; } var start = getCurrentPosition(); // Firmware is Grbl if (fw == eFirmware.GRBL) { if (isFullCircle()) { if (isHelical()) { linearize(tolerance); return; } switch (getCircularPlane()) { case PLANE_XY: writeBlock(gPlaneModal.format(17), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), iOutput.format(cx - start.x, 0), jOutput.format(cy - start.y, 0), fOutput.format(feed)); break; case PLANE_ZX: writeBlock(gPlaneModal.format(18), gMotionModal.format(clockwise ? 2 : 3), zOutput.format(z), iOutput.format(cx - start.x, 0), kOutput.format(cz - start.z, 0), fOutput.format(feed)); break; case PLANE_YZ: writeBlock(gPlaneModal.format(19), gMotionModal.format(clockwise ? 2 : 3), yOutput.format(y), jOutput.format(cy - start.y, 0), kOutput.format(cz - start.z, 0), fOutput.format(feed)); break; default: linearize(tolerance); } } else { switch (getCircularPlane()) { case PLANE_XY: writeBlock(gPlaneModal.format(17), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), iOutput.format(cx - start.x, 0), jOutput.format(cy - start.y, 0), fOutput.format(feed)); break; case PLANE_ZX: writeBlock(gPlaneModal.format(18), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), iOutput.format(cx - start.x, 0), kOutput.format(cz - start.z, 0), fOutput.format(feed)); break; case PLANE_YZ: writeBlock(gPlaneModal.format(19), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), jOutput.format(cy - start.y, 0), kOutput.format(cz - start.z, 0), fOutput.format(feed)); break; default: linearize(tolerance); } } } // Default else { // Marlin supports arcs only on XY plane if (isFullCircle()) { if (isHelical()) { linearize(tolerance); return; } switch (getCircularPlane()) { case PLANE_XY: writeBlock(gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), iOutput.format(cx - start.x, 0), jOutput.format(cy - start.y, 0), fOutput.format(feed)); break; default: linearize(tolerance); } } else { switch (getCircularPlane()) { case PLANE_XY: writeBlock(gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), iOutput.format(cx - start.x, 0), jOutput.format(cy - start.y, 0), fOutput.format(feed)); break; default: linearize(tolerance); } } } } function askUser(text, title, allowJog) { // Firmware is RepRap? if (fw == eFirmware.REPRAP) { var v1 = " P\"" + text + "\" R\"" + title + "\" S3"; var v2 = allowJog ? " X1 Y1 Z1" : ""; writeBlock(mFormat.format(291), (properties.job8_SeparateWordsWithSpace ? "" : " ") + v1 + v2); } // Default else { writeBlock(mFormat.format(0), (properties.job8_SeparateWordsWithSpace ? "" : " ") + text); } } function toolChange() { // Grbl tool change? if (fw == eFirmware.GRBL) { writeBlock(mFormat.format(6), tFormat.format(tool.number)); writeBlock(gFormat.format(54)); } // Default tool change else { flushMotions(); // Go to tool change position onRapid(propertyMmToUnit(properties.toolChange1_X), propertyMmToUnit(properties.toolChange2_Y), propertyMmToUnit(properties.toolChange3_Z)); flushMotions(); // turn off spindle and coolant onCommand(COMMAND_COOLANT_OFF); onCommand(COMMAND_STOP_SPINDLE); if (!properties.job2_ManualSpindlePowerControl) { // Beep writeBlock(mFormat.format(300), sFormat.format(400), pFormat.format(2000)); } // Disable Z stepper if (properties.toolChange4_DisableZStepper) { askUser("Z Stepper will disabled. Wait for STOP!!", "Tool change", false); writeBlock(mFormat.format(17), 'Z'); // Disable steppers timeout } // Ask tool change and wait user to touch lcd button askUser("Tool " + tool.number + " " + tool.comment, "Tool change", true); // Run Z probe gcode if (properties.probe2_OnToolChange && tool.number != 0) { onCommand(COMMAND_TOOL_MEASURE); } } } function probeTool() { // Is Grbl? if (fw == eFirmware.GRBL) { writeComment(eComment.Important, " >>> WARNING: No probing implemented for GRBL"); } // Default else { writeComment(eComment.Important, " Probe to Zero Z"); writeComment(eComment.Info, " Ask User to Attach the Z Probe"); writeComment(eComment.Info, " Do Probing"); writeComment(eComment.Info, " Set Z to probe thickness: " + zFormat.format(propertyMmToUnit(properties.probe3_Thickness))) if (properties.toolChange3_Z != "") { writeComment(eComment.Info, " Retract the tool to " + propertyMmToUnit(properties.toolChange3_Z)); } writeComment(eComment.Info, " Ask User to Remove the Z Probe"); askUser("Attach ZProbe", "Probe", false); // refer http://marlinfw.org/docs/gcode/G038.html if (properties.probe4_UseHomeZ) { writeBlock(gFormat.format(28), 'Z'); } else { writeBlock(gMotionModal.format(38.3), fFormat.format(propertyMmToUnit(properties.probe6_G38Speed)), zFormat.format(propertyMmToUnit(properties.probe5_G38Target))); } let z = zFormat.format(propertyMmToUnit(properties.probe3_Thickness)); writeBlock(gFormat.format(92), z); // Set origin to initial position resetAll(); if (properties.toolChange3_Z != "") { // move up tool to safe height again after probing rapidMovementsZ(propertyMmToUnit(properties.toolChange3_Z), false); } flushMotions(); askUser("Detach ZProbe", "Probe", false); } }