Duet3D Logo Duet3D
    • Tags
    • Documentation
    • Order
    • Register
    • Login

    Running Gcode / CNC on Duet Wifi

    Scheduled Pinned Locked Moved
    CNC
    8
    51
    10.1k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Whitewolfundefined
      Whitewolf
      last edited by

      @o_lampe:

      Changing the gcode parser is not like adding another 3D-printer kinematic. ( AFAIK, they are all based on the same parser )
      I'd be happy, when the parser can do all_in_one without compromising, but I have my concernes.

      Although RRF is opensource, most of the users do not compile their own builds. As a RADDS owner I tried to follow the "how to make your own build" paper from RRF github, but failed to understand the paper. ( Seems written from an insider for insiders )
      David and Tony are concerned about the additional support required for two RRF branches, but guess what happens, when everyone builds his own FW, trying to taylor the HW-resources for their needs.

      Forgive me if I am wrong here because I have not yet dug into the firmware side of things (on my list to do) but if we were to implement a pluggable module to keep things clean, is there something in particular preventing an online compiler that pulls directly from github and downloads the resulting binaries based on the users selection? a tool similar to what there is now for downloading configuration.

      I would even be willing to develop it if need be. The concerns on this subject are valid just seems to me there are workable solutions to those concerns

      Exploring the universe wherever the tech blows

      1 Reply Last reply Reply Quote 0
      • dc42undefined
        dc42 administrators
        last edited by

        All of that is certainly possible, and thanks for your offer. However, our policy with RRF is that a single compiled binary should meet the needs of all users, except those with very specialised requirements, or those who want to make their own firmware modifications ("one binary to rule them all"). Other firmwares require some users to recompile the firmware to enable or disable functionality, either because they don't have sufficient load-time configurability, or because the hardware doesn't have enough flash or (more usually) enough RAM to support all of the functions available. In RRF we are only at 75% flash capacity even allowing for the 64Kb kept in reserve to help with flashing new firmware via the web interface - and we could reduce that 64Kb if we needed to. On the Duet WiFi and Duet Ethernet, we have spare RAM as well, and I haven't tried to economise on RAM usage yet (e.g. I use a 8K SD card write buffer, because it gives slightly better file upload speeds than 4K does).

        The other issue with using pluggable modules is that they only work well if new functionality can be implemented using the existing interfaces. In fact this is rarely the case. Almost every new features I add cuts across module boundaries and requires changes to the module interfaces.

        One particular situation in which a pluggable module might be useful is for supporting additional kinematics. I have refactored the kinematics code extensively in RRF 1.19 to make it easier to support new kinematics. But this still assumes that new kinematics can be implemented using a fixed set of interfaces. In practice, each kinematics appears to have its own specialised requirements. I still haven't worked out an interface for controlling homing behaviour that will work for all of Cartesian, Linear Delta, CoreXY and Scara kinematics. One option is to move much of the homing code into the kinematics classes; but that defeats the objective of making it straightforward to add classes for new kinematics.

        So my preference is to make the GCode parsing changes that CNC applications need - and support this extended parsing on 3D printers too unless there is a good reason not to - and to add support for CNC-specific gcodes. Most of this is not likely to happen before firmware version 1.21 because version 1.20 will be mostly about changing RRF for the Duet WiFi and Duet Ethernet to use a RTOS kernel.

        Duet WiFi hardware designer and firmware engineer
        Please do not ask me for Duet support via PM or email, use the forum
        http://www.escher3d.com, https://miscsolutions.wordpress.com

        1 Reply Last reply Reply Quote 0
        • o_lampeundefined
          o_lampe
          last edited by

          There's one minor thing I want to mention early:
          It seems that tool diameter, spindle speed, and feed rate have a "golden ratio". At least Fusion360 always changes the spindle speed, when I change tools or reduce feed rate.
          My point is, when the planner has to cut feed rate because max. feed rate is lower than gcode numbers, the planner has to reduce spindle RPM as well.
          That's only my observation, please feel free to correct me.

          1 Reply Last reply Reply Quote 0
          • deckingmanundefined
            deckingman
            last edited by

            @o_lampe:

            ….......................It seems that tool diameter, spindle speed, and feed rate have a "golden ratio". ............................

            That's very true in machining terms. There is always an optimum cutting speed, and by that I mean material removal speed. Either too slow, or too fast can result in the tool overheating due to friction, whereby it first goes blue then becomes brittle and breaks. (At least that was the case way back in the 60s when I was an apprentice).

            Ian
            https://somei3deas.wordpress.com/
            https://www.youtube.com/@deckingman

            1 Reply Last reply Reply Quote 0
            • Adamfilipundefined
              Adamfilip
              last edited by

              Looking forward to firmware 1.22 🙂

              1 Reply Last reply Reply Quote 0
              • o_lampeundefined
                o_lampe
                last edited by

                After rewriting several mach3-gcode files generated by Fusion360, I saw that there is a "brackets"-editor that allows us to write our own gcode-generator. That way, the firmware-branch for CNC would be much easier to write. It could even stay untouched. Just a few commands would be missing.

                I'm wondering, if other CAM-software allow custom-machine gcode generator implementation, too? If so, which one is the most common between Duet/RRF users?

                Here's the fusion360_mach3 code generator: ( sorry, I don't know how to attach files )

                /**
                  Copyright (C) 2012-2017 by Autodesk, Inc.
                  All rights reserved.
                
                  Mach3Mill post processor configuration.
                
                  $Revision: 41432 76c1b5f6cafe0274f1c22e1d96e245a2f903b3b1 $
                  $Date: 2017-05-17 22:55:14 $
                
                  FORKID {AE2102AB-B86A-4aa7-8E9B-F0B6935D4E9F}
                */
                
                description = "Generic Mach3Mill";
                vendor = "Artsoft";
                vendorUrl = "http://www.machsupport.com";
                legal = "Copyright (C) 2012-2017 by Autodesk, Inc.";
                certificationLevel = 2;
                minimumRevision = 24000;
                
                longDescription = "Generic milling post for Mach3.";
                
                extension = "gco";
                setCodePage("ascii");
                
                capabilities = CAPABILITY_MILLING;
                tolerance = spatial(0.002, MM);
                
                minimumChordLength = spatial(0.01, MM);
                minimumCircularRadius = spatial(0.01, MM);
                maximumCircularRadius = spatial(1000, MM);
                minimumCircularSweep = toRad(0.01);
                maximumCircularSweep = toRad(180);
                allowHelicalMoves = true;
                allowedCircularPlanes = undefined; // allow any circular motion
                
                // user-defined properties
                properties = {
                  writeMachine: true, // write machine
                  writeTools: true, // writes the tools
                  useG28: false, // disable to avoid G28 output for safe machine retracts - when disabled you must manually ensure safe retracts
                  useM6: true, // disable to avoid M6 output - preload is also disabled when M6 is disabled
                  preloadTool: false, // preloads next tool on tool change if any
                  showSequenceNumbers: false, // show sequence numbers
                  sequenceNumberStart: 10, // first sequence number
                  sequenceNumberIncrement: 5, // increment for sequence numbers
                  optionalStop: true, // optional stop
                  separateWordsWithSpace: true, // specifies that the words should be separated with a white space
                  useRadius: false, // specifies that arcs should be output using the radius (R word) instead of the I, J, and K words.
                  dwellInSeconds: true // specifies the unit for dwelling: true:seconds and false:milliseconds.
                };
                
                var permittedCommentChars = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,=_-";
                
                var mapCoolantTable = new Table(
                  [9, 8, 7],
                  {initial:COOLANT_OFF, force:true},
                  "Invalid coolant mode"
                );
                
                var nFormat = createFormat({prefix:"N", decimals:0});
                var gFormat = createFormat({prefix:"G", decimals:1});
                var mFormat = createFormat({prefix:"M", decimals:0});
                var hFormat = createFormat({prefix:"H", decimals:0});
                var pFormat = createFormat({prefix:"P", decimals:(unit == MM ? 3 : 4), scale:0.5});
                var xyzFormat = createFormat({decimals:(unit == MM ? 3 : 4), forceDecimal:true});
                var rFormat = xyzFormat; // radius
                var abcFormat = createFormat({decimals:3, forceDecimal:true, scale:DEG});
                var feedFormat = createFormat({decimals:(unit == MM ? 0 : 1), forceDecimal:true});
                var inverseTimeFormat = createFormat({decimals:4, forceDecimal:true});
                var toolFormat = createFormat({decimals:0});
                var rpmFormat = createFormat({decimals:0});
                var secFormat = createFormat({decimals:3, forceDecimal:true}); // seconds - range 0.001-99999.999
                var milliFormat = createFormat({decimals:0}); // milliseconds // range 1-9999
                var taperFormat = createFormat({decimals:1, scale:DEG});
                
                var xOutput = createVariable({prefix:"X"}, xyzFormat);
                var yOutput = createVariable({prefix:"Y"}, xyzFormat);
                var zOutput = createVariable({prefix:"Z"}, xyzFormat);
                var aOutput = createVariable({prefix:"A"}, abcFormat);
                var bOutput = createVariable({prefix:"B"}, abcFormat);
                var cOutput = createVariable({prefix:"C"}, abcFormat);
                var feedOutput = createVariable({prefix:"F"}, feedFormat);
                var inverseTimeOutput = createVariable({prefix:"F", force:true}, inverseTimeFormat);
                var sOutput = createVariable({prefix:"S", force:true}, rpmFormat);
                var pOutput = createVariable({}, pFormat);
                
                // circular output
                var iOutput = createReferenceVariable({prefix:"I", force:true}, xyzFormat);
                var jOutput = createReferenceVariable({prefix:"J", force:true}, xyzFormat);
                var kOutput = createReferenceVariable({prefix:"K", force:true}, xyzFormat);
                
                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
                var gCycleModal = createModal({}, gFormat); // modal group 9 // G81, ...
                var gRetractModal = createModal({}, gFormat); // modal group 10 // G98-99
                
                var WARNING_WORK_OFFSET = 0;
                
                // collected state
                var sequenceNumber;
                var currentWorkOffset;
                
                /**
                  Writes the specified block.
                */
                function writeBlock() {
                  if (properties.showSequenceNumbers) {
                    writeWords2(nFormat.format(sequenceNumber % 100000), arguments);
                    sequenceNumber += properties.sequenceNumberIncrement;
                  } else {
                    writeWords(arguments);
                  }
                }
                
                /**
                  Output a comment.
                */
                function writeComment(text) {
                  writeln("(" + filterText(String(text).toUpperCase(), permittedCommentChars) + ")");
                }
                
                function onOpen() {
                  if (properties.useRadius) {
                    maximumCircularSweep = toRad(90); // avoid potential center calculation errors for CNC
                  }
                
                  if (false) {
                    var aAxis = createAxis({coordinate:0, table:true, axis:[-1, 0, 0], cyclic:true, preference:1});
                    machineConfiguration = new MachineConfiguration(aAxis);
                
                    setMachineConfiguration(machineConfiguration);
                    optimizeMachineAngles2(1); // map tip mode
                  }
                
                  if (!machineConfiguration.isMachineCoordinate(0)) {
                    aOutput.disable();
                  }
                  if (!machineConfiguration.isMachineCoordinate(1)) {
                    bOutput.disable();
                  }
                  if (!machineConfiguration.isMachineCoordinate(2)) {
                    cOutput.disable();
                  }
                
                  if (!properties.separateWordsWithSpace) {
                    setWordSeparator("");
                  }
                
                  sequenceNumber = properties.sequenceNumberStart;
                
                  if (programName) {
                    writeComment(programName);
                  }
                  if (programComment) {
                    writeComment(programComment);
                  }
                
                  // dump machine configuration
                  var vendor = machineConfiguration.getVendor();
                  var model = machineConfiguration.getModel();
                  var description = machineConfiguration.getDescription();
                
                  if (properties.writeMachine && (vendor || model || description)) {
                    writeComment(localize("Machine"));
                    if (vendor) {
                      writeComment("  " + localize("vendor") + ": " + vendor);
                    }
                    if (model) {
                      writeComment("  " + localize("model") + ": " + model);
                    }
                    if (description) {
                      writeComment("  " + localize("description") + ": "  + description);
                    }
                  }
                
                  // dump tool information
                  if (properties.writeTools) {
                    var zRanges = {};
                    if (is3D()) {
                      var numberOfSections = getNumberOfSections();
                      for (var i = 0; i < numberOfSections; ++i) {
                        var section = getSection(i);
                        var zRange = section.getGlobalZRange();
                        var tool = section.getTool();
                        if (zRanges[tool.number]) {
                          zRanges[tool.number].expandToRange(zRange);
                        } else {
                          zRanges[tool.number] = zRange;
                        }
                      }
                    }
                
                    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) + " " +
                          localize("CR") + "=" + xyzFormat.format(tool.cornerRadius);
                        if ((tool.taperAngle > 0) && (tool.taperAngle < Math.PI)) {
                          comment += " " + localize("TAPER") + "=" + taperFormat.format(tool.taperAngle) + localize("deg");
                        }
                        if (zRanges[tool.number]) {
                          comment += " - " + localize("ZMIN") + "=" + xyzFormat.format(zRanges[tool.number].getMinimum());
                        }
                        comment += " - " + getToolTypeName(tool.type);
                        writeComment(comment);
                      }
                    }
                  }
                
                  if (false) {
                    // check for duplicate tool number
                    for (var i = 0; i < getNumberOfSections(); ++i) {
                      var sectioni = getSection(i);
                      var tooli = sectioni.getTool();
                      for (var j = i + 1; j < getNumberOfSections(); ++j) {
                        var sectionj = getSection(j);
                        var toolj = sectionj.getTool();
                        if (tooli.number == toolj.number) {
                          if (xyzFormat.areDifferent(tooli.diameter, toolj.diameter) ||
                              xyzFormat.areDifferent(tooli.cornerRadius, toolj.cornerRadius) ||
                              abcFormat.areDifferent(tooli.taperAngle, toolj.taperAngle) ||
                              (tooli.numberOfFlutes != toolj.numberOfFlutes)) {
                            error(
                              subst(
                                localize("Using the same tool number for different cutter geometry for operation '%1' and '%2'."),
                                sectioni.hasParameter("operation-comment") ? sectioni.getParameter("operation-comment") : ("#" + (i + 1)),
                                sectionj.hasParameter("operation-comment") ? sectionj.getParameter("operation-comment") : ("#" + (j + 1))
                              )
                            );
                            return;
                          }
                        }
                      }
                    }
                  }
                
                  if ((getNumberOfSections() > 0) && (getSection(0).workOffset == 0)) {
                    for (var i = 0; i < getNumberOfSections(); ++i) {
                      if (getSection(i).workOffset > 0) {
                        error(localize("Using multiple work offsets is not possible if the initial work offset is 0."));
                        return;
                      }
                    }
                  }
                
                  // absolute coordinates and feed per min
                  writeBlock(gAbsIncModal.format(90), gFeedModeModal.format(94), gFormat.format(91.1), gFormat.format(40), gFormat.format(49), gPlaneModal.format(17));
                
                  switch (unit) {
                  case IN:
                    writeBlock(gUnitModal.format(20));
                    break;
                  case MM:
                    writeBlock(gUnitModal.format(21));
                    break;
                  }
                }
                
                function onComment(message) {
                  var comments = String(message).split(";");
                  for (comment in comments) {
                    writeComment(comments[comment]);
                  }
                }
                
                /** Force output of X, Y, and Z. */
                function forceXYZ() {
                  xOutput.reset();
                  yOutput.reset();
                  zOutput.reset();
                }
                
                /** Force output of A, B, and C. */
                function forceABC() {
                  aOutput.reset();
                  bOutput.reset();
                  cOutput.reset();
                }
                
                /** Force output of X, Y, Z, A, B, C, and F on next output. */
                function forceAny() {
                  forceXYZ();
                  forceABC();
                  feedOutput.reset();
                }
                
                var currentWorkPlaneABC = undefined;
                
                function forceWorkPlane() {
                  currentWorkPlaneABC = undefined;
                }
                
                function setWorkPlane(abc) {
                  if (!machineConfiguration.isMultiAxisConfiguration()) {
                    return; // ignore
                  }
                
                  if (!((currentWorkPlaneABC == undefined) ||
                        abcFormat.areDifferent(abc.x, currentWorkPlaneABC.x) ||
                        abcFormat.areDifferent(abc.y, currentWorkPlaneABC.y) ||
                        abcFormat.areDifferent(abc.z, currentWorkPlaneABC.z))) {
                    return; // no change
                  }
                
                  onCommand(COMMAND_UNLOCK_MULTI_AXIS);
                
                  // NOTE: add retract here
                
                  writeBlock(
                    gMotionModal.format(0),
                    conditional(machineConfiguration.isMachineCoordinate(0), "A" + abcFormat.format(abc.x)),
                    conditional(machineConfiguration.isMachineCoordinate(1), "B" + abcFormat.format(abc.y)),
                    conditional(machineConfiguration.isMachineCoordinate(2), "C" + abcFormat.format(abc.z))
                  );
                
                  onCommand(COMMAND_LOCK_MULTI_AXIS);
                
                  currentWorkPlaneABC = abc;
                }
                
                var closestABC = false; // choose closest machine angles
                var currentMachineABC;
                
                function getWorkPlaneMachineABC(workPlane) {
                  var W = workPlane; // map to global frame
                
                  var abc = machineConfiguration.getABC(W);
                  if (closestABC) {
                    if (currentMachineABC) {
                      abc = machineConfiguration.remapToABC(abc, currentMachineABC);
                    } else {
                      abc = machineConfiguration.getPreferredABC(abc);
                    }
                  } else {
                    abc = machineConfiguration.getPreferredABC(abc);
                  }
                
                  try {
                    abc = machineConfiguration.remapABC(abc);
                    currentMachineABC = abc;
                  } catch (e) {
                    error(
                      localize("Machine angles not supported") + ":"
                      + conditional(machineConfiguration.isMachineCoordinate(0), " A" + abcFormat.format(abc.x))
                      + conditional(machineConfiguration.isMachineCoordinate(1), " B" + abcFormat.format(abc.y))
                      + conditional(machineConfiguration.isMachineCoordinate(2), " C" + abcFormat.format(abc.z))
                    );
                  }
                
                  var direction = machineConfiguration.getDirection(abc);
                  if (!isSameDirection(direction, W.forward)) {
                    error(localize("Orientation not supported."));
                  }
                
                  if (!machineConfiguration.isABCSupported(abc)) {
                    error(
                      localize("Work plane is not supported") + ":"
                      + conditional(machineConfiguration.isMachineCoordinate(0), " A" + abcFormat.format(abc.x))
                      + conditional(machineConfiguration.isMachineCoordinate(1), " B" + abcFormat.format(abc.y))
                      + conditional(machineConfiguration.isMachineCoordinate(2), " C" + abcFormat.format(abc.z))
                    );
                  }
                
                  var tcp = true;
                  if (tcp) {
                    setRotation(W); // TCP mode
                  } else {
                    var O = machineConfiguration.getOrientation(abc);
                    var R = machineConfiguration.getRemainingOrientation(abc, W);
                    setRotation(R);
                  }
                
                  return abc;
                }
                
                function onSection() {
                  var insertToolCall = isFirstSection() ||
                    currentSection.getForceToolChange && currentSection.getForceToolChange() ||
                    (tool.number != getPreviousSection().getTool().number);
                
                  var retracted = false; // specifies that the tool has been retracted to the safe plane
                  var newWorkOffset = isFirstSection() ||
                    (getPreviousSection().workOffset != currentSection.workOffset); // work offset changes
                  var newWorkPlane = isFirstSection() ||
                    !isSameDirection(getPreviousSection().getGlobalFinalToolAxis(), currentSection.getGlobalInitialToolAxis());
                  if (insertToolCall || newWorkOffset || newWorkPlane) {
                
                    if (properties.useG28) {
                      // retract to safe plane
                      retracted = true;
                      writeBlock(gFormat.format(28), gAbsIncModal.format(91), "Z" + xyzFormat.format(machineConfiguration.getRetractPlane())); // retract
                      writeBlock(gAbsIncModal.format(90));
                      zOutput.reset();
                    }
                  }
                
                  writeln("");
                
                  if (hasParameter("operation-comment")) {
                    var comment = getParameter("operation-comment");
                    if (comment) {
                      writeComment(comment);
                    }
                  }
                
                  if (insertToolCall) {
                    forceWorkPlane();
                
                    onCommand(COMMAND_STOP_SPINDLE);
                    onCommand(COMMAND_COOLANT_OFF);
                
                    if (!isFirstSection() && properties.optionalStop) {
                      onCommand(COMMAND_OPTIONAL_STOP);
                    }
                
                    if (tool.number > 256) {
                      warning(localize("Tool number exceeds maximum value."));
                    }
                
                    if (properties.useM6) {
                      writeBlock("T" + toolFormat.format(tool.number), mFormat.format(6));
                    } else {
                      writeBlock("T" + toolFormat.format(tool.number));
                    }
                    if (tool.comment) {
                      writeComment(tool.comment);
                    }
                    var showToolZMin = false;
                    if (showToolZMin) {
                      if (is3D()) {
                        var numberOfSections = getNumberOfSections();
                        var zRange = currentSection.getGlobalZRange();
                        var number = tool.number;
                        for (var i = currentSection.getId() + 1; i < numberOfSections; ++i) {
                          var section = getSection(i);
                          if (section.getTool().number != number) {
                            break;
                          }
                          zRange.expandToRange(section.getGlobalZRange());
                        }
                        writeComment(localize("ZMIN") + "=" + zRange.getMinimum());
                      }
                    }
                
                    if (properties.preloadTool && properties.useM6) {
                      var nextTool = getNextTool(tool.number);
                      if (nextTool) {
                        writeBlock("T" + toolFormat.format(nextTool.number));
                      } else {
                        // preload first tool
                        var section = getSection(0);
                        var firstToolNumber = section.getTool().number;
                        if (tool.number != firstToolNumber) {
                          writeBlock("T" + toolFormat.format(firstToolNumber));
                        }
                      }
                    }
                  }
                
                  if (insertToolCall ||
                      isFirstSection() ||
                      (rpmFormat.areDifferent(tool.spindleRPM, sOutput.getCurrent())) ||
                      (tool.clockwise != getPreviousSection().getTool().clockwise)) {
                    if (tool.spindleRPM < 1) {
                      error(localize("Spindle speed out of range."));
                      return;
                    }
                    if (tool.spindleRPM > 99999) {
                      warning(localize("Spindle speed exceeds maximum value."));
                    }
                    writeBlock(
                      sOutput.format(tool.spindleRPM), mFormat.format(tool.clockwise ? 3 : 4)
                    );
                  }
                
                  // wcs
                  if (insertToolCall) { // force work offset when changing tool
                    currentWorkOffset = undefined;
                  }
                  var workOffset = currentSection.workOffset;
                  if (workOffset == 0) {
                    warningOnce(localize("Work offset has not been specified. Using G54 as WCS."), WARNING_WORK_OFFSET);
                    workOffset = 1;
                  }
                  if (workOffset > 0) {
                    if (workOffset > 6) {
                      var p = workOffset; // 1->... // G59 P1 is the same as G54 and so on
                      if (p > 254) {
                        error(localize("Work offset out of range."));
                      } else {
                        if (workOffset != currentWorkOffset) {
                          writeBlock(gFormat.format(59), "P" + p); // G59 P
                          currentWorkOffset = workOffset;
                        }
                      }
                    } else {
                      if (workOffset != currentWorkOffset) {
                        writeBlock(gFormat.format(53 + workOffset)); // G54->G59
                        currentWorkOffset = workOffset;
                      }
                    }
                  }
                
                  forceXYZ();
                
                  if (machineConfiguration.isMultiAxisConfiguration()) { // use 5-axis indexing for multi-axis mode
                    // set working plane after datum shift
                
                    var abc = new Vector(0, 0, 0);
                    if (currentSection.isMultiAxis()) {
                      forceWorkPlane();
                      cancelTransformation();
                    } else {
                      abc = getWorkPlaneMachineABC(currentSection.workPlane);
                    }
                    setWorkPlane(abc);
                  } else { // pure 3D
                    var remaining = currentSection.workPlane;
                    if (!isSameDirection(remaining.forward, new Vector(0, 0, 1))) {
                      error(localize("Tool orientation is not supported."));
                      return;
                    }
                    setRotation(remaining);
                  }
                
                  // set coolant after we have positioned at Z
                  {
                    var c = mapCoolantTable.lookup(tool.coolant);
                    if (c) {
                      writeBlock(mFormat.format(c));
                    } else {
                      warning(localize("Coolant not supported."));
                    }
                  }
                
                  forceAny();
                  gMotionModal.reset();
                
                  var initialPosition = getFramePosition(currentSection.getInitialPosition());
                  if (!retracted) {
                    if (getCurrentPosition().z < initialPosition.z) {
                      writeBlock(gMotionModal.format(0), zOutput.format(initialPosition.z));
                    }
                  }
                
                  if (insertToolCall || retracted) {
                    var lengthOffset = tool.lengthOffset;
                    if (lengthOffset > 256) {
                      error(localize("Length offset out of range."));
                      return;
                    }
                
                    gMotionModal.reset();
                    writeBlock(gPlaneModal.format(17));
                
                    if (!machineConfiguration.isHeadConfiguration()) {
                      writeBlock(
                        gAbsIncModal.format(90),
                        gMotionModal.format(0), xOutput.format(initialPosition.x), yOutput.format(initialPosition.y)
                      );
                      writeBlock(gMotionModal.format(0), gFormat.format(43), zOutput.format(initialPosition.z), hFormat.format(lengthOffset));
                    } else {
                      writeBlock(
                        gAbsIncModal.format(90),
                        gMotionModal.format(0),
                        gFormat.format(43), xOutput.format(initialPosition.x),
                        yOutput.format(initialPosition.y),
                        zOutput.format(initialPosition.z), hFormat.format(lengthOffset)
                      );
                    }
                  } else {
                    writeBlock(
                      gAbsIncModal.format(90),
                      gMotionModal.format(0),
                      xOutput.format(initialPosition.x),
                      yOutput.format(initialPosition.y)
                    );
                  }
                }
                
                function onDwell(seconds) {
                  if (seconds > 99999.999) {
                    warning(localize("Dwelling time is out of range."));
                  }
                  if (properties.dwellInSeconds) {
                    writeBlock(gFormat.format(4), "P" + secFormat.format(seconds));
                  } else {
                    milliseconds = clamp(1, seconds * 1000, 99999999);
                    writeBlock(gFormat.format(4), "P" + milliFormat.format(milliseconds));
                  }
                }
                
                function onSpindleSpeed(spindleSpeed) {
                  writeBlock(sOutput.format(spindleSpeed));
                }
                
                function onCycle() {
                  writeBlock(gPlaneModal.format(17));
                }
                
                function getCommonCycle(x, y, z, r) {
                  forceXYZ();
                  return [xOutput.format(x), yOutput.format(y),
                    zOutput.format(z),
                    "R" + xyzFormat.format(r)];
                }
                
                function onCyclePoint(x, y, z) {
                  if (isFirstCyclePoint()) {
                    repositionToCycleClearance(cycle, x, y, z);
                
                    // return to initial Z which is clearance plane and set absolute mode
                
                    var F = cycle.feedrate;
                    var P = (cycle.dwell == 0) ? 0 : cycle.dwell; // in seconds
                
                    switch (cycleType) {
                    case "drilling":
                      writeBlock(
                        gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(81),
                        getCommonCycle(x, y, z, cycle.retract),
                        feedOutput.format(F)
                      );
                      break;
                    case "counter-boring":
                      if (P > 0) {
                        writeBlock(
                          gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(82),
                          getCommonCycle(x, y, z, cycle.retract),
                          "P" + secFormat.format(P),
                          feedOutput.format(F)
                        );
                      } else {
                        writeBlock(
                          gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(81),
                          getCommonCycle(x, y, z, cycle.retract),
                          feedOutput.format(F)
                        );
                      }
                      break;
                    case "chip-breaking":
                      // cycle.accumulatedDepth is ignored
                      if (P > 0) {
                        expandCyclePoint(x, y, z);
                      } else {
                        writeBlock(
                          gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(73),
                          getCommonCycle(x, y, z, cycle.retract),
                          "Q" + xyzFormat.format(cycle.incrementalDepth),
                          feedOutput.format(F)
                        );
                      }
                      break;
                    case "deep-drilling":
                      if (P > 0) {
                        expandCyclePoint(x, y, z);
                      } else {
                        writeBlock(
                          gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(83),
                          getCommonCycle(x, y, z, cycle.retract),
                          "Q" + xyzFormat.format(cycle.incrementalDepth),
                          // conditional(P > 0, "P" + secFormat.format(P)),
                          feedOutput.format(F)
                        );
                      }
                      break;
                    case "tapping":
                      if (tool.type == TOOL_TAP_LEFT_HAND) {
                        expandCyclePoint(x, y, z);
                      } else {
                        if (!F) {
                          F = tool.getTappingFeedrate();
                        }
                        writeBlock(mFormat.format(29), sOutput.format(tool.spindleRPM));
                        writeBlock(
                          gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(84),
                          getCommonCycle(x, y, z, cycle.retract),
                          feedOutput.format(F)
                        );
                      }
                      break;
                    case "left-tapping":
                      expandCyclePoint(x, y, z);
                      break;
                    case "right-tapping":
                      if (!F) {
                        F = tool.getTappingFeedrate();
                      }
                      writeBlock(mFormat.format(29), sOutput.format(tool.spindleRPM));
                      writeBlock(
                        gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(84),
                        getCommonCycle(x, y, z, cycle.retract),
                        feedOutput.format(F)
                      );
                      break;
                    case "fine-boring":
                      writeBlock(
                        gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(76),
                        getCommonCycle(x, y, z, cycle.retract),
                        "I" + xyzFormat.format(cycle.shift),
                        "J" + xyzFormat.format(0),
                        "P" + secFormat.format(P),
                        // "Q" + xyzFormat.format(cycle.shift),
                        feedOutput.format(F)
                      );
                      break;
                    case "back-boring":
                      var dx = (gPlaneModal.getCurrent() == 19) ? cycle.backBoreDistance : 0;
                      var dy = (gPlaneModal.getCurrent() == 18) ? cycle.backBoreDistance : 0;
                      var dz = (gPlaneModal.getCurrent() == 17) ? cycle.backBoreDistance : 0;
                      writeBlock(
                        gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(87),
                        getCommonCycle(x - dx, y - dy, z - dz, cycle.bottom),
                        "I" + xyzFormat.format(cycle.shift),
                        "J" + xyzFormat.format(0),
                        "P" + secFormat.format(P),
                        feedOutput.format(F)
                      );
                      break;
                    case "reaming":
                      if (P > 0) {
                        writeBlock(
                          gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(89),
                          getCommonCycle(x, y, z, cycle.retract),
                          "P" + secFormat.format(P),
                          feedOutput.format(F)
                        );
                      } else {
                        writeBlock(
                          gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(85),
                          getCommonCycle(x, y, z, cycle.retract),
                          feedOutput.format(F)
                        );
                      }
                      break;
                    case "stop-boring":
                      writeBlock(
                        gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(86),
                        getCommonCycle(x, y, z, cycle.retract),
                        "P" + secFormat.format(P),
                        feedOutput.format(F)
                      );
                      break;
                    case "manual-boring":
                      writeBlock(
                        gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(88),
                        getCommonCycle(x, y, z, cycle.retract),
                        "P" + secFormat.format(P),
                        feedOutput.format(F)
                      );
                      break;
                    case "boring":
                      if (P > 0) {
                        writeBlock(
                          gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(89),
                          getCommonCycle(x, y, z, cycle.retract),
                          "P" + secFormat.format(P),
                          feedOutput.format(F)
                        );
                      } else {
                        writeBlock(
                          gRetractModal.format(98), gAbsIncModal.format(90), gCycleModal.format(85),
                          getCommonCycle(x, y, z, cycle.retract),
                          feedOutput.format(F)
                        );
                      }
                      break;
                    default:
                      expandCyclePoint(x, y, z);
                    }
                  } else {
                    if (cycleExpanded) {
                      expandCyclePoint(x, y, z);
                    } else {
                      writeBlock(xOutput.format(x), yOutput.format(y));
                    }
                  }
                }
                
                function onCycleEnd() {
                  if (!cycleExpanded) {
                    writeBlock(gCycleModal.format(80));
                    zOutput.reset();
                  }
                }
                
                var pendingRadiusCompensation = -1;
                
                function onRadiusCompensation() {
                  pendingRadiusCompensation = radiusCompensation;
                }
                
                function onRapid(_x, _y, _z) {
                  var x = xOutput.format(_x);
                  var y = yOutput.format(_y);
                  var z = zOutput.format(_z);
                  if (x || y || z) {
                    if (pendingRadiusCompensation >= 0) {
                      error(localize("Radius compensation mode cannot be changed at rapid traversal."));
                      return;
                    }
                    writeBlock(gMotionModal.format(0), x, y, z);
                    feedOutput.reset();
                  }
                }
                
                function onLinear(_x, _y, _z, feed) {
                  var x = xOutput.format(_x);
                  var y = yOutput.format(_y);
                  var z = zOutput.format(_z);
                  var f = feedOutput.format(feed);
                  if (x || y || z) {
                    if (pendingRadiusCompensation >= 0) {
                      pendingRadiusCompensation = -1;
                      writeBlock(gPlaneModal.format(17));
                      switch (radiusCompensation) {
                      case RADIUS_COMPENSATION_LEFT:
                        pOutput.reset();
                        writeBlock(gMotionModal.format(1), gFormat.format(41), x, y, z, f, pOutput.format(tool.diameter));
                        break;
                      case RADIUS_COMPENSATION_RIGHT:
                        pOutput.reset();
                        writeBlock(gMotionModal.format(1), gFormat.format(42), x, y, z, f, pOutput.format(tool.diameter));
                        break;
                      default:
                        writeBlock(gMotionModal.format(1), gFormat.format(40), x, y, z, f);
                      }
                    } else {
                      writeBlock(gMotionModal.format(1), x, y, z, f);
                    }
                  } else if (f) {
                    if (getNextRecord().isMotion()) { // try not to output feed without motion
                      feedOutput.reset(); // force feed on next line
                    } else {
                      writeBlock(gMotionModal.format(1), f);
                    }
                  }
                }
                
                function onRapid5D(_x, _y, _z, _a, _b, _c) {
                  if (!currentSection.isOptimizedForMachine()) {
                    error(localize("This post configuration has not been customized for 5-axis simultaneous toolpath."));
                    return;
                  }
                  if (pendingRadiusCompensation >= 0) {
                    error(localize("Radius compensation mode cannot be changed at rapid traversal."));
                    return;
                  }
                  var x = xOutput.format(_x);
                  var y = yOutput.format(_y);
                  var z = zOutput.format(_z);
                  var a = aOutput.format(_a);
                  var b = bOutput.format(_b);
                  var c = cOutput.format(_c);
                  writeBlock(gMotionModal.format(0), x, y, z, a, b, c);
                  feedOutput.reset();
                }
                
                function onLinear5D(_x, _y, _z, _a, _b, _c, feed) {
                  if (!currentSection.isOptimizedForMachine()) {
                    error(localize("This post configuration has not been customized for 5-axis simultaneous toolpath."));
                    return;
                  }
                  if (pendingRadiusCompensation >= 0) {
                    error(localize("Radius compensation cannot be activated/deactivated for 5-axis move."));
                    return;
                  }
                  var x = xOutput.format(_x);
                  var y = yOutput.format(_y);
                  var z = zOutput.format(_z);
                  var a = aOutput.format(_a);
                  var b = bOutput.format(_b);
                  var c = cOutput.format(_c);
                
                  // get feedrate number
                  var f = {frn:0, fmode:0};
                  if (a || b || c) {
                    f = getMultiaxisFeed(_x, _y, _z, _a, _b, _c, feed);
                  } else {
                    f.frn = feedOutput.format(feed);
                    f.fmode = 94;
                  }
                
                  if (x || y || z || a || b || c) {
                    writeBlock(gFeedModeModal.format(f.fmode), gMotionModal.format(1), x, y, z, a, b, c, f.frn);
                  } else if (f.frn) {
                    if (getNextRecord().isMotion()) { // try not to output feed without motion
                      feedOutput.reset(); // force feed on next line
                    } else {
                      writeBlock(gFeedModeModal.format(f.fmode), gMotionModal.format(1), f.frn);
                    }
                  }
                }
                
                // Start of multi-axis feedrate logic
                /***** Be sure to add 'useInverseTime' to post properties if necessary. *****/
                /***** 'inverseTimeOutput' must be defined. *****/
                /***** 'headOffset' should be defined when a head rotary axis is defined. *****/
                /***** The feedrate mode must be included in motion block output (linear, circular, etc. *****/
                var dpmBPW = 0.1; // ratio of rotary accuracy to linear accuracy for DPM calculations
                var inverseTimeUnits = 1.0; // 1.0 = minutes, 60.0 = seconds
                var maxInverseTime = 999999.9999; // maximum value to output for Inverse Time feeds
                
                /** Calculate the multi-axis feedrate number. */
                function getMultiaxisFeed(_x, _y, _z, _a, _b, _c, feed) {
                  var f = {frn:0, fmode:0};
                  if (feed <= 0) {
                    error(localize("Feedrate is less than or equal to 0."));
                    return f;
                  }
                
                  var length = getMoveLength(_x, _y, _z, _a, _b, _c);
                
                  if (true) { // inverse time
                    f.frn = inverseTimeOutput.format(getInverseTime(length[0], feed));
                    f.fmode = 93;
                    feedOutput.reset();
                  } else { // degrees per minute
                    f.frn = feedOutput.format(getFeedDPM(length, feed));
                    f.fmode = 94;
                  }
                  return f;
                }
                
                /** Calculate the DPM feedrate number. */
                function getFeedDPM(_moveLength, _feed) {
                  // moveLength[0] = Tool tip, [1] = XYZ, [2] = ABC
                
                  if (false) { // TCP mode is supported, output feed as FPM
                    return feed;
                  } else { // DPM feedrate calculation
                    var moveTime = ((_moveLength[0] < 1.e-6) ? 0.001 : _moveLength[0]) / _feed;
                    var length = Math.sqrt(Math.pow(_moveLength[1], 2.0) + Math.pow((toDeg(_moveLength[2]) * dpmBPW), 2.0));
                    return length / moveTime;
                  }
                }
                
                /** Calculate the Inverse time feedrate number. */
                function getInverseTime(_length, _feed) {
                  var inverseTime;
                  if (_length < 1.e-6) { // tool doesn't move
                    if (typeof maxInverseTime === "number") {
                      inverseTime = maxInverseTime;
                    } else {
                      inverseTime = 999999;
                    }
                  } else {
                    inverseTime = _feed / _length / inverseTimeUnits;
                    if (typeof maxInverseTime === "number") {
                      if (inverseTime > maxInverseTime) {
                        inverseTime = maxInverseTime;
                      }
                    }
                  }
                  return inverseTime;
                }
                
                /** Calculate the distance of the tool position to the center of a rotary axis. */
                function getRotaryRadius(center, direction, toolPosition) {
                  var normal = direction.getNormalized();
                  var d1 = toolPosition.x - center.x;
                  var d2 = toolPosition.y - center.y;
                  var d3 = toolPosition.z - center.z;
                  var radius = Math.sqrt(
                    Math.pow((d1 * normal.y) - (d2 * normal.x), 2.0) +
                    Math.pow((d2 * normal.z) - (d3 * normal.y), 2.0) +
                    Math.pow((d3 * normal.x) - (d1 * normal.z), 2.0)
                   );
                   return radius;
                }
                
                /** Calculate the linear distance based on the rotation of a rotary axis. */
                function getRadialDistance(axis, startTool, endTool, startABC, endABC) {
                  // rotary axis does not exist
                  if (!axis.isEnabled()) {
                    return 0.0;
                  }
                
                  // calculate the rotary center based on head/table
                  var center;
                  if (axis.isHead()) {
                    var pivot;
                    if (typeof headOffset === "number") {
                      pivot = headOffset;
                    } else {
                      pivot = tool.getBodyLength();
                    }
                    center = Vector.sum(startTool, Vector.product(machineConfiguration.getSpindleAxis(), pivot));
                    center = Vector.sum(center, axis.getOffset());
                  } else {
                    center = axis.getOffset();
                  }
                
                  // calculate the radius of the tool end point compared to the rotary center
                  var startRadius = getRotaryRadius(center, axis.getEffectiveAxis(), startTool);
                  var endRadius = getRotaryRadius(center, axis.getEffectiveAxis(), endTool);
                
                  // calculate length of radial move
                  var radius = Math.max(startRadius, endRadius);
                  var delta = Math.abs(endABC[axis.getCoordinate()] - startABC[axis.getCoordinate()]);
                  if (delta > Math.PI) {
                    delta = 2*Math.PI - delta;
                  }
                  var radialLength = (2 * Math.PI * radius) * (delta / (2 * Math.PI));
                  return radialLength;
                }
                
                /** Calculate tooltip, XYZ, and rotary move lengths. */
                function getMoveLength(_x, _y, _z, _a, _b, _c) {
                  // get starting and ending positions
                  var moveLength = new Array();
                  var startTool;
                  var endTool;
                  var startXYZ;
                  var endXYZ;
                  var startABC = new Array(getCurrentDirection().x, getCurrentDirection().y, getCurrentDirection().z);
                  var endABC = new Array(_a, _b, _c);
                
                  if (currentSection.getOptimizedTCPMode() == 0) {
                    startTool = getCurrentPosition();
                    endTool = new Vector(_x, _y, _z);
                    startXYZ = machineConfiguration.getOrientation(startABC).getTransposed().multiply(startTool);
                    endXYZ = machineConfiguration.getOrientation(endABC).getTransposed().multiply(endTool);
                  } else {
                    startXYZ = getCurrentPosition();
                    endXYZ = new Vector(_x, _y, _z);
                    startTool = machineConfiguration.getOrientation(getCurrentDirection()).multiply(startXYZ);
                    endTool = machineConfiguration.getOrientation(new Vector(_a, _b, _c)).multiply(endXYZ);
                  }
                
                  // calculate the radial portion of the move
                  var radialLength = Math.sqrt(
                    Math.pow(getRadialDistance(machineConfiguration.getAxisU(), startTool, endTool, startABC, endABC), 2.0) +
                    Math.pow(getRadialDistance(machineConfiguration.getAxisV(), startTool, endTool, startABC, endABC), 2.0) +
                    Math.pow(getRadialDistance(machineConfiguration.getAxisW(), startTool, endTool, startABC, endABC), 2.0)
                  );
                
                  // calculate the lengths of move
                  // tool tip distance is the move distance based on a combination of linear and rotary axes movement
                  var linearLength = Vector.diff(endXYZ, startXYZ).length;
                  moveLength[0] = linearLength + radialLength;
                  moveLength[1] = Vector.diff(endXYZ, startXYZ).length;
                  moveLength[2] = 0;
                  for (var i = 0; i < 3; ++i) {
                    var delta = Math.abs(endABC[i] - startABC[i]);
                    if (delta > Math.PI) {
                      delta = 2*Math.PI - delta;
                    }
                    moveLength[2] += Math.pow(delta, 2.0);
                  }
                  moveLength[2] = Math.sqrt(moveLength[2]);
                  return moveLength;
                }
                // End of multi-axis feedrate logic
                
                function onCircular(clockwise, cx, cy, cz, x, y, z, feed) {
                  if (pendingRadiusCompensation >= 0) {
                    error(localize("Radius compensation cannot be activated/deactivated for a circular move."));
                    return;
                  }
                
                  var start = getCurrentPosition();
                
                  if (isFullCircle()) {
                    if (properties.useRadius || isHelical()) { // radius mode does not support full arcs
                      linearize(tolerance);
                      return;
                    }
                    switch (getCircularPlane()) {
                    case PLANE_XY:
                      writeBlock(gAbsIncModal.format(90), gFeedModeModal.format(94), gPlaneModal.format(17), gMotionModal.format(clockwise ? 2 : 3), iOutput.format(cx - start.x, 0), jOutput.format(cy - start.y, 0), feedOutput.format(feed));
                      break;
                    case PLANE_ZX:
                      writeBlock(gAbsIncModal.format(90), gFeedModeModal.format(94), gPlaneModal.format(18), gMotionModal.format(clockwise ? 2 : 3), iOutput.format(cx - start.x, 0), kOutput.format(cz - start.z, 0), feedOutput.format(feed));
                      break;
                    case PLANE_YZ:
                      writeBlock(gAbsIncModal.format(90), gFeedModeModal.format(94), gPlaneModal.format(19), gMotionModal.format(clockwise ? 2 : 3), jOutput.format(cy - start.y, 0), kOutput.format(cz - start.z, 0), feedOutput.format(feed));
                      break;
                    default:
                      linearize(tolerance);
                    }
                  } else if (!properties.useRadius) {
                    switch (getCircularPlane()) {
                    case PLANE_XY:
                      writeBlock(gAbsIncModal.format(90), gFeedModeModal.format(94), 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), feedOutput.format(feed));
                      break;
                    case PLANE_ZX:
                      writeBlock(gAbsIncModal.format(90), gFeedModeModal.format(94), 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), feedOutput.format(feed));
                      break;
                    case PLANE_YZ:
                      writeBlock(gAbsIncModal.format(90), gFeedModeModal.format(94), 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), feedOutput.format(feed));
                      break;
                    default:
                      linearize(tolerance);
                    }
                  } else { // use radius mode
                    var r = getCircularRadius();
                    if (toDeg(getCircularSweep()) > (180 + 1e-9)) {
                      r = -r; // allow up to <360 deg arcs
                    }
                    switch (getCircularPlane()) {
                    case PLANE_XY:
                      writeBlock(gFeedModeModal.format(94), gPlaneModal.format(17), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), "R" + rFormat.format(r), feedOutput.format(feed));
                      break;
                    case PLANE_ZX:
                      writeBlock(gFeedModeModal.format(94), gPlaneModal.format(18), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), "R" + rFormat.format(r), feedOutput.format(feed));
                      break;
                    case PLANE_YZ:
                      writeBlock(gFeedModeModal.format(94), gPlaneModal.format(19), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), "R" + rFormat.format(r), feedOutput.format(feed));
                      break;
                    default:
                      linearize(tolerance);
                    }
                  }
                }
                
                var mapCommand = {
                  COMMAND_STOP:0,
                  COMMAND_OPTIONAL_STOP:1,
                  COMMAND_END:2,
                  COMMAND_SPINDLE_CLOCKWISE:3,
                  COMMAND_SPINDLE_COUNTERCLOCKWISE:4,
                  COMMAND_STOP_SPINDLE:5,
                  COMMAND_ORIENTATE_SPINDLE:19,
                  COMMAND_LOAD_TOOL:6,
                  COMMAND_COOLANT_ON:8, // flood
                  COMMAND_COOLANT_OFF:9
                };
                
                function onCommand(command) {
                  switch (command) {
                  case COMMAND_START_SPINDLE:
                    onCommand(tool.clockwise ? COMMAND_SPINDLE_CLOCKWISE : COMMAND_SPINDLE_COUNTERCLOCKWISE);
                    return;
                  case COMMAND_LOCK_MULTI_AXIS:
                    return;
                  case COMMAND_UNLOCK_MULTI_AXIS:
                    return;
                  case COMMAND_BREAK_CONTROL:
                    return;
                  case COMMAND_TOOL_MEASURE:
                    return;
                  }
                
                  var stringId = getCommandStringId(command);
                  var mcode = mapCommand[stringId];
                  if (mcode != undefined) {
                    writeBlock(mFormat.format(mcode));
                  } else {
                    onUnsupportedCommand(command);
                  }
                }
                
                function onSectionEnd() {
                  writeBlock(gPlaneModal.format(17));
                
                  if (((getCurrentSectionId() + 1) >= getNumberOfSections()) ||
                      (tool.number != getNextSection().getTool().number)) {
                    onCommand(COMMAND_BREAK_CONTROL);
                  }
                
                  forceAny();
                }
                
                function onClose() {
                  writeln("");
                
                  onCommand(COMMAND_COOLANT_OFF);
                
                  if (properties.useG28) {
                    writeBlock(gFormat.format(28), gAbsIncModal.format(91), "Z" + xyzFormat.format(machineConfiguration.getRetractPlane())); // retract
                    zOutput.reset();
                  }
                
                  setWorkPlane(new Vector(0, 0, 0)); // reset working plane
                
                  if (!machineConfiguration.hasHomePositionX() && !machineConfiguration.hasHomePositionY()) {
                    if (properties.useG28) {
                      writeBlock(gFormat.format(28), gAbsIncModal.format(91), "X" + xyzFormat.format(0), "Y" + xyzFormat.format(0)); // return to home
                    }
                  } else {
                    var homeX;
                    if (machineConfiguration.hasHomePositionX()) {
                      homeX = "X" + xyzFormat.format(machineConfiguration.getHomePositionX());
                    }
                    var homeY;
                    if (machineConfiguration.hasHomePositionY()) {
                      homeY = "Y" + xyzFormat.format(machineConfiguration.getHomePositionY());
                    }
                    writeBlock(gAbsIncModal.format(90), gFormat.format(53), gMotionModal.format(0), homeX, homeY);
                  }
                
                  onImpliedCommand(COMMAND_END);
                  onImpliedCommand(COMMAND_STOP_SPINDLE);
                  writeBlock(mFormat.format(30)); // stop program, spindle stop, coolant off
                }
                
                [/i][/i]
                
                1 Reply Last reply Reply Quote 0
                • dc42undefined
                  dc42 administrators
                  last edited by

                  I have just implemented M codes 3, 4, 5, 450, 451, 452 and 453. So the 1.20beta1 release will have spindle support.

                  Duet WiFi hardware designer and firmware engineer
                  Please do not ask me for Duet support via PM or email, use the forum
                  http://www.escher3d.com, https://miscsolutions.wordpress.com

                  1 Reply Last reply Reply Quote 0
                  • o_lampeundefined
                    o_lampe
                    last edited by

                    That's good news.
                    I've noticed in MACH3 the G2&G3 commands often come with a "K" parameter too. I can only guess it defines a z plane?
                    Also the code looks like this:

                    G3 X... Y... I... ( When J or I are 0, they don't appear in the line )
                    X... Y... I... ( The last command "G3" is still valid )
                    
                    

                    This output can be changed in brackets. But it doesn't make much sense to change that while RRF changes too.
                    It would be a temporary solution.

                    1 Reply Last reply Reply Quote 0
                    • cmefrydayundefined
                      cmefryday
                      last edited by

                      I'm working with- Fusion360 and a Shapeoko 3 CNC router, wishing i could use my Duet. A few observations about Fusion 360's Post-processor and G-code preview/senders.

                      -Brackets mentioned above to edit post processors in Fusion360 is just a text editor from Adobe that Fusion automatically opens. I prefer Notepad++.

                      -Some report Autodesk threw these together hastily and have added features like speed dial adjustment calibration: https://discuss.inventables.com/t/a-better-fusion360-grbl-postprocessor/27607

                      -Shapeoko had a nice summary and wiki here: https://www.shapeoko.com/wiki/index.php/Communication_/_Control. They also discuss rationale for limiting Gcode implementation.

                      -I found a 3D preview to be paramount. I liked Candle: https://github.com/Denvi/Candle

                      -GRBL Panel had good features and UI too: https://github.com/gerritv/Grbl-Panel/wiki

                      1 Reply Last reply Reply Quote 0
                      • dc42undefined
                        dc42 administrators
                        last edited by

                        @cmefryday:

                        I'm working with- Fusion360 and a Shapeoko 3 CNC router, wishing i could use my Duet.

                        Can you tell me which Gcodes not supported by the Duet are preventing you from using it for CNC?

                        Duet WiFi hardware designer and firmware engineer
                        Please do not ask me for Duet support via PM or email, use the forum
                        http://www.escher3d.com, https://miscsolutions.wordpress.com

                        1 Reply Last reply Reply Quote 0
                        • rjenkinsgbundefined rjenkinsgb referenced this topic
                        • First post
                          Last post
                        Unless otherwise noted, all forum content is licensed under CC-BY-SA