/**
  Copyright (C) 2012-2024 by Autodesk, Inc.
  All rights reserved.

  DATRON post processor configuration.

  $Revision: 44144 242ff9806d1be21f545eeef3c11449cf966c6f94 $
  $Date: 2024-09-13 10:53:59 $

  FORKID {21ADEFBF-939E-4D3F-A935-4E61F5958698}
*/

// >>>>> INCLUDED FROM generic_posts/datron next.cps
description = "DATRON next";
vendor = "DATRON";
vendorUrl = "http://www.datron.com";
legal = "Copyright (C) 2012-2024 by Autodesk, Inc.";
certificationLevel = 2;
minimumRevision = 45917;

longDescription = "Post for Datron next control. This post is for use with the Datron neo CNC.";
deprecatedDescription = "This post has been deprecated. Use the DATRON next Inspection post instead.";

extension = "simpl";
setCodePage("utf-8");

capabilities = CAPABILITY_MILLING | CAPABILITY_MACHINE_SIMULATION;
tolerance = spatial(0.002, MM);

minimumChordLength = spatial(0.25, MM);
minimumCircularRadius = spatial(0.01, MM);
maximumCircularRadius = spatial(1000, MM);
minimumCircularSweep = toRad(0.01);
maximumCircularSweep = toRad(120);
allowHelicalMoves = true;
allowedCircularPlanes = (1 << PLANE_XY); // allow XY plane only
probeMultipleFeatures = true;

// user-defined properties
properties = {
  writeMachine: {
    title      : "Write machine",
    description: "Output the machine settings in the header of the code.",
    group      : "formats",
    type       : "boolean",
    value      : true,
    scope      : "post"
  },
  writeDateAndTime: {
    title      : "Write date and time",
    description: "Output date and time in the header of the code.",
    group      : "formats",
    type       : "boolean",
    value      : true,
    scope      : "post"
  },
  showNotes: {
    title      : "Show notes",
    description: "Writes operation notes as comments in the outputted code.",
    group      : "formats",
    type       : "boolean",
    value      : false,
    scope      : "post"
  },
  useSmoothing: {
    title      : "Use smoothing",
    description: "Specifies that smoothing mode should be used.",
    group      : "preferences",
    type       : "boolean",
    value      : true,
    scope      : "post"
  },
  useDynamic: {
    title      : "Dynamic mode",
    description: "Specifies that dynamic mode should be used.",
    group      : "preferences",
    type       : "boolean",
    value      : true,
    scope      : "post"
  },
  machineModel: {
    title      : "Machine type",
    description: "Specifies the DATRON machine type.",
    group      : "configuration",
    type       : "enum",
    values     : [
      {title:"NEO", id:"NEO"},
      {title:"MX Cube", id:"MX"},
      {title:"Cube", id:"Cube"}
    ],
    value: "NEO",
    scope: "post"
  },
  useParkPosition: {
    title      : "Park at end of program",
    description: "Enable to use the park position at end of program.",
    group      : "homePositions",
    type       : "boolean",
    value      : true,
    scope      : "post"
  },
  writeToolTable: {
    title      : "Write tool table",
    description: "Write a tool table containing geometric tool information.",
    group      : "formats",
    type       : "boolean",
    value      : true,
    scope      : "post"
  },
  useSequences: {
    title      : "Use sequences",
    description: "If enabled, sequences are used in the output format on large files.",
    group      : "preferences",
    type       : "boolean",
    value      : true,
    scope      : "post"
  },
  useExternalSequencesFiles: {
    title      : "Use external sequence files",
    description: "If enabled, an external sequence file is created for each operation.",
    group      : "preferences",
    type       : "boolean",
    value      : false,
    scope      : "post"
  },
  useCoolant: {
    title      : "Write coolant commands",
    description: "Enable/disable coolant code outputs for the entire program.",
    group      : "preferences",
    type       : "boolean",
    value      : true,
    scope      : "post"
  },
  useParametricFeed: {
    title      : "Parametric feed",
    description: "Specifies that parametric feedrates should be used.",
    group      : "preferences",
    type       : "boolean",
    value      : true,
    scope      : "post"
  },
  waitAfterOperation: {
    title      : "Wait after operation",
    description: "If enabled, an optional stop will get generated to pause after each operation.",
    group      : "preferences",
    type       : "boolean",
    value      : false,
    scope      : "post"
  },
  rotaryAxisSetup: {
    title      : "Rotary axis setup",
    description: "Specifies the rotary axis setup.",
    group      : "configuration",
    type       : "enum",
    values     : [
      {title:"No rotary axis", id:"NONE"},
      {title:"4th axis along X+", id:"4X"},
      {title:"DST (5 axis)", id:"DST"}
    ],
    value: "NONE",
    scope: "post"
  },
  useSuction: {
    title      : "Use Suction",
    description: "Enable the suction for every operation.",
    group      : "preferences",
    type       : "boolean",
    value      : false,
    scope      : "post"
  },
  createThreadChamfer: {
    title      : "Create a Thread Chamfer",
    description: "Create a chamfer with the thread milling tool",
    group      : "preferences",
    type       : "boolean",
    value      : false,
    scope      : "post"
  },
  preloadTool: {
    title      : "Preload tool",
    description: "Preload the next Tool in the DATRON Tool assist.",
    group      : "preferences",
    type       : "boolean",
    value      : false,
    scope      : "post"
  },
  useZAxisOffset: {
    title      : "Output Z Offset command",
    description: "This creates a command to allow a manual Z offset for each operation.",
    group      : "preferences",
    type       : "boolean",
    value      : false,
    scope      : "post"
  },
  useTCP: {
    title      : "Use RTCP",
    description: "Use the NEXT 5axis setup correction.",
    group      : "multiAxis",
    type       : "boolean",
    value      : false,
    scope      : "post"
  },
  userLibraries: {
    title      : "User Libraries",
    description: "Allows to specify the desired user libraries.",
    group      : "preferences",
    type       : "string",
    value      : "",
    scope      : "post"
  }
};

// wcs definiton
wcsDefinitions = {
  useZeroOffset: true,
  wcs          : [
    {name:"Zero Offset", format:"", range:[0, 0]},
    {name:"Standard", format:"LoadWcs name=\"#\"", range:[1, 999]}
  ]
};

var gFormat = createFormat({prefix:"G", width:2, zeropad:true, decimals:1});
var mFormat = createFormat({prefix:"M", width:2, zeropad:true, decimals:1});

var xyzFormat = createFormat({decimals:(unit == MM ? 5 : 5), forceDecimal:false});
var abcFormat = createFormat({decimals:5, scale:DEG});
var feedFormat = createFormat({decimals:(unit == MM ? 2 : 2)});
var toolFormat = createFormat({decimals:0});
var dimensionFormat = createFormat({decimals:(unit == MM ? 3 : 5), forceDecimal:false});
var rpmFormat = createFormat({decimals:0, scale:1});
var sleepFormat = createFormat({decimals:0, scale:1000}); // milliseconds
var workpieceFormat = createFormat({decimals:(unit == MM ? 3 : 4), forceSign:true, trim:false});

var toolOutput = createOutputVariable({prefix:"Tool_", control:CONTROL_FORCE}, toolFormat);
var feedOutput = createOutputVariable({prefix:""}, feedFormat);

var xOutput = createOutputVariable({prefix:" X="}, xyzFormat);
var yOutput = createOutputVariable({prefix:" Y="}, xyzFormat);
var zOutput = createOutputVariable({prefix:" Z="}, xyzFormat);
var aOutput = createOutputVariable({prefix:" A="}, abcFormat);
var bOutput = createOutputVariable({prefix:" B="}, abcFormat);
var cOutput = createOutputVariable({prefix:" C="}, abcFormat);

var iOutput = createOutputVariable({prefix:" dX=", control:CONTROL_FORCE}, xyzFormat);
var jOutput = createOutputVariable({prefix:" dY=", control:CONTROL_FORCE}, xyzFormat);
var kOutput = createOutputVariable({prefix:" dZ="}, xyzFormat);

// fixed settings
var useDatronFeedCommand = false; // unsupported for now, keep false
var spacingDepth = 0;
var spacingString = "  ";
var spacing = "##########################################################";
var currentWorkOffset;
var allowIndexingWCSProbing = false; // specifies that probe WCS with tool orientation is supported

// buffer for building up a program not serial created
var sequenceBuffer = new StringBuffer();

function NewOperation(operationCall) {
  this.operationCall = operationCall;
  this.operationProgram = new StringBuffer();
  this.operationProgram.append("");
}
var currentOperation;
function NewSimPLProgram() {
  this.moduleName = new StringBuffer();
  this.measuringSystem = "Metric";
  this.toolDescriptionList = new Array();
  this.workpieceGeometry = "";
  this.sequenceList = new Array();
  this.usingList = new Array();
  this.externalUsermodules = new Array();
  this.globalVariableList = new Array();
  this.mainProgram = new StringBuffer();
  this.operationList = new Array();
}

var SimPLProgram = new NewSimPLProgram();

// collected state
var currentFeedValue = -1;
var optionalSection = false;
var activeMovements; // do not use by default
var currentFeedId;
var multiAxisCommandsEnabled = false;

var TCP_OFF = 0;
var TCP_ON = 1;

// format date + time
var timeFormat = createFormat({decimals:0, width:2, zeropad:true});
var now = new Date();
var nowDay = now.getDate();
var nowMonth = now.getMonth() + 1;
var nowHour = now.getHours();
var nowMin = now.getMinutes();
var nowSec = now.getSeconds();

function getSequenceName(section) {
  var sequenceName = "";
  if (getProperty("useExternalSequencesFiles")) {
    sequenceName += getProgramFilename();
  }
  sequenceName += "SEQUENCE_" + mapComment(getOperationDescription(section));
  return sequenceName;
}

function getProgramFilename() {
  var outputPath = getOutputPath();
  var programFilename = FileSystem.getFilename(outputPath.substr(0, outputPath.lastIndexOf("."))) + "_";
  return programFilename;
}

function getOperationName(section) {
  return "Operation_" + getOperationDescription(section);
}

function capitalizeFirstLetter(text) {
  return text.substring(0, 1).toUpperCase() + text.substring(1).toLowerCase();
}

function getSpacing() {
  var space = "";
  for (var i = 0; i < spacingDepth; i++) {
    space += spacingString;
  }
  return space;
}

/**
  Redirect the output to an infinite number of buffers
*/
var writeRedirectionStack = new Array();

function setWriteRedirection(redirectionBuffer) {
  writeRedirectionStack.push(redirectionBuffer);
}

function resetWriteRedirection() {
  writeRedirectionStack.pop();
}

/**
  Writes the specified block.
*/
function writeBlock() {
  var text = getSpacing() + formatWords(arguments);
  if (writeRedirectionStack.length == 0) {
    writeWords(text);
  } else {
    writeRedirectionStack[writeRedirectionStack.length - 1].append(text + EOL);
  }
}

/**
  Output a comment.
*/
function writeComment(text) {
  if (text) {
    text = getSpacing() + "# " + text;
    if (writeRedirectionStack.length == 0) {
      writeln(text);
    } else {
      writeRedirectionStack[writeRedirectionStack.length - 1].append(text + EOL);
    }
  }
}

var charMap = {
  "\u00c4": "Ae",
  "\u00e4": "ae",
  "\u00dc": "Ue",
  "\u00fc": "ue",
  "\u00d6": "Oe",
  "\u00f6": "oe",
  "\u00df": "ss",
  "\u002d": "_",
  "\u0020": "_"
};

/** Map specific chars. */
function mapComment(text) {
  text = formatVariable(text);
  var result = "";
  for (var i = 0; i < text.length; ++i) {
    var ch = charMap[text[i]];
    result += ch ? ch : text[i];
  }
  return result;
}

function formatComment(text) {
  return mapComment(text);
}

function formatVariable(text) {
  return String(text).replace(/[^A-Za-z0-9\-_]/g, "");
}

// Start of machine configuration logic
var compensateToolLength = false; // add the tool length to the pivot distance for nonTCP rotary heads
var useMultiAxisFeatures = false; // enable to use control enabled tilted plane, can be overridden with a property
var useABCPrepositioning = false; // enable to preposition rotary axes prior to tilted plane output, can be overridden with a property
var forceMultiAxisIndexing = false; // force multi-axis indexing for 3D programs
var eulerConvention = EULER_ZXZ_R; // euler angle convention for 3+2 operations

// internal variables, do not change
var receivedMachineConfiguration;
var operationSupportsTCP;
var multiAxisFeedrate;

/**
  Activates the machine configuration (both from CAM and hardcoded)
*/
function activateMachine() {
  // disable unsupported rotary axes output
  if (!machineConfiguration.isMachineCoordinate(0) && (typeof aOutput != "undefined")) {
    aOutput.disable();
  }
  if (!machineConfiguration.isMachineCoordinate(1) && (typeof bOutput != "undefined")) {
    bOutput.disable();
  }
  if (!machineConfiguration.isMachineCoordinate(2) && (typeof cOutput != "undefined")) {
    cOutput.disable();
  }

  // setup usage of multiAxisFeatures
  useMultiAxisFeatures = getProperty("useMultiAxisFeatures") != undefined ? getProperty("useMultiAxisFeatures") :
    (typeof useMultiAxisFeatures != "undefined" ? useMultiAxisFeatures : false);
  useABCPrepositioning = getProperty("useABCPrepositioning") != undefined ? getProperty("useABCPrepositioning") :
    (typeof useABCPrepositioning != "undefined" ? useABCPrepositioning : false);

  // don't need to modify any settings if 3-axis machine
  if (!machineConfiguration.isMultiAxisConfiguration()) {
    return;
  }

  if (false) { // set to false to disable the warning message below
    var axes = [machineConfiguration.getAxisU(), machineConfiguration.getAxisV(), machineConfiguration.getAxisW()];
    for (var i in axes) {
      if (machineConfiguration.isTableConfiguration() && axes[i].isEnabled() &&  axes[i].getOffset().isNonZero() && !axes[i].isTCPEnabled()) {
        warning(localize("A rotary axis offset is defined in the machine configuration on a non-TCP machine which will influence the NC output." + EOL +
          "The setup origin should be defined appropriately, probably at the table center, and not at the center of the rotary axes."));
        break;
      }
    }
  }

  // save multi-axis feedrate settings from machine configuration
  var mode = machineConfiguration.getMultiAxisFeedrateMode();
  var type = mode == FEED_INVERSE_TIME ? machineConfiguration.getMultiAxisFeedrateInverseTimeUnits() :
    (mode == FEED_DPM ? machineConfiguration.getMultiAxisFeedrateDPMType() : DPM_STANDARD);
  multiAxisFeedrate = {
    mode     : mode,
    maximum  : machineConfiguration.getMultiAxisFeedrateMaximum(),
    type     : type,
    tolerance: mode == FEED_DPM ? machineConfiguration.getMultiAxisFeedrateOutputTolerance() : 0,
    bpwRatio : mode == FEED_DPM ? machineConfiguration.getMultiAxisFeedrateBpwRatio() : 1
  };

  // setup of retract/reconfigure  TAG: Only needed until post kernel supports these machine config settings
  if (receivedMachineConfiguration && machineConfiguration.performRewinds()) {
    safeRetractDistance = machineConfiguration.getSafeRetractDistance();
    safePlungeFeed = machineConfiguration.getSafePlungeFeedrate();
    safeRetractFeed = machineConfiguration.getSafeRetractFeedrate();
  }
  if (typeof safeRetractDistance == "number" && getProperty("safeRetractDistance") != undefined && getProperty("safeRetractDistance") != 0) {
    safeRetractDistance = getProperty("safeRetractDistance");
  }

  // setup for head configurations
  if (machineConfiguration.isHeadConfiguration()) {
    compensateToolLength = typeof compensateToolLength == "undefined" ? false : compensateToolLength;
  }

  // calculate the ABC angles and adjust the points for multi-axis operations
  // rotary heads may require the tool length be added to the pivot length
  // so we need to optimize each section individually
  if (machineConfiguration.isHeadConfiguration() && compensateToolLength) {
    for (var i = 0; i < getNumberOfSections(); ++i) {
      var section = getSection(i);
      if (section.isMultiAxis()) {
        machineConfiguration.setToolLength(getBodyLength(section.getTool())); // define the tool length for head adjustments
        section.optimizeMachineAnglesByMachine(machineConfiguration, OPTIMIZE_AXIS);
      }
    }
  } else { // tables and rotary heads with TCP support can be optimized with a single call
    optimizeMachineAngles2(OPTIMIZE_AXIS);
  }
}

function getBodyLength(tool) {
  for (var i = 0; i < getNumberOfSections(); ++i) {
    var section = getSection(i);
    if (tool.number == section.getTool().number) {
      return section.getParameter("operation:tool_overallLength", tool.bodyLength + tool.holderLength);
    }
  }
  return tool.bodyLength + tool.holderLength;
}

/**
  Defines a hardcoded machine configuration
*/
function defineMachine() {
  var useTCP = getProperty("useTCP");
  if (getProperty("rotaryAxisSetup") != "NONE") {
    if (receivedMachineConfiguration && machineConfiguration.isMultiAxisConfiguration()) {
      error(localize("You can only select either a machine in the CAM setup or use the properties to define your kinematics."));
      return;
    }
    if (getProperty("rotaryAxisSetup") == "4X") {
      var aAxis = createAxis({coordinate:0, table:true, axis:[1, 0, 0], range:[0, 360], cyclic:true, preference:0, tcp:false});
      machineConfiguration = new MachineConfiguration(aAxis);
      machineConfiguration.setVendor("DATRON");
      machineConfiguration.setModel("NEO with A Axis");
      machineConfiguration.setDescription("DATRON NEXT Control with additional A-Axis");
    } else if (getProperty("rotaryAxisSetup") == "DST") {
      var aAxis = createAxis({coordinate:0, table:true, axis:[1, 0, 0], range:[-10, 110], cyclic:false, preference:0, tcp:useTCP});
      var cAxis = createAxis({coordinate:2, table:true, axis:[0, 0, 1], range:[-360, 360], cyclic:true, preference:0, tcp:useTCP});
      machineConfiguration = new MachineConfiguration(aAxis, cAxis);
      machineConfiguration.setVendor("DATRON");
      machineConfiguration.setModel("NEXT with DST");
      machineConfiguration.setDescription("DATRON NEXT Control with additional DST");
    }
    setMachineConfiguration(machineConfiguration);
    if (receivedMachineConfiguration) {
      warning(localize("The provided CAM machine configuration is overwritten by the postprocessor."));
      receivedMachineConfiguration = false; // CAM provided machine configuration is overwritten
    }
  } else {
    if (false) { // note: setup your machine here
      var aAxis = createAxis({coordinate:X, table:true, axis:[1, 0, 0], offset:[0, 0, 0], range:[-120, 30], cyclic:false, preference:-1, tcp:useTCP});
      var cAxis = createAxis({coordinate:Z, table:true, axis:[0, 0, 1], offset:[0, 0, 0], cyclic:true, reset:0, tcp:useTCP});
      machineConfiguration = new MachineConfiguration(aAxis, cAxis);

      setMachineConfiguration(machineConfiguration);
      if (receivedMachineConfiguration) {
        warning(localize("The provided CAM machine configuration is overwritten by the postprocessor."));
        receivedMachineConfiguration = false; // CAM provided machine configuration is overwritten
      }
    }
  }

  if (!receivedMachineConfiguration) {
    // multiaxis settings
    if (machineConfiguration.isHeadConfiguration()) {
      machineConfiguration.setVirtualTooltip(false); // translate the pivot point to the virtual tool tip for nonTCP rotary heads
    }

    // retract / reconfigure
    var performRewinds = true; // set to true to enable the retract/reconfigure logic
    if (performRewinds) {
      machineConfiguration.enableMachineRewinds(); // enables the retract/reconfigure logic
      safeRetractDistance = (unit == IN) ? 1 : 25; // additional distance to retract out of stock, can be overridden with a property
      safeRetractFeed = (unit == IN) ? 20 : 500; // retract feed rate
      safePlungeFeed = (unit == IN) ? 10 : 250; // plunge feed rate
      machineConfiguration.setSafeRetractDistance(safeRetractDistance);
      machineConfiguration.setSafeRetractFeedrate(safeRetractFeed);
      machineConfiguration.setSafePlungeFeedrate(safePlungeFeed);
      var stockExpansion = new Vector(toPreciseUnit(0.1, IN), toPreciseUnit(0.1, IN), toPreciseUnit(0.1, IN)); // expand stock XYZ values
      machineConfiguration.setRewindStockExpansion(stockExpansion);
    }

    // multi-axis feedrates
    if (machineConfiguration.isMultiAxisConfiguration()) {
      var useDPMFeeds = false;
      machineConfiguration.setMultiAxisFeedrate(
        // useTCP ? FEED_FPM : useDPMFeeds ? FEED_DPM : FEED_INVERSE_TIME,
        FEED_FPM, // only FPM feedrate is supported
        useDPMFeeds ? 9999.99 : 99999.99, // maximum output value for inverse time feed rates
        useDPMFeeds ? DPM_COMBINATION : INVERSE_MINUTES, // INVERSE_MINUTES/INVERSE_SECONDS or DPM_COMBINATION/DPM_STANDARD
        0.5, // tolerance to determine when the DPM feed has changed
        (unit == MM) ? 1.0 : 0.1 // ratio of rotary accuracy to linear accuracy for DPM calculations
      );
      setMachineConfiguration(machineConfiguration);
    }
  }
}
// End of machine configuration logic

function onOpen() {
  writeWords("\ufeff"); // write BOM for UTF-8
  // define and enable machine configuration
  receivedMachineConfiguration = machineConfiguration.isReceived();
  if (typeof defineMachine == "function") {
    defineMachine(); // hardcoded machine configuration
  }
  activateMachine(); // enable the machine optimizations and settings

  if (!machineConfiguration.isMachineCoordinate(0)) {
    aOutput.disable();
  }
  if (!machineConfiguration.isMachineCoordinate(1)) {
    bOutput.disable();
  }
  if (!machineConfiguration.isMachineCoordinate(2)) {
    cOutput.disable();
  }

  // header
  writeProgramHeader();
  spacingDepth -= 1;
  resetWriteRedirection();

  // surface inspection
  if (typeof inspectionWriteVariables == "function") {
    inspectionWriteVariables();
  }
}

function getOperationDescription(section) {
  // creates the name of the operation
  var operationComment = "";
  if (section.hasParameter("operation-comment")) {
    operationComment = section.getParameter("operation-comment");
    operationComment = formatComment(operationComment);
  }

  var cycleTypeString = "";
  if (section.hasParameter("operation:cycleType")) {
    cycleTypeString = localize(section.getParameter("operation:cycleType")).toString();
    cycleTypeString = formatComment(cycleTypeString);
  }

  var sectionID = section.getId() + 1;
  var description = operationComment + "_" + cycleTypeString + "_" + sectionID;
  return description;
}

function createToolVariables() {
  var tools = getToolTable();
  var toolVariables = new Array();
  if (tools.getNumberOfTools() > 0 && !getProperty("writeToolTable")) {
    for (var i = 0; i < tools.getNumberOfTools(); ++i) {
      var tool = tools.getTool(i);
      toolVariables.push(toolOutput.format(tool.number) + ":number");
    }
  }
  return toolVariables;
}

function createToolDescriptionTable() {
  if (!getProperty("writeToolTable")) {
    return;
  }
  var toolDescriptionArray = new Array();
  var toolNameList = new Array();
  var numberOfSections = getNumberOfSections();
  for (var i = 0; i < numberOfSections; ++i) {
    var section = getSection(i);
    var tool = section.getTool();
    if (tool.type != TOOL_PROBE) {
      var toolName = createToolName(tool);
      var toolProgrammed = createToolDescription(tool);
      if (toolNameList.indexOf(toolName) == -1) {
        toolNameList.push(toolName);
        toolDescriptionArray.push(toolProgrammed);
      }
    }
  }

  SimPLProgram.toolDescriptionList = toolDescriptionArray;
}

function createToolDescription(tool) {
  var toolProgrammed = "@ ToolDescription : " +
      "\"" + "Name" + "\"" + ":" +  "\"" + createToolName(tool) + "\"" + ", " +
      "\"" + "Category" + "\"" + ":" +  "\"" + translateToolType(tool.type) + "\"" + ", " +
      "\"" + "ArticleNr" + "\"" + ":" +  "\"" + tool.productId + "\"" + ", " +
      "\"" + "ToolNumber" + "\"" + ":" + toolFormat.format(tool.number) + ", " +
      "\"" + "Vendor" + "\"" + ":" +  "\"" + tool.vendor + "\"" + ", " +
      "\"" + "Diameter" + "\"" + ":" + dimensionFormat.format(tool.diameter) + ", " +
      "\"" + "TipAngle" + "\"" + ":" + dimensionFormat.format(toDeg(tool.taperAngle)) + ", " +
      "\"" + "TipDiameter" + "\"" + ":" + dimensionFormat.format(tool.tipDiameter) + ", " +
      "\"" + "FluteLength" + "\"" + ":" + dimensionFormat.format(tool.fluteLength) + ", " +
      "\"" + "CornerRadius" + "\"" + ":" + dimensionFormat.format(tool.cornerRadius) + ", " +
      "\"" + "ShoulderLength" + "\"" + ":" + dimensionFormat.format(tool.shoulderLength) + ", " +
      "\"" + "ShoulderDiameter" + "\"" + ":" + dimensionFormat.format(tool.diameter) + ", " +
      "\"" + "BodyLength" + "\"" + ":" + dimensionFormat.format(tool.bodyLength) + ", " +
      "\"" + "NumberOfFlutes" + "\"" + ":" + toolFormat.format(tool.numberOfFlutes) + ", " +
      "\"" + "ThreadPitch" + "\"" + ":" + dimensionFormat.format(tool.threadPitch) + ", " +
      "\"" + "ShaftDiameter" + "\"" + ":" + dimensionFormat.format(tool.shaftDiameter) + ", " +
      "\"" + "OverallLength" + "\"" + ":" + dimensionFormat.format(tool.bodyLength + 2 * tool.shaftDiameter) +
      " @";
  return toolProgrammed;
}

/**
  Generate the logical tool name for the assignment table of used tools.
*/
function createToolName(tool) {
  var toolName = toolFormat.format(tool.number);
  toolName += "_" + translateToolType(tool.type);
  if (tool.comment) {
    toolName += "_" + tool.comment;
  }
  if (tool.diameter) {
    toolName += "_D" + dimensionFormat.format(tool.diameter);
  }
  var description = tool.getDescription();
  if (description) {
    toolName += "_" + description;
  }
  if (tool.productId) {
    toolName += "_" + tool.productId;
  }
  toolName = formatVariable(toolName);
  return toolName;
}

/**
  Translate HSM tools to Datron tool categories.
*/
function translateToolType(toolType) {

  var datronCategoryName = "";

  toolCategory = toolType;
  switch (toolType) {
  case TOOL_UNSPECIFIED:
    datronCategoryName = "Unspecified";
    break;
  case TOOL_DRILL:
    datronCategoryName = "Drill";
    break;
  case TOOL_DRILL_CENTER:
    datronCategoryName = "DrillCenter";
    break;
  case TOOL_DRILL_SPOT:
    datronCategoryName = "DrillSpot";
    break;
  case TOOL_DRILL_BLOCK:
    datronCategoryName = "DrillBlock";
    break;
  case TOOL_MILLING_END_FLAT:
    datronCategoryName = "MillingEndFlat";
    break;
  case TOOL_MILLING_END_BALL:
    datronCategoryName = "MillingEndBall";
    break;
  case TOOL_MILLING_END_BULLNOSE:
    datronCategoryName = "MillingEndBullnose";
    break;
  case TOOL_MILLING_CHAMFER:
    datronCategoryName = "Graver";
    break;
  case TOOL_MILLING_FACE:
    datronCategoryName = "MillingFace";
    break;
  case TOOL_MILLING_SLOT:
    datronCategoryName = "MillingSlot";
    break;
  case TOOL_MILLING_RADIUS:
    datronCategoryName = "MillingRadius";
    break;
  case TOOL_MILLING_DOVETAIL:
    datronCategoryName = "MillingDovetail";
    break;
  case TOOL_MILLING_TAPERED:
    datronCategoryName = "MillingTapered";
    break;
  case TOOL_MILLING_LOLLIPOP:
    datronCategoryName = "MillingLollipop";
    break;
  case TOOL_TAP_RIGHT_HAND:
    datronCategoryName = "TapRightHand";
    break;
  case TOOL_TAP_LEFT_HAND:
    datronCategoryName = "TapLeftHand";
    break;
  case TOOL_REAMER:
    datronCategoryName = "Reamer";
    break;
  case TOOL_BORING_BAR:
    datronCategoryName = "BoringBar";
    break;
  case TOOL_COUNTER_BORE:
    datronCategoryName = "CounterBore";
    break;
  case TOOL_COUNTER_SINK:
    datronCategoryName = "CounterSink";
    break;
  case TOOL_HOLDER_ONLY:
    datronCategoryName = "HolderOnly";
    break;
  case TOOL_PROBE:
    datronCategoryName = "XYZSensor";
    break;
  default:
    datronCategoryName = "Unspecified";
  }
  return datronCategoryName;
}

function writeProgramHeader() {
  // write creation Date
  setWriteRedirection(SimPLProgram.moduleName);
  if (getProperty("writeDateAndTime")) {
    var date = timeFormat.format(nowDay) + "." + timeFormat.format(nowMonth) + "." + now.getFullYear();
    var time = timeFormat.format(nowHour) + ":" + timeFormat.format(nowMin);
    writeComment("!File ; generated at " + date + " - " + time);
  } else {
    writeComment("!File ;");
  }

  if (programComment) {
    writeComment(formatComment(programComment));
  }

  writeBlock(" ");
  // dump machine configuration
  var vendor = machineConfiguration.getVendor();
  var model = machineConfiguration.getModel();
  var description = machineConfiguration.getDescription();

  if (getProperty("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);
    }
  }

  writeBlock("module " + "CamGeneratedModule");
  writeBlock(" ");

  writeBlock("@ MeasuringSystem = " + (unit == MM ? "\"" + "Metric" + "\"" + " @" : "\"" + "Imperial" + "\"" + " @"));
  resetWriteRedirection();

  // set the table of used tools in the header of the program
  createToolDescriptionTable();

  // set the workpiece information
  SimPLProgram.workpieceGeometry = writeWorkpiece();

  // set the sequence header in the program file
  if (getProperty("useSequences")) {
    var sequences = new Array();
    var numberOfSections = getNumberOfSections();
    for (var i = 0; i < numberOfSections; ++i) {
      var section = getSection(i);
      if (!isProbeOperation(section)) {
        sequences.push("sequence " + getSequenceName(section));
      }
    }
    if (getProperty("useExternalSequencesFiles")) {
      writeBlock("@ EmbeddedSequences = false @");
    }

    SimPLProgram.sequenceList = sequences;
  }

  // set usings
  SimPLProgram.usingList.push("using Base" + conditional(getProperty("userLibraries") != "", "," + getProperty("userLibraries")));
  if (getProperty("waitAfterOperation")) {
    SimPLProgram.usingList.push("import System");
  }

  if (typeof inspectionCycleInspect == "function") {
    addInspectionReferences();
  }

  // set paramtric feed variables
  for (var i = 0; i < getNumberOfSections(); ++i) {
    var section = getSection(i);
    if (getProperty("useParametricFeed") && (!useDatronFeedCommand)) {
      activeFeeds = initializeActiveFeeds(section);
      for (var j = 0; j < activeFeeds.length; ++j) {
        var feedContext = activeFeeds[j];
        var feedDescription = formatVariable(feedContext.description);
        if (SimPLProgram.globalVariableList.indexOf(feedDescription + ":number") == -1) {
          SimPLProgram.globalVariableList.push(feedDescription + ":number");
        }
      }
    }
  }

  setWriteRedirection(SimPLProgram.mainProgram);
  writeBlock("export program Main # " + (programName ? (SP + formatComment(programName)) : "") + ((unit == MM) ? " MM" : " INCH"));
  spacingDepth += 1;
  writeBlock("Absolute");

  // set the parameter tool table
  SimPLProgram.globalVariableList.push(createToolVariables());

  // write the parameter tool table
  if (!getProperty("writeToolTable")) {
    var tools = getToolTable();
    writeComment("Number of tools in use" + ": " + tools.getNumberOfTools());
    if (tools.getNumberOfTools() > 0) {
      for (var i = 0; i < tools.getNumberOfTools(); ++i) {
        var tool = tools.getTool(i);
        var toolAsigment = toolOutput.format(tool.number) + " = " + (tool.number) + "# " +
          formatComment(getToolTypeName(tool.type)) + " " +
          "D:" + dimensionFormat.format(tool.diameter) + " " +
          "L2:" + dimensionFormat.format(tool.fluteLength) + " " +
          "L3:" + dimensionFormat.format(tool.shoulderLength) + " " +
          "ProductID:" + formatComment(tool.productId);
        writeBlock(toolAsigment);
      }
      writeBlock(" ");
    }
  }
  resetWriteRedirection();
}

function writeWorkpiece() {
  var workpieceString = new StringBuffer();
  setWriteRedirection(workpieceString);
  var workpiece = getWorkpiece();
  var delta = Vector.diff(workpiece.upper, workpiece.lower);

  writeBlock("# Workpiece dimensions");
  writeBlock(
    "# min:      X: " + workpieceFormat.format(workpiece.lower.x) + ";" +
    " Y: " + workpieceFormat.format(workpiece.lower.y) + ";" +
    " Z: " + workpieceFormat.format(workpiece.lower.z));
  writeBlock(
    "# max:      X: " + workpieceFormat.format(workpiece.upper.x) + ";" +
    " Y: " + workpieceFormat.format(workpiece.upper.y) + ";" +
    " Z: " + workpieceFormat.format(workpiece.upper.z));
  writeBlock(
    "# Part size X: " + workpieceFormat.format(delta.x) + ";" +
    " Y: " + workpieceFormat.format(delta.y) + ";" +
    " Z: " + workpieceFormat.format(delta.z));

  writeBlock("@ WorkpieceGeometry : " + "\"" + "MinEdge" + "\"" + ":{" + "\"" + "X" + "\"" + ":" + workpieceFormat.format(workpiece.lower.x) + "," +
    "\"" + "Y" + "\"" + ":" + workpieceFormat.format(workpiece.lower.y) + "," +
    "\"" + "Z" + "\"" + ":" +  workpieceFormat.format(workpiece.lower.z) + "}," +
    "\"" + "MaxEdge" + "\"" + ":{" + "\"" + "X" + "\"" + ":" + workpieceFormat.format(workpiece.upper.x) + "," +
    "\"" + "Y" + "\"" + ":" + workpieceFormat.format(workpiece.upper.y) + "," +
    "\"" + "Z" + "\"" + ":" + workpieceFormat.format(workpiece.upper.z) + "}" +
    " @");
  resetWriteRedirection();
  return workpieceString;
}

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();
}

function forceFeed() {
  currentFeedId = undefined;
  feedOutput.reset();
  currentFeedValue = -1;
}

/** Force output of X, Y, Z, A, B, C, and F on next output. */
function forceAny() {
  forceXYZ();
  forceABC();
  forceFeed();
}

function FeedContext(id, description, datronFeedName, feed) {
  this.id = id;
  this.description = description;
  this.datronFeedName = datronFeedName;
  if (revision < 41759) {
    this.feed = (unit == MM ? feed : toPreciseUnit(feed, MM)); // temporary solution
  } else {
    this.feed = feed;
  }
}

/** Maps the specified feed value to Q feed or formatted feed. */
function getFeed(f) {
  if (activeMovements) {
    var feedContext = activeMovements[movement];
    if (feedContext != undefined) {
      if (!feedFormat.areDifferent(feedContext.feed, f)) {
        if (feedContext.id == currentFeedId) {
          return ""; // nothing has changed
        }
        forceFeed();
        currentFeedId = feedContext.id;
        if (useDatronFeedCommand) {
          return ("Feed " + capitalizeFirstLetter(feedContext.datronFeedName));
        } else {
          return ("Feed=" + formatVariable(feedContext.description));
        }
      }
    }
    currentFeedId = undefined; // force Q feed next time
  }
  if (feedFormat.areDifferent(currentFeedValue, f)) {
    currentFeedValue = f;
    return "Feed=" + feedFormat.format(f);
  }
  return "";
}

function initializeActiveFeeds(section) {
  var activeFeeds = new Array();
  if (section.hasAnyCycle && section.hasAnyCycle()) {
    return activeFeeds;
  }
  activeMovements = new Array();
  var movements = section.getMovements();
  var id = 0;

  if (section.hasParameter("operation:tool_feedCutting")) {
    if (movements & ((1 << MOVEMENT_CUTTING) | (1 << MOVEMENT_LINK_TRANSITION) | (1 << MOVEMENT_EXTENDED))) {
      var feedContext = new FeedContext(id, localize("Cutting"), "roughing", section.getParameter("operation:tool_feedCutting"));
      addFeedContext(feedContext, activeFeeds);
      activeMovements[MOVEMENT_CUTTING] = feedContext;
      if (!section.hasParameter("operation:tool_feedTransition")) {
        activeMovements[MOVEMENT_LINK_TRANSITION] = feedContext;
      }
      activeMovements[MOVEMENT_EXTENDED] = feedContext;
    }
    ++id;
    if (movements & (1 << MOVEMENT_PREDRILL)) {
      feedContext = new FeedContext(id, localize("Predrilling"), "plunge", section.getParameter("operation:tool_feedCutting"));
      activeMovements[MOVEMENT_PREDRILL] = feedContext;
      addFeedContext(feedContext, activeFeeds);
    }
    ++id;
    if (section.hasParameter("operation-strategy") && (section.getParameter("operation-strategy") == "drill")) {
      var feedContext = new FeedContext(id, localize("Cutting"), "roughing", section.getParameter("operation:tool_feedCutting"));
      addFeedContext(feedContext, activeFeeds);
      activeMovements[MOVEMENT_CUTTING] = feedContext;
    }
    ++id;
  }
  if (section.hasParameter("operation:finishFeedrate")) {
    if (movements & (1 << MOVEMENT_FINISH_CUTTING)) {
      var feedContext = new FeedContext(id, localize("Finish"), "finishing", section.getParameter("operation:finishFeedrate"));
      addFeedContext(feedContext, activeFeeds);
      activeMovements[MOVEMENT_FINISH_CUTTING] = feedContext;
    }
    ++id;
  } else if (section.hasParameter("operation:tool_feedCutting")) {
    if (movements & (1 << MOVEMENT_FINISH_CUTTING)) {
      var feedContext = new FeedContext(id, localize("Finish"), "finishing", section.getParameter("operation:tool_feedCutting"));
      addFeedContext(feedContext, activeFeeds);
      activeMovements[MOVEMENT_FINISH_CUTTING] = feedContext;
    }
    ++id;
  }
  if (section.hasParameter("operation:tool_feedEntry")) {
    if (movements & (1 << MOVEMENT_LEAD_IN)) {
      var feedContext = new FeedContext(id, localize("Entry"), "approach", section.getParameter("operation:tool_feedEntry"));
      addFeedContext(feedContext, activeFeeds);
      activeMovements[MOVEMENT_LEAD_IN] = feedContext;
    }
    ++id;
  }
  if (section.hasParameter("operation:tool_feedExit")) {
    if (movements & (1 << MOVEMENT_LEAD_OUT)) {
      var feedContext = new FeedContext(id, localize("Exit"), "approach", section.getParameter("operation:tool_feedExit"));
      addFeedContext(feedContext, activeFeeds);
      activeMovements[MOVEMENT_LEAD_OUT] = feedContext;
    }
    ++id;
  }
  if (section.hasParameter("operation:noEngagementFeedrate")) {
    if (movements & (1 << MOVEMENT_LINK_DIRECT)) {
      var feedContext = new FeedContext(id, localize("Direct"), "approach", section.getParameter("operation:noEngagementFeedrate"));
      addFeedContext(feedContext, activeFeeds);
      activeMovements[MOVEMENT_LINK_DIRECT] = feedContext;
    }
    ++id;
  } else if (section.hasParameter("operation:tool_feedCutting") &&
    section.hasParameter("operation:tool_feedEntry") &&
    section.hasParameter("operation:tool_feedExit")) {
    if (movements & (1 << MOVEMENT_LINK_DIRECT)) {
      var feedContext = new FeedContext(id, localize("Direct"), "approach", Math.max(section.getParameter("operation:tool_feedCutting"), section.getParameter("operation:tool_feedEntry"), section.getParameter("operation:tool_feedExit")));
      addFeedContext(feedContext, activeFeeds);
      activeMovements[MOVEMENT_LINK_DIRECT] = feedContext;
    }
    ++id;
  }
  if (section.hasParameter("operation:reducedFeedrate")) {
    if (movements & (1 << MOVEMENT_REDUCED)) {
      var feedContext = new FeedContext(id, localize("Reduced"), "finishing", section.getParameter("operation:reducedFeedrate"));
      addFeedContext(feedContext, activeFeeds);
      activeMovements[MOVEMENT_REDUCED] = feedContext;
    }
    ++id;
  }
  if (section.hasParameter("operation:tool_feedRamp")) {
    if (movements & ((1 << MOVEMENT_RAMP) | (1 << MOVEMENT_RAMP_HELIX) | (1 << MOVEMENT_RAMP_PROFILE) | (1 << MOVEMENT_RAMP_ZIG_ZAG))) {
      var feedContext = new FeedContext(id, localize("Ramping"), "ramp", section.getParameter("operation:tool_feedRamp"));
      addFeedContext(feedContext, activeFeeds);
      activeMovements[MOVEMENT_RAMP] = feedContext;
      activeMovements[MOVEMENT_RAMP_HELIX] = feedContext;
      activeMovements[MOVEMENT_RAMP_PROFILE] = feedContext;
      activeMovements[MOVEMENT_RAMP_ZIG_ZAG] = feedContext;
    }
    ++id;
  }
  if (section.hasParameter("operation:tool_feedPlunge")) {
    if (movements & (1 << MOVEMENT_PLUNGE)) {
      var feedContext = new FeedContext(id, localize("Plunge"), "plunge", section.getParameter("operation:tool_feedPlunge"));
      addFeedContext(feedContext, activeFeeds);
      activeMovements[MOVEMENT_PLUNGE] = feedContext;
    }
    ++id;
  }

  // this part allows us to use feedContext also for the cycles
  if (hasParameter("operation:cycleType")) {
    var cycleType = getParameter("operation:cycleType");
    if (hasParameter("movement:plunge")) {
      var feedContext = new FeedContext(id, localize("Plunge"), "plunge", section.getParameter("movement:plunge"));
      addFeedContext(feedContext, activeFeeds);
      ++id;
    }

    switch (cycleType) {
    case "thread-milling":
      if (hasParameter("movement:plunge")) {
        var feedContext = new FeedContext(id, localize("Plunge"), "plunge", section.getParameter("movement:plunge"));
        addFeedContext(feedContext, activeFeeds);
        ++id;
      }
      if (hasParameter("movement:ramp")) {
        var feedContext = new FeedContext(id, localize("Ramping"), "ramp", section.getParameter("movement:ramp"));
        addFeedContext(feedContext, activeFeeds);
        ++id;
      }
      if (hasParameter("movement:finish_cutting")) {
        var feedContext = new FeedContext(id, localize("Finish"), "finishing", section.getParameter("movement:finish_cutting"));
        addFeedContext(feedContext, activeFeeds);
        ++id;
      }
      break;
    case "bore-milling":
      if (section.hasParameter("movement:plunge")) {
        var feedContext = new FeedContext(id, localize("Plunge"), "plunge", section.getParameter("movement:plunge"));
        addFeedContext(feedContext, activeFeeds);
        ++id;
      }
      if (section.hasParameter("movement:ramp")) {
        var feedContext = new FeedContext(id, localize("Ramping"), "ramp", section.getParameter("movement:ramp"));
        addFeedContext(feedContext, activeFeeds);
        ++id;
      }
      if (hasParameter("movement:finish_cutting")) {
        var feedContext = new FeedContext(id, localize("Finish"), "finishing", section.getParameter("movement:finish_cutting"));
        addFeedContext(feedContext, activeFeeds);
        ++id;
      }
      break;
    }
  }

  if (true) { // high feed
    if (movements & (1 << MOVEMENT_HIGH_FEED)) {
      var feedContext = new FeedContext(id, localize("High Feed"), "roughing", this.highFeedrate);
      addFeedContext(feedContext, activeFeeds);
      activeMovements[MOVEMENT_HIGH_FEED] = feedContext;
    }
    ++id;
  }
  if (section.hasParameter("operation:tool_feedTransition")) {
    if (movements & (1 << MOVEMENT_LINK_TRANSITION)) {
      var feedContext = new FeedContext(id, localize("Transition"), "transition", section.getParameter("operation:tool_feedTransition"));
      addFeedContext(feedContext, activeFeeds);
      activeMovements[MOVEMENT_LINK_TRANSITION] = feedContext;
    }
    ++id;
  }
  return activeFeeds;
}

/** Check that all elements are only one time in the result list. */
function addFeedContext(feedContext, activeFeeds) {
  if (activeFeeds.indexOf(feedContext) == -1) {
    activeFeeds.push(feedContext);
  }
}

var currentWorkPlaneABC = undefined;

function forceWorkPlane() {
  currentWorkPlaneABC = undefined;
}

function setWorkPlane(abc) {
  if (!machineConfiguration.isMultiAxisConfiguration()) {
    return; // ignore
  }
  if (isInspectionOperation(currentSection) && !is3D()) {
    error(localize("Multi axis Inspect surface is not supported."));
    return;
  }
  forceWorkPlane(); // always need the new workPlane
  forceABC();

  writeBlock("MoveToSafetyPosition");
  writeBlock("Rapid" + aOutput.format(abc.x) + bOutput.format(abc.y) + cOutput.format(abc.z));

  setCurrentABC(abc); // required for machine simulation
  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 = false;
  if (tcp) {
    setRotation(W); // TCP mode
  } else {
    var O = machineConfiguration.getOrientation(abc);
    var R = machineConfiguration.getRemainingOrientation(abc, W);
    setRotation(R);
  }

  return abc;
}

function setTCPMode(mode) {
  if (getProperty("useTCP") || operationSupportsTCP) {
    writeBlock(mode == TCP_ON ? "Rtcp On" : "Rtcp Off");
  }
}

function onSection() {
  currentOperation = new NewOperation(getOperationName(currentSection));
  setWriteRedirection(currentOperation.operationProgram);

  var forceSectionRestart = optionalSection && !currentSection.isOptional();
  optionalSection = currentSection.isOptional();

  if (!isProbeOperation(currentSection)) {
    writeComment("Operation Time: " + formatCycleTime(currentSection.getCycleTime()));
  }

  var showToolZMin = true;
  if (showToolZMin) {
    if (is3D()) {
      var zRange = currentSection.getGlobalZRange();
      var number = tool.number;
      zRange.expandToRange(currentSection.getGlobalZRange());
      writeComment("ZMIN = " + xyzFormat.format(zRange.getMinimum()));
    }
  }

  // create sub program
  writeBlock("program " + getOperationName(currentSection));
  spacingDepth += 1;

  // this control structure allows us to show the user the operation from the CAM application as a block of within the whole program similarly to Heidenhain structure.
  writeBlock("BeginBlock name=" + "\"" + getOperationDescription(currentSection) + "\"");
  var operationTolerance = tolerance;
  if (hasParameter("operation:tolerance")) {
    operationTolerance = Math.max(getParameter("operation:tolerance"), 0);
  }

  // wcs
  currentWorkOffset = undefined; // force wcs output
  if (currentSection.workOffset > 0) {
    if (currentSection.workOffset != currentWorkOffset) {
      writeBlock(currentSection.wcs);
      currentWorkOffset = currentSection.workOffset;
    }
  }

  if (getProperty("useSmoothing") && !currentSection.isMultiAxis() && !isProbeOperation(currentSection)) {
    var operationName = currentSection.getParameter("operation-comment", "").toUpperCase();
    var smoothIndex = operationName.lastIndexOf("SMOOTH");
    var smoothingTolerance = Number(operationName.substring(smoothIndex + 6));
    if (smoothIndex > 0 && !isNaN(smoothingTolerance)) {
      writeBlock("Smoothing On allowedDeviation=" + xyzFormat.format(smoothingTolerance) + "  # Created from operation name");
    } else {
      writeBlock("Smoothing On allowedDeviation=" + xyzFormat.format(operationTolerance * 1.2));
    }
  } else {
    writeBlock("Smoothing Off");
  }

  if (getProperty("useDynamic")) {
    var dynamic = 5;
    operationName = getOperationName(currentSection).toUpperCase();
    dynamicIndex = operationName.lastIndexOf("DYN");
    explicitDynamic = parseInt(operationName.substring(dynamicIndex + 3, dynamicIndex + 4), 5);
    if (!isNaN(explicitDynamic) && ((explicitDynamic > 0 && explicitDynamic < 6))) {
      writeBlock("Dynamic = " + explicitDynamic +  "  # Created from operation name");
      dynamic = explicitDynamic;
    } else {
      // set machine type specific dynamic sets
      switch (getProperty("machineModel")) {
      case "NEO":
        dynamic = 5;
        break;
      case "MX":
      case "Cube":
        if (operationTolerance <= toPreciseUnit(0.04, MM)) {
          dynamic = 4;
        }
        if (operationTolerance <= toPreciseUnit(0.02, MM)) {
          dynamic = 3;
        }
        if (operationTolerance <= toPreciseUnit(0.005, MM)) {
          dynamic = 2;
        }
        if (operationTolerance <= toPreciseUnit(0.003, MM)) {
          dynamic = 1;
        }
        break;
      }
      writeBlock("Dynamic = " + dynamic);
    }
  }
  if (getProperty("waitAfterOperation")) {
    showWaitDialog(getOperationName(currentSection));
  }

  if (machineConfiguration.isMultiAxisConfiguration()) {
    if (currentSection.isMultiAxis()) {
      forceWorkPlane();
      cancelTransformation();
      var abc = currentSection.getInitialToolAxisABC();
      writeBlock("MoveToSafetyPosition");
      forceABC();
      writeBlock("Rapid" + aOutput.format(abc.x) + bOutput.format(abc.y) + cOutput.format(abc.z));
    } else {
      forceWorkPlane();
      var abc = getWorkPlaneMachineABC(currentSection.workPlane);
      setWorkPlane(abc);
    }
  } else {
    // pure 3D
    var remaining = currentSection.workPlane;
    if (!isSameDirection(remaining.forward, new Vector(0, 0, 1)) || currentSection.isMultiAxis()) {
      error("\r\n_________________________________________" +
         "\r\n|              error                     |" +
         "\r\n|                                        |" +
         "\r\n| Tool orientation detected.             |" +
         "\r\n| You have to specify your kinematic     |" +
         "\r\n| by using property 'Rotary axis setup', |" +
         "\r\n| otherwise you can only post            |" +
         "\r\n| 3 axis programs.                       |" +
         "\r\n| If you still have issues,              |" +
         "\r\n| please contact www.DATRON.com!         |" +
         "\r\n|________________________________________|\r\n");
      return;
    }
    setRotation(remaining);
  }
  operationSupportsTCP = currentSection.getOptimizedTCPMode() == OPTIMIZE_NONE && currentSection.isMultiAxis();
  if (!currentSection.isMultiAxis() && (useMultiAxisFeatures || isSameDirection(machineConfiguration.getSpindleAxis(), currentSection.workPlane.forward))) {
    operationSupportsTCP = false;
  }
  if (machineConfiguration.isMultiAxisConfiguration()) {
    setTCPMode(TCP_ON);
    if (!multiAxisCommandsEnabled) {
      SimPLProgram.usingList.push("using Rtcp"); // always required when using -MultiAxisMode on/off- since MultiAxisMode is part of library Rtcp
      setWriteRedirection(SimPLProgram.mainProgram);
      writeBlock("MultiAxisMode On");
      resetWriteRedirection();
      multiAxisCommandsEnabled = true;
    }
  }

  forceAny();

  if (getProperty("showNotes") && currentSection.hasParameter("notes")) {
    var notes = currentSection.getParameter("notes");
    if (notes) {
      var lines = String(notes).split("\n");
      var r1 = new RegExp("^[\\s]+", "g");
      var r2 = new RegExp("[\\s]+$", "g");
      for (line in lines) {
        var comment = lines[line].replace(r1, "").replace(r2, "");
        if (comment) {
          writeComment(comment);
        }
      }
    }
  }

  var clearance = getFramePosition(currentSection.getInitialPosition()).z;
  writeBlock("SafeZHeightForWorkpiece=" + xyzFormat.format(clearance));

  // write the optional length offset for each operation
  if (getProperty("useZAxisOffset")) {
    writeBlock("ZAxisOffset = 0");
  }

  // radius compensation
  var compensationType;
  if (hasParameter("operation:compensationType")) {
    compensationType = getParameter("operation:compensationType");
  } else {
    compensationType = "computer";
  }

  var wearCompensation;
  if (hasParameter("operation:compensationDeltaRadius")) {
    wearCompensation = getParameter("operation:compensationDeltaRadius");
  } else {
    wearCompensation = 0;
  }

  if (true /*getProperty("writePathOffset")*/) {
    switch (compensationType) {
    case "computer":
      break;
    case "control":
      writeBlock("PathOffset = 0");
      break;
    case "wear":
    case "inverseWear":
      writeBlock("PathOffset = " + dimensionFormat.format(wearCompensation));
      break;
    }
  }

  if (!isProbeOperation(currentSection) && !isInspectionOperation(currentSection)) {

    // set coolant after we have positioned at Z
    setCoolant(tool.coolant);

    // tool changer command
    if (getProperty("writeToolTable")) {
      writeBlock("Tool name=" + "\"" + createToolName(tool) + "\"" +
        " newRpm=" + rpmFormat.format(spindleSpeed) +
        " skipRestoring"
      );
    } else {
      writeBlock("Tool = " + toolOutput.format(tool.number) +
        " newRpm=" + rpmFormat.format(spindleSpeed) +
        " skipRestoring"
      );
    }

    // preload the next tool for the Datron tool assist
    if (getProperty("preloadTool")) {
      var nextTool = getNextTool(tool.number);
      if (nextTool && nextTool.type != TOOL_PROBE) {
        if (getProperty("writeToolTable")) {
          writeBlock("ProvideTool name=" + "\"" + createToolName(nextTool) +  "\"");
        } else {
          writeBlock("ProvideTool = " + toolOutput.format(nextTool.number));
        }
      }
    }

    // set the current feed
    // replace by the default feed command
    if (getProperty("useParametricFeed") && !(currentSection.hasAnyCycle && currentSection.hasAnyCycle())) {
      activeFeeds = initializeActiveFeeds(currentSection);
      if (useDatronFeedCommand) {
        var datronFeedParameter = new Array();
        for (var j = 0; j < activeFeeds.length; ++j) {
          var feedContext = activeFeeds[j];
          var datronFeedCommand = {
            name: feedContext.datronFeedName,
            feed: feedFormat.format(feedContext.feed)
          };
/*eslint-disable*/
          var indexOfFeedContext = datronFeedParameter.map(function(e) {return e.name;}).indexOf(datronFeedCommand.name);
          /*eslint-enable*/
          if (indexOfFeedContext == -1) {
            datronFeedParameter.push(datronFeedCommand);
          } else {
            var existingFeedContext = datronFeedParameter[indexOfFeedContext];
            if (existingFeedContext.feed < datronFeedCommand.feed) {
              existingFeedContext.feed = datronFeedCommand.feed;
            }
          }
        }
        var datronFeedCommand = "SetFeedTechnology";
        for (var i = 0; i < datronFeedParameter.length; i++) {
          datronFeedCommand += " " + datronFeedParameter[i].name + "=" + datronFeedParameter[i].feed;
        }
        writeBlock(datronFeedCommand);

      } else {
        for (var j = 0; j < activeFeeds.length; ++j) {
          var feedContext = activeFeeds[j];
          writeBlock(formatVariable(feedContext.description) + " = " + feedFormat.format(feedContext.feed) + (unit == MM ? " # mm/min!" : " # in/min!"));
        }
      }
    }
  }

  // parameter for the sequences
  var sequenceParamter = new Array();

  if (hasParameter("operation:cycleType")) {
    // reset all movements to suppress older entries...
    activeMovements = new Array();
    var cycleType = getParameter("operation:cycleType");
    writeComment("Parameter " + cycleType + " cycle");
    var cycleFeedrate = currentSection.getParameter("feedrate");
    var cyclePlungeFeedrate = currentSection.getParameter("plungeFeedrate");
    var cycleRampingFeedrate = currentSection.getParameter("operation:tool_feedRamp");

    switch (cycleType) {
    case "thread-milling":
      writeBlock("SetFeedTechnology ramp=" + feedFormat.format(cycleRampingFeedrate) + " finishing=" + feedFormat.format(cycleFeedrate));
      var diameter = currentSection.getParameter("diameter");
      var pitch = currentSection.getParameter("pitch");
      var finishing = currentSection.getParameter("stepover");

      writeBlock("nominalDiameter=" + xyzFormat.format(diameter));
      sequenceParamter.push("nominalDiameter=nominalDiameter");
      writeBlock("pitch=" + xyzFormat.format(pitch));
      sequenceParamter.push("pitch=pitch");
      if (xyzFormat.isSignificant(parseInt(finishing, 10))) {
        writeBlock("finishing=" + xyzFormat.format(finishing));
        sequenceParamter.push("finishing=finishing");
      } else {
        sequenceParamter.push("finishing=0");
      }
      /*
      writeBlock('threadName="M' +  toolFormat.format(diameter) + '"');
      sequenceParamter.push('threadName=threadName');
      writeBlock("threading = " + currentSection.getParameter("threading"));
      sequenceParamter.push("threading=threading");

      TAG: den Standard auch mit Imperial unterstuezten
      sequenceParamter.push("threadStandard=ThreadStandards.Metric");
      sequenceParamter.push("deburring=ThreadMillingDeburring.NoDeburring");
      sequenceParamter.push("insideOutside=ThreadMillingSide.Inside");
      sequenceParamter.push("direction=ThreadMillingDirection.RightHandThread");
      writeBlock("direction = " + dimensionFormat.format(currentSection.getParameter("direction")));
      sequenceParamter.push("direction=direction");
      writeBlock("repeatPass = " + dimensionFormat.format(currentSection.getParameter("repeatPass")));
      sequenceParamter.push("repeatPass=repeatPass");
*/
      break;
    case "bore-milling":
      writeBlock("SetFeedTechnology roughing=" + feedFormat.format(cycleFeedrate) + " finishing=" + feedFormat.format(cycleFeedrate));
      writeBlock("diameter = " + dimensionFormat.format(currentSection.getParameter("diameter")));
      sequenceParamter.push("diameter=diameter");

      writeBlock("infeedZ = " + dimensionFormat.format(currentSection.getParameter("pitch")));
      sequenceParamter.push("infeedZ=infeedZ");
      writeBlock("repeatPass = " + dimensionFormat.format(currentSection.getParameter("repeatPass")));
      sequenceParamter.push("repeatPass=repeatPass");
      break;
    case "drilling":
      writeBlock("SetFeedTechnology plunge=" + feedFormat.format(cyclePlungeFeedrate));
      break;
    case "chip-breaking":
      writeBlock("SetFeedTechnology plunge=" + feedFormat.format(cyclePlungeFeedrate) + " roughing=" + feedFormat.format(cycleFeedrate));
      writeBlock("infeedZ = " + dimensionFormat.format(currentSection.getParameter("incrementalDepth")));
      sequenceParamter.push("infeedZ=infeedZ");
      break;
    }
  }

  if (getProperty("useSequences") && !isProbeOperation(currentSection) && !isInspectionOperation(currentSection)) {
    // call sequence
    if (getProperty("useParametricFeed") && (!useDatronFeedCommand) && !(currentSection.hasAnyCycle && currentSection.hasAnyCycle())) {
      activeFeeds = initializeActiveFeeds(currentSection);
      for (var j = 0; j < activeFeeds.length; ++j) {
        var feedContext = activeFeeds[j];
        sequenceParamter.push(formatVariable(feedContext.description) + "=" + formatVariable(feedContext.description));
      }
    }
    var currentSectionCall = getSequenceName(currentSection) + " " + sequenceParamter.join(" ");
    writeBlock(currentSectionCall);

    // write sequence
    var currentSequenceName = getSequenceName(currentSection);
    if (getProperty("useExternalSequencesFiles")) {
      spacingDepth -= 1;
      var filename = getOutputPath();
      // sequenceFilePath = filename.substr(0, filename.lastIndexOf(".")) + "_" + currentSequenceName + ".seq";
      sequenceFilePath = FileSystem.getFolderPath(getOutputPath()) + "\\";
      sequenceFilePath += currentSequenceName + ".seq";
      redirectToFile(sequenceFilePath);
    } else {
      setWriteRedirection(sequenceBuffer);
      writeBlock(" ");
      // TAG: modify parameter
      spacingDepth -= 1;
      writeBlock("$$$ " + currentSequenceName);
    }
  }

  if (tool.type == TOOL_PROBE) {
    writeBlock("Spindle Off");
    writeBlock("PrepareXyzSensor");
  } else {
    writeBlock(spindleSpeed > 100 ? "Spindle On" : "Spindle Off");
  }

  // move to initial position (this command moves the Z axis to safe height, positions in XY and then moves Z to the end position)
  var initialPosition = getFramePosition(currentSection.getInitialPosition());
  if (!isProbeOperation(currentSection)) { // PrePositioning is done per probe cycle
    onPrePositioning(initialPosition.x, initialPosition.y, initialPosition.z);
  } else {
    // set output formats to avoid onRapid output for probing
    setProbeCyclePosition(initialPosition.x, initialPosition.y, initialPosition.z);
  }

  if (getProperty("useSuction") && tool.type != TOOL_PROBE) {
    writeBlock("Suction On");
  }
  // surface Inspection
  if (isInspectionOperation(currentSection) && (typeof inspectionProcessSectionStart == "function")) {
    inspectionProcessSectionStart();
  }

}

function showWaitDialog(operationName) {
  writeBlock("showWaitDialog operationName=\"" + operationName + "\"");
}

function writeWaitProgram() {
  var waitProgram =
    ["# Show the wait dialog for the next operation",
      "program showWaitDialog optional operationName:string",
      "  if not operationName hasvalue",
      "    operationName = " + "\"" + "\"",
      "  endif",
      "",
      "  messageString = " + "\"" + "Start next Operation\r"  + "\"" + "  + operationName",
      "  dialogRes = System::Dialog message=messageString caption=" + "\"" + "Start next Operation?" + "\"" + "Yes  Cancel",
      "  if dialogRes == System::DialogResult.Cancel",
      "    exit",
      "  endif",
      "endprogram"
    ];
  waitProgramOperation = {operationProgram:waitProgram.join(EOL)};
  SimPLProgram.operationList.push(waitProgramOperation);
}

function onDwell(seconds) {
  writeBlock("Sleep " + "milliseconds=" + sleepFormat.format(seconds));
}

function onSpindleSpeed(spindleSpeed) {
  writeBlock("Rpm=" + rpmFormat.format(spindleSpeed));
}

var pendingRadiusCompensation = -1;

function onRadiusCompensation() {
  pendingRadiusCompensation = radiusCompensation;
}

function onRapid(x, y, z) {
  var xyz = "";
  xyz += (x !== null) ? xOutput.format(x) : "";
  xyz += (y !== null) ? yOutput.format(y) : "";
  xyz += (z !== null) ? zOutput.format(z) : "";

  if (xyz) {
    if (pendingRadiusCompensation >= 0) {
      error(localize("Radius compensation mode cannot be changed at rapid traversal."));
      return;
    }
    writeBlock("Rapid" + xyz);
    forceFeed();
  }
}

function onPrePositioning(x, y, z) {
  forceXYZ();
  var xyz = "";
  xyz += (x !== null) ? xOutput.format(x) : "";
  xyz += (y !== null) ? yOutput.format(y) : "";
  xyz += (z !== null) ? zOutput.format(z) : "";

  if (xyz) {
    if (pendingRadiusCompensation >= 0) {
      error(localize("Radius compensation mode cannot be changed at rapid traversal."));
      return;
    }
    writeBlock("PrePositioning" + xyz);
    forceFeed();
  }
}

function onLinear(x, y, z, feed) {
  if (isInspectionOperation(currentSection)) {
    onExpandedRapid(x, y, z);
    return;
  }
  var xyz = "";
  xyz += (x !== null) ? xOutput.format(x) : "";
  xyz += (y !== null) ? yOutput.format(y) : "";
  xyz += (z !== null) ? zOutput.format(z) : "";

  var f = getFeed(feed);

  if (pendingRadiusCompensation >= 0) {
    pendingRadiusCompensation = -1;
    var d = tool.diameterOffset;
    if (d > 99) {
      warning(localize("The diameter offset exceeds the maximum value."));
    }
    var compensationType = undefined;
    if (currentSection.hasParameter("operation:compensationType")) {
      compensationType = currentSection.getParameter("operation:compensationType");
    }

    switch (radiusCompensation) {
    case RADIUS_COMPENSATION_LEFT:
      switch (compensationType) {
      case "control":
        writeBlock("ToolCompensation Left");
        writeBlock("PathCorrection Left");
        break;
      case "wear":
      case "inverseWear":
        writeBlock("PathCorrection Left");
        break;
      default:
        writeBlock("ToolCompensation Off");
        writeBlock("PathCorrection Off");
        break;
      }
      break;
    case RADIUS_COMPENSATION_RIGHT:
      switch (compensationType) {
      case "control":
        writeBlock("ToolCompensation Right");
        writeBlock("PathCorrection Right");
        break;
      case "wear":
      case "inverseWear":
        writeBlock("PathCorrection Right");
        break;
      default:
        writeBlock("ToolCompensation Off");
        writeBlock("PathCorrection Off");
        break;
      }
      break;
    case RADIUS_COMPENSATION_OFF:
      writeBlock("ToolCompensation Off");
      writeBlock("PathCorrection Off");
      break;
    }
  }

  if (xyz) {
    if (f) {
      writeBlock(f);
    }
    writeBlock("Line" + xyz);
  }
}

function onCircular(clockwise, cx, cy, cz, x, y, z, feed) {
  // one of X/Y and I/J are required and likewise
  var f = getFeed(feed);

  if (f) {
    writeBlock(f);
  }

  if (pendingRadiusCompensation >= 0) {
    error(localize("Radius compensation cannot be activated/deactivated for a circular move."));
    return;
  }

  var start = getCurrentPosition();

  if (isFullCircle()) {
    if (isHelical()) {
      linearize(tolerance);
      return;
    }
    // TAG: are 360deg arcs supported
    switch (getCircularPlane()) {
    case PLANE_XY:
      writeBlock("Arc" +
        (clockwise ? " CW" : " CCW") +
        xOutput.format(x) +
        iOutput.format(cx - start.x) +
        jOutput.format(cy - start.y)
      );
      break;
    default:
      linearize(tolerance);
    }
  } else {
    switch (getCircularPlane()) {
    case PLANE_XY:
      writeBlock("Arc" +
        (clockwise ? " CW" : " CCW") +
        xOutput.format(x) +
        yOutput.format(y) +
        zOutput.format(z) +
        iOutput.format(cx - start.x) +
        jOutput.format(cy - start.y)
      );
      break;
    default:
      linearize(tolerance);
    }
  }
}

function onRapid5D(_x, _y, _z, _a, _b, _c) {
  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);

  forceABC();
  var xyzabc = x + y + z + a + b + c;
  writeBlock("Rapid" + xyzabc);
  forceFeed();
}

function onLinear5D(_x, _y, _z, _a, _b, _c, feed) {
  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);
  var f = getFeed(feed);

  writeBlock(f);
  if (x || y || z || a || b || c) {
    var xyzabc = x + y + z + a + b + c;
    writeBlock("Line" + xyzabc);
  }
}

var currentCoolantMode = COOLANT_OFF;

function setCoolant(coolant) {
  if (getProperty("useCoolant")) {
    if (coolant == COOLANT_OFF) {
      writeBlock("SpraySystem Off");
      currentCoolantMode = COOLANT_OFF;
      return;
    }

    switch (coolant) {
    case COOLANT_FLOOD:
    case COOLANT_MIST:
      writeBlock("SprayTechnology External");
      writeBlock("Coolant Alcohol");
      break;
    case COOLANT_AIR:
      writeBlock("SprayTechnology External");
      writeBlock("Coolant Air");
      break;
    case COOLANT_THROUGH_TOOL:
      writeBlock("SprayTechnology Internal");
      writeBlock("Coolant Alcohol");
      break;
    case COOLANT_AIR_THROUGH_TOOL:
      writeBlock("SprayTechnology Internal");
      writeBlock("Coolant Air");
      break;

    default:
      onUnsupportedCoolant(coolant);
    }
    writeBlock("SpraySystem On");
    currentCoolantMode = coolant;
  }
}

/*
var isInsideProgramDeclaration = false;
var directNcOperation;
function parseManualNc(text) { // todo
  var modulePattern = new RegExp(".*(using|import)");
  var isModuleImport = modulePattern.test(text);
  if (isModuleImport) {
    SimPLProgram.usingList.push(text);
    return;
  }

  var programPattern = /(?:\s*program\s+)(\w+)/;
  var isProgramDeclaration = programPattern.test(text);
  if (isProgramDeclaration) {
    var subProgramName =  programPattern.exec(text);
    if (subProgramName == undefined) {
      return;
    }
    directNcOperation = {operationCall:subProgramName[1], operationProgram: new StringBuffer()};
    SimPLProgram.operationList.push(directNcOperation);
    isInsideProgramDeclaration = true;
  }

  var isEndProgram = (/\s*endprogram/).test(text);
  if (isEndProgram) {
    isInsideProgramDeclaration = false;
    directNcOperation.operationProgram.append(text + EOL);
    return;
  }

  if (isInsideProgramDeclaration) {
    directNcOperation.operationProgram.append(text + EOL);
    return;
  }
  return;
}
*/

function onManualNC(command, value) {
  var _value;
  switch (command) {
  case COMMAND_PASS_THROUGH:
    _value = value;
    break;
  case COMMAND_COMMENT:
    _value = "# " + value;
    break;
  case COMMAND_DWELL:
    _value = "Sleep seconds=" + value;
    break;
  case COMMAND_COOLANT_OFF:
    _value = "SpraySystem Off";
    break;
  case COMMAND_COOLANT_ON:
    _value = "SpraySystem On";
    break;
  case COMMAND_STOP:
    _value = "Dialog message=\"Press the pause button on the handheld unit to get more options. Then press Ok\"  Warning";
    break;
  case COMMAND_START_SPINDLE:
    _value = "Spindle On";
    break;
  case COMMAND_START_CHIP_TRANSPORT:
    _value = "ChipConveyor On";
    break;
  case COMMAND_STOP_CHIP_TRANSPORT:
    _value = "ChipConveyor Off";
    break;
  case COMMAND_OPEN_DOOR:
    _value = "ReleaseDoor";
    break;
  case COMMAND_VERIFY:
    _value = "Dialog message=\"Please check workpiece!\" Ok Cancel caption=\"Cam generated dialog\"";
    break;
  case COMMAND_CLEAN:
    _value = "Dialog message=\"Please clean workpiece!\" Ok Cancel caption=\"Cam generated dialog\"";
    break;
  case COMMAND_PRINT_MESSAGE:
    // value = 'Dialog message="' + value + '" Ok Cancel caption="Cam generated dialog"'
    SimPLProgram.usingList.push("using File");
    SimPLProgram.usingList.push("using DateTimeModule");
    var message = (" value=(GetNow + \"\t" + value + "\")");
    _value = "FileWriteLine filename=\"" + getProgramFilename() + ".log\""  + message;
    break;
  case COMMAND_DISPLAY_MESSAGE:
    _value = "StatusMessage message=\"" + value + "\"";
    break;
  case COMMAND_ALARM:
    _value = "Dialog message=\"Alarm!\" Ok Cancel caption=\"Cam generated dialog\"";
    break;
  case COMMAND_ALERT:
    _value = "Dialog message=\"Warning!\" Ok Cancel caption=\"Cam generated dialog\"";
    break;
  case COMMAND_BREAK_CONTROL:
    _value = "MeasureToolLength";
    break;
  case COMMAND_TOOL_MEASURE:
    _value = "MeasureToolLength";
    break;
  case COMMAND_OPTIONAL_STOP:
    _value = "OptionalBreak";
    break;
  case COMMAND_CALL_PROGRAM:
    var subprogramName = "SubProgram_" + SimPLProgram.externalUsermodules.length;
    SimPLProgram.externalUsermodules.push("usermodule " + subprogramName + "=\"" + value + "\"");
    _value = subprogramName;
    break;
  }

  if (_value != undefined) {
    var operation = {operationCall:_value, operationProgram:""};
    SimPLProgram.operationList.push(operation);
  } else {
    onUnsupportedCommand(command);
  }
}

var mapCommand = {};

function onCommand(command) {
  switch (command) {
  case COMMAND_PROBE_ON:
    return;
  case COMMAND_PROBE_OFF:
    return;
  }
  var stringId = getCommandStringId(command);
  var mcode = mapCommand[stringId];
  if (mcode != undefined) {
    writeBlock(mFormat.format(mcode));
  } else {
    onUnsupportedCommand(command);
  }
}

function onCycle() {
}

function setProbeCyclePosition(x, y, z) {
  xOutput.format(x);
  yOutput.format(y);
  zOutput.format(z);
}

function onCycleEnd() {
  if (isProbeOperation(currentSection)) { // set output formats to avoid onRapid output for probing
    setProbeCyclePosition(getCurrentPosition().x, getCurrentPosition().y, getCurrentPosition().z);
  }
}

function approach(value) {
  validate((value == "positive") || (value == "negative"), "Invalid approach.");
  return (value == "positive") ? 1 : -1;
}

var probeOutputWorkOffset = 0;
function onParameter(name, value) {
  if (name == "probe-output-work-offset") {
    probeOutputWorkOffset = value;
  }
}

var requiresProgramProbeActions = false;
var WARNING_PROBE_OPTIONS = 0;
function onCyclePoint(x, y, z) {
  if (cycleType == "inspect") {
    if (typeof inspectionCycleInspect == "function") {
      inspectionCycleInspect(cycle, x, y, z);
      return;
    } else {
      cycleNotSupported();
    }
  }
  var probeWCS = hasParameter("operation-strategy") && (getParameter("operation-strategy") == "probe");

  if (isProbeOperation(currentSection)) {
    if (!isSameDirection(currentSection.workPlane.forward, new Vector(0, 0, 1))) {
      if (!allowIndexingWCSProbing && currentSection.strategy == "probe") {
        error(localize("Updating WCS / work offset using probing is only supported by the CNC in the WCS frame."));
        return;
      }
    }
    if (cycle.updateToolWear || cycle.printResults) {
      warningOnce(localize("Probe cycle options 'Update Tool Wear' and/or 'Print Results' are not supported by the postprocessor."), WARNING_PROBE_OPTIONS);
    }
    if (probeWCS && probeOutputWorkOffset == 0 && getParameter("operation:probe_overrideWorkOffset", 0) != 0)  {
      error(localize("Override Driving WCS is not supported when the initial work offset is 0."));
    }
    var startPositionOffset = cycle.probeClearance + tool.cornerRadius;

    var measResult = "";
    if (cycle.angleAskewAction != undefined || cycle.wrongSizeAction != undefined || cycle.outOfPositionAction != undefined) {
      measResult = "measResult = ";
      requiresProgramProbeActions = true;
    }
  }

  switch (cycleType) {
  case "bore-milling":
    for (var i = 0; i <= cycle.repeatPass; ++i) {
      forceXYZ();
      onExpandedRapid(x, y, cycle.clearance);
      boreMilling(cycle);
      onExpandedRapid(x, y, cycle.clearance);
    }
    break;
  case "thread-milling":
    for (var i = 0; i <= cycle.repeatPass; ++i) {
      forceXYZ();
      onExpandedRapid(x, y, cycle.clearance);
      threadMilling(cycle);
      onExpandedRapid(x, y, cycle.clearance);
    }
    break;
  case "drilling":
    forceXYZ();
    onExpandedRapid(x, y, cycle.clearance);
    drilling(cycle);
    onExpandedRapid(x, y, cycle.clearance);
    break;
    /*
  case "chip-breaking":
    forceXYZ();
    onExpandedRapid(x, y, null);
    onExpandedRapid(x, y, cycle.retract);
    chipBreaking(cycle);
    onExpandedRapid(x, y, cycle.clearance);
    break;
*/

  case "tapping":
  case "left-tapping":
  case "right-tapping":
  case "tapping-with-chip-breaking":
  case "left-tapping-with-chip-breaking":
  case "right-tapping-with-chip-breaking":
    forceXYZ();
    onExpandedRapid(x, y, cycle.clearance);
    tapping(cycle);
    onExpandedRapid(x, y, cycle.clearance);
    break;
  case "probing-x":
    var _x = x + (approach(cycle.approach1) * startPositionOffset);
    onPrePositioning(_x, y, z);

    var measureString = measResult + "EdgeMeasureV2 ";
    measureString += (cycle.approach1 == "positive" ? "XPositive" : "XNegative");
    measureString += " searchDistance=" + xyzFormat.format(cycle.probeClearance);
    measureString += " skipZMeasure";
    measureString += " retractDistance=" + xyzFormat.format(cycle.retract);
    measureString += " measureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += probeWCS ? " originXShift=" + xyzFormat.format(-_x) : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedXPos=" +  xyzFormat.format(_x);
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  case "probing-y":
    var _y = y + (approach(cycle.approach1) * startPositionOffset);
    onPrePositioning(x, _y, z);

    var measureString = measResult + "EdgeMeasureV2 ";
    measureString += (cycle.approach1 == "positive" ? "YPositive" : "YNegative");
    measureString += " searchDistance=" + xyzFormat.format(cycle.probeClearance);
    measureString += " skipZMeasure";
    measureString += " retractDistance=" + xyzFormat.format(cycle.retract);
    measureString += " measureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += probeWCS ? " originYShift=" + xyzFormat.format(-_y) : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedYPos=" + xyzFormat.format(_y);
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  case "probing-z":
    onPrePositioning(x, y, z);

    var measureString = measResult + "SurfaceMeasure ";
    measureString += probeWCS ? " originZShift=" + xyzFormat.format(z - cycle.depth) * -1 : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedZPos=" + xyzFormat.format(z - cycle.depth);
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  case "probing-x-wall":
    onPrePositioning(x, y, z);

    var measureString = measResult + "SymmetryAxisMeasure";
    measureString += " width=" + xyzFormat.format(cycle.width1);
    measureString += " searchDistance=" + xyzFormat.format(cycle.probeClearance);
    measureString += " measureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " Outside";
    measureString += " YAligned";
    measureString += " skipZMeasure";
    measureString += " retractDistance=" + xyzFormat.format(cycle.retract);
    measureString += probeWCS ? " originXShift=" + xyzFormat.format(-x) : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedXPos=" + xyzFormat.format(x) +
        " expectedDimensionX=" + xyzFormat.format(cycle.width1);
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  case "probing-y-wall":
    onPrePositioning(x, y, z);

    var measureString = measResult + "SymmetryAxisMeasure";
    measureString += " width=" + xyzFormat.format(cycle.width1);
    measureString += " searchDistance=" + xyzFormat.format(cycle.probeClearance);
    measureString += " measureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " Outside";
    measureString += " XAligned";
    measureString += " skipZMeasure";
    measureString += " retractDistance=" + xyzFormat.format(cycle.retract);
    measureString += probeWCS ? " originYShift=" + xyzFormat.format(-y) : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedYPos=" + xyzFormat.format(y) +
        " expectedDimensionY=" + xyzFormat.format(cycle.width1);
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  case "probing-x-channel":
  case "probing-x-channel-with-island":
    onPrePositioning(x, y, z);
    var measureString = measResult + "SymmetryAxisMeasure";
    measureString += " width=" + xyzFormat.format(cycle.width1);
    measureString += " searchDistance=" + xyzFormat.format(cycle.probeClearance);
    measureString += " measureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " Inside";
    measureString += " YAligned";
    measureString += conditional(cycleType == "probing-x-channel-with-island", " forceSafeHeight");
    measureString += " skipZMeasure";
    measureString += " retractDistance=" + xyzFormat.format(cycle.retract);
    measureString += probeWCS ? " originXShift=" + xyzFormat.format(-x) : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedXPos=" + xyzFormat.format(x) +
        " expectedDimensionX=" + xyzFormat.format(cycle.width1);
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  case "probing-y-channel":
  case "probing-y-channel-with-island":
    onPrePositioning(x, y, z);
    var measureString = measResult + "SymmetryAxisMeasure";
    measureString += " width=" + xyzFormat.format(cycle.width1);
    measureString += " searchDistance=" + xyzFormat.format(cycle.probeClearance);
    measureString += " measureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " Inside";
    measureString += " XAligned";
    measureString += conditional(cycleType == "probing-y-channel-with-island", " forceSafeHeight");
    measureString += " skipZMeasure";
    measureString += " retractDistance=" + xyzFormat.format(cycle.retract);
    measureString += probeWCS ? " originYShift=" + xyzFormat.format(-y) : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedYPos=" + xyzFormat.format(y) +
        " expectedDimensionY=" + xyzFormat.format(cycle.width1);
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  case "probing-xy-circular-boss":
    onPrePositioning(x, y, z);
    var measureString = measResult + "CircleMeasure";
    measureString += " diameter=" + xyzFormat.format(cycle.width1);
    measureString += " searchDistance=" + xyzFormat.format(cycle.probeClearance);
    measureString += " measureZPos=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " Outside";
    measureString += " skipZMeasure";
    measureString += " retractDistance=" + xyzFormat.format(cycle.retract);
    measureString += probeWCS ? " originXShift=" + xyzFormat.format(-x) +  " originYShift=" + xyzFormat.format(-y) : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedXPos=" +  xyzFormat.format(x) +
        " expectedYPos=" + xyzFormat.format(y) +
        " expectedDimensionX=" + xyzFormat.format(cycle.width1);
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  case "probing-xy-circular-hole":
  case "probing-xy-circular-hole-with-island":
    onPrePositioning(x, y, z);
    var measureString = measResult + "CircleMeasure";
    measureString += " diameter=" + xyzFormat.format(cycle.width1);
    measureString += " searchDistance=" + xyzFormat.format(cycle.probeClearance);
    measureString += " measureZPos=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " Inside";
    measureString += conditional(cycleType == "probing-xy-circular-hole-with-island", " forceSafeHeight");
    measureString += " skipZMeasure";
    measureString += " retractDistance=" + xyzFormat.format(cycle.retract);
    measureString += probeWCS ? " originXShift=" + xyzFormat.format(-x) +  " originYShift=" + xyzFormat.format(-y) : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedXPos=" +  xyzFormat.format(x) +
        " expectedYPos=" + xyzFormat.format(y) +
        " expectedDimensionX=" + xyzFormat.format(cycle.width1);
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  case "probing-xy-rectangular-boss":
    onPrePositioning(x, y, z);
    var measureString = measResult + "RectangleMeasure";
    measureString += " dimensionX=" + xyzFormat.format(cycle.width1);
    measureString += " dimensionY=" + xyzFormat.format(cycle.width2);
    measureString += " searchDistance=" + xyzFormat.format(cycle.probeClearance);
    measureString += " xMeasureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " yMeasureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " Outside";
    measureString += " Center";
    measureString += " skipZMeasure";
    measureString += " retractDistance=" + xyzFormat.format(cycle.retract);
    measureString += probeWCS ? " originXShift=" + xyzFormat.format(-x) +  " originYShift=" + xyzFormat.format(-y) : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedXPos=" +  xyzFormat.format(x) +
        " expectedYPos=" + xyzFormat.format(y) +
        " expectedDimensionX=" + xyzFormat.format(cycle.width1) +
        " expectedDimensionY=" + xyzFormat.format(cycle.width2);
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  case "probing-xy-rectangular-hole":
  case "probing-xy-rectangular-hole-with-island":
    onPrePositioning(x, y, z);
    var measureString = measResult + "RectangleMeasure";
    measureString += " dimensionX=" + xyzFormat.format(cycle.width1);
    measureString += " dimensionY=" + xyzFormat.format(cycle.width2);
    measureString += " searchDistance=" + xyzFormat.format(cycle.probeClearance);
    measureString += " xMeasureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " yMeasureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " Inside";
    measureString += " Center";
    measureString += conditional(cycleType == "probing-xy-rectangular-hole-with-island", " forceSafeHeight");
    measureString += " skipZMeasure";
    measureString += " retractDistance=" + xyzFormat.format(cycle.retract);
    measureString += probeWCS ? " originXShift=" + xyzFormat.format(-x) +  " originYShift=" + xyzFormat.format(-y) : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedXPos=" +  xyzFormat.format(x) +
        " expectedYPos=" + xyzFormat.format(y) +
        " expectedDimensionX=" + xyzFormat.format(cycle.width1) +
        " expectedDimensionY=" + xyzFormat.format(cycle.width2);
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  case "probing-xy-inner-corner":
    var _x = x + approach(cycle.approach1) * ((tool.diameter / 2) + cycle.probeClearance);
    var _y = y + approach(cycle.approach2) * ((tool.diameter / 2) + cycle.probeClearance);
    onPrePositioning(_x, _y, z);

    var isXNegative = (cycle.approach1 == "negative");
    var isYNegative = (cycle.approach2 == "negative");
    var orientation = "";
    if (isXNegative) {
      orientation = isYNegative ? "FrontLeft" : "BackLeft";
    } else {
      orientation = isYNegative ? "FrontRight" : "BackRight";
    }

    var measureString = measResult + "CornerMeasure";
    measureString += " " + orientation;
    measureString += " Inside";
    measureString += " xMeasureYOffset=" + xyzFormat.format(cycle.probeClearance);
    measureString += " yMeasureXOffset=" + xyzFormat.format(cycle.probeClearance);
    measureString += " searchDistance=" + xyzFormat.format(cycle.probeClearance);
    measureString += " xMeasureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " yMeasureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " forceSafeHeight";
    measureString += " skipZMeasure";
    measureString += " retractDistance=" + xyzFormat.format(cycle.retract);
    measureString += probeWCS ? " originXShift=" + xyzFormat.format(-_x) +  " originYShift=" + xyzFormat.format(-_y) : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedXPos=" +  xyzFormat.format(x) + " expectedYPos=" + xyzFormat.format(y);
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  case "probing-xy-outer-corner":
    var _x = x + approach(cycle.approach1) * ((tool.diameter / 2) + cycle.probeClearance);
    var _y = y + approach(cycle.approach2) * ((tool.diameter / 2) + cycle.probeClearance);
    onPrePositioning(_x, _y, z);

    var isXNegative = (cycle.approach1 == "negative");
    var isYNegative = (cycle.approach2 == "negative");
    var orientation = "";
    if (isXNegative) {
      orientation = isYNegative ? "BackRight" : "FrontRight";
    } else {
      orientation = isYNegative ? "BackLeft" : "FrontLeft";
    }

    var measureString = measResult + "CornerMeasure";
    measureString += " " + orientation;
    measureString += " Outside";
    measureString += " xMeasureYOffset=" + xyzFormat.format(cycle.probeClearance);
    measureString += " yMeasureXOffset=" + xyzFormat.format(cycle.probeClearance);
    measureString += " searchDistance=" + xyzFormat.format(cycle.probeClearance);
    measureString += " xMeasureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " yMeasureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " forceSafeHeight";
    measureString += " skipZMeasure";
    measureString += " retractDistance=" + xyzFormat.format(cycle.retract);
    measureString += probeWCS ? " originXShift=" + xyzFormat.format(-_x) +  " originYShift=" + xyzFormat.format(-_y) : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedXPos=" +  xyzFormat.format(x) + " expectedYPos=" + xyzFormat.format(y);
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  case "probing-x-plane-angle":
    var _x = x + (approach(cycle.approach1) * startPositionOffset);
    onPrePositioning(_x, y, z);

    var measureString = measResult + "EdgeMeasureV2 ";
    measureString += (cycle.approach1 == "positive" ? "XPositive" : "XNegative");
    measureString += " searchDistance=" + xyzFormat.format(cycle.probeClearance);
    measureString += " offsetForRotation=" + xyzFormat.format(cycle.probeSpacing);
    measureString += " measureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " skipZMeasure";
    measureString += " retractDistance=" + xyzFormat.format(cycle.retract);
    measureString += " setRotationOnly=true";
    measureString += probeWCS ? "" : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedXPos=" +  xyzFormat.format(_x) +
        " measureDirectionX=" + (cycle.approach1 == "positive" ? "positive" : "negative");
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  case "probing-y-plane-angle":
    var _y = y + (approach(cycle.approach1) * startPositionOffset);
    onPrePositioning(x, _y, z);

    var measureString = measResult + "EdgeMeasureV2 ";
    measureString += (cycle.approach1 == "positive" ? "YPositive" : "YNegative");
    measureString += " searchDistance=" + xyzFormat.format(cycle.probeClearance);
    measureString += " offsetForRotation=" + xyzFormat.format(cycle.probeSpacing);
    measureString += " measureZOffset=" + xyzFormat.format((z - cycle.depth + tool.diameter / 2));
    measureString += " skipZMeasure";
    measureString += " retractDistance=" + xyzFormat.format(cycle.retract);
    measureString += " setRotationOnly=true";
    measureString += probeWCS ? "" : " None";
    writeBlock(measureString);
    if (measResult != "") {
      expectedArgs = "expectedYPos=" +  xyzFormat.format(_y);
      writeBlock(getProbingArguments(cycle, expectedArgs));
    }
    break;
  default:
    expandCyclePoint(x, y, z);
    return;
  }

  // save probing result in setup wcs
  if (probeWCS && currentSection.workOffset != 0 &&  currentSection.workOffset !== undefined) {
    writeBlock("SaveWcs name=\"" + currentSection.probeWorkOffset + "\"");
  }
  return;
}

function getProbingArguments(cycle, additionalArguments) {
  var toolToCompensate;
  var tools = getToolTable();
  if (tools.getNumberOfTools()) {
    for (var i = 0; i < tools.getNumberOfTools(); ++i) {
      var tool = tools.getTool(i);
      if (tool.number == cycle.toolWearNumber) {
        toolToCompensate = tool;
      }
    }
  }

  return [
    "ProbeActions measResult=measResult",
    (cycle.angleAskewAction == "stop-message" ? "angleTolerance=" + xyzFormat.format(cycle.toleranceAngle ? cycle.toleranceAngle : 0) : undefined),
    // ((cycle.updateToolWear && cycle.toolWearErrorCorrection < 100) ? "toolWearErrorCorrection=" + xyzFormat.format(cycle.toolWearErrorCorrection ? cycle.toolWearErrorCorrection / 100 : 100) : undefined),
    (cycle.wrongSizeAction == "stop-message" ? "sizeTolerance=" + xyzFormat.format(cycle.toleranceSize ? cycle.toleranceSize : 0) : undefined),
    (cycle.outOfPositionAction == "stop-message" ? "positionTolerance=" + xyzFormat.format(cycle.tolerancePosition ? cycle.tolerancePosition : 0) : undefined),
    /*
    (cycle.updateToolWear ? "toolToUpdateWear=\"" + createToolName(toolToCompensate) + "\"" : undefined),
    ((cycle.updateToolWear && cycleType == "probing-z") ? "Length" : undefined),
    ((cycle.updateToolWear && cycleType !== "probing-z") ? "Diameter" : undefined),
    (cycle.updateToolWear ? "toolUpdateTreshold=" + xyzFormat.format(cycle.toolWearUpdateThreshold ? cycle.toolWearUpdateThreshold : 0) : undefined),
    (cycle.printResults ? "printResults" : undefined),
    */
    additionalArguments
  ];
}

function programIncludesOperationType(operationType) {
  var numberOfSections = getNumberOfSections();
  for (var i = 0; i < numberOfSections; ++i) {
    var section = getSection(i);
    if (operationType == "probe") {
      if (isProbeOperation(section)) {
        return true;
      }
    } else if (operationType == "inspection") {
      if (isInspectionOperation(section)) {
        return true;
      }
    } else {
      error(localize("Unsupported operation type."));
    }
  }
  return false;
}

function writeProgramProbeActions() {
  addVariable("enumeration direction (positive, negative)");
  addModuleReference("import Math");
  addModuleReference("import String");

  /*
  // Update tool wear and print results is currently not supported
  addVariable("enumeration toolWearGeometry (Diameter, Length)");
  addModuleReference("import ToolChangingUtilities");
  addModuleReference("import ToolParameter");
  addModuleReference("import ToolData");
  */

  probingProgram = [
    "program ProbeActions(",
    "  measResult:MeasuringDataWithInfo",
    "  optional expectedDimensionX:number",
    "  optional expectedDimensionY:number",
    "  optional expectedXPos:number",
    "  optional expectedYPos:number",
    "  optional expectedZPos:number  ",
    "  optional positionTolerance:number    ",
    "  optional angleTolerance:number",
    "  optional sizeTolerance:number)",
    /*
    // Update tool wear and print results is currently not supported
    "  optional toolUpdateTreshold:number",
    "  optional toolWearErrorCorrection:number",
    "  optional toolToUpdateWear:string",
    "  optional updateToolWear:toolWearGeometry",
    "  optional printResults:boolean)",
    */
    "",
    "  outOfDimension=\"\"",
    "",
    "  # Tolerance check",
    "  if sizeTolerance hasvalue ",
    "    ",
    "    if expectedDimensionX hasvalue  ",
    "        if Math::Abs(expectedDimensionX - measResult.DimensionX) > sizeTolerance ",
    "            outOfDimension = outOfDimension + GetToleranceLine(",
    "                name=\"DimensionX\"",
    "                ref=expectedDimensionX",
    "                measure=measResult.DimensionX",
    "                tol=sizeTolerance)        ",
    "        endif",
    "    endif",
    "    if expectedDimensionY hasvalue",
    "        if Math::Abs(expectedDimensionY - measResult.DimensionY) > sizeTolerance ",
    "            outOfDimension = outOfDimension + GetToleranceLine(",
    "                name=\"DimensionY\"",
    "                ref=expectedDimensionY",
    "                measure=measResult.DimensionY",
    "                tol=sizeTolerance)        ",
    "        endif",
    "    endif",
    "    ",
    "    if expectedXPos hasvalue",
    "        if Math::Abs(expectedXPos - measResult.MeasuredPosition.X) > sizeTolerance",
    "            outOfDimension = outOfDimension + GetToleranceLine(",
    "                name=\"Position X\"",
    "                ref=expectedXPos",
    "                measure=measResult.MeasuredPosition.X",
    "                tol=sizeTolerance)              ",
    "        endif",
    "    endif",
    "    if expectedYPos hasvalue",
    "        if Math::Abs(expectedYPos - measResult.MeasuredPosition.Y) > sizeTolerance",
    "            outOfDimension = outOfDimension + GetToleranceLine(",
    "                name=\"Position Y\"",
    "                ref=expectedYPos",
    "                measure=measResult.MeasuredPosition.Y",
    "                tol=sizeTolerance)              ",
    "        endif",
    "    endif",
    "    if expectedZPos hasvalue",
    "        if Math::Abs(expectedZPos - measResult.MeasuredPosition.Z) > sizeTolerance",
    "            outOfDimension = outOfDimension + GetToleranceLine(",
    "                name=\"Position Z\"",
    "                ref=expectedZPos",
    "                measure=measResult.MeasuredPosition.Z",
    "                tol=sizeTolerance)              ",
    "        endif",
    "    endif    ",
    "  endif",
    "  ",
    "  # Angle check",
    "  if angleTolerance hasvalue",
    "      if measResult.MeasuredTransformation.RotationZ > angleTolerance",
    "         Dialog message=\"Angle out of tolerance\" caption=\"Angle out of tolerance\" Error        ",
    "      endif",
    "  endif",
    "  ",
    "  # Position",
    "  if positionTolerance hasvalue",
    "    isOutOfTolerance: boolean",
    "    if expectedXPos hasvalue",
    "        isOutOfTolerance = Math::Abs(expectedXPos - measResult.MeasuredPosition.X) > positionTolerance        ",
    "    endif",
    "    if expectedYPos hasvalue",
    "        isOutOfTolerance = Math::Abs(expectedYPos - measResult.MeasuredPosition.Y) > positionTolerance  ",
    "    endif",
    "    if expectedZPos hasvalue",
    "        isOutOfTolerance = Math::Abs(expectedZPos - measResult.MeasuredPosition.Z) > positionTolerance  ",
    "    endif  ",
    "    if isOutOfTolerance",
    "        Dialog message=\"Position out of tolerance\" caption=\"Position out of tolerance\" Error",
    "    endif",
    "  endif  ",
    "  ",
    "  if outOfDimension <> \"\"",
    "    resultHeader = \"|Element|ref|measured|tolerance|",
    "|--|--|--|--|",
    "\"",
    "    message = resultHeader + outOfDimension",
    "    message = message + \"Continue program?\"",
    "    diagResult = Dialog message=message caption=\"Geometry out of tolerance\" Error enableMarkdown Yes No",
    "    if diagResult == DialogResult.No",
    "        exit",
    "    endif",
    "  endif",
    "endprogram",
    "",
    "program GetToleranceLine name:string ref:number measure:number tol:number returns string",
    "    resultRow = String::StringFormat(",
    "        baseString=\"|{0}|{1:f3}|{2:f3}|{3:f3}|",
    "\"",
    "        p0=name",
    "        p1=ref",
    "        p2=measure",
    "        p3=tol)",
    "    return resultRow",
    "endprogram",
    "",
  ];

  probingProgramOperation = {operationProgram:probingProgram.join(EOL)};
  SimPLProgram.operationList.push(probingProgramOperation);
}

function drilling(cycle) {
  var boreCommandString = new Array();
  var depth = xyzFormat.format(cycle.depth);

  boreCommandString.push("Drill");
  boreCommandString.push("depth=" + depth);
  boreCommandString.push("strokeRapidZ=" + xyzFormat.format(cycle.clearance - cycle.retract));
  boreCommandString.push("strokeCuttingZ=" + xyzFormat.format(cycle.retract - cycle.stock));
  writeBlock(boreCommandString.join(" "));
}

function chipBreaking(cycle) {
  var boreCommandString = new Array();
  var depth = xyzFormat.format(cycle.depth);

  boreCommandString.push("Drill");
  boreCommandString.push("depth=" + depth);
  boreCommandString.push("strokeRapidZ=" + xyzFormat.format(cycle.clearance - cycle.retract));
  boreCommandString.push("strokeCuttingZ=" + xyzFormat.format(cycle.retract - cycle.stock));
  boreCommandString.push("infeedZ=infeedZ");
  writeBlock(boreCommandString.join(" "));
}

function boreMilling(cycle) {
  var numberOfSteps = (cycle.numberOfSteps != undefined) ? cycle.numberOfSteps : 0;
  if (numberOfSteps > 2) {
    error("Only 2 steps are allowed for bore-milling.");
  }

  var boreCommandString = new Array();
  var depth = xyzFormat.format(cycle.depth);
  boreCommandString.push("DrillMilling");
  boreCommandString.push("diameter=diameter");
  boreCommandString.push("depth=" + depth);
  boreCommandString.push("infeedZ=infeedZ");
  boreCommandString.push("strokeRapidZ=" + xyzFormat.format(cycle.clearance - cycle.retract));
  boreCommandString.push("strokeCuttingZ=" + xyzFormat.format(cycle.retract - cycle.stock));

  if (numberOfSteps == 2) {
    var xycleaning = cycle.stepover;
    var maxzdepthperstep = tool.fluteLength * 0.8;
    boreCommandString.push("finishingXY=" + xyzFormat.format(xycleaning));
    boreCommandString.push("infeedFinishingZ=" + xyzFormat.format(maxzdepthperstep));
  }
  var bottomcleaning = 0;
  // finishingZ = 1;
  writeBlock(boreCommandString.join(" "));
}

function threadMilling(cycle) {
  var threadString = new Array();
  var depth = xyzFormat.format(cycle.depth);

  threadString.push("SpecialThread");
  // threadString.push('threadName=threadName');
  threadString.push("nominalDiameter=nominalDiameter");
  threadString.push("pitch=pitch");
  threadString.push("depth=" + depth);
  threadString.push("strokeRapidZ=" + xyzFormat.format(cycle.clearance - cycle.retract));
  threadString.push("strokeCuttingZ=" + xyzFormat.format(cycle.retract - cycle.stock));
  if (getProperty("createThreadChamfer")) {
    threadString.push("Deburring");
  }
  // threadString.push("insideOutside=ThreadMillingSide.Inside");
  threadString.push("finishing=finishing");
  if (cycle.threading == "left") {
    threadString.push("direction=ThreadMillingDirection.LeftHandThread");
  } else {
    threadString.push("direction=ThreadMillingDirection.RightHandThread");
  }
  writeBlock(threadString.join(" "));
}

function tapping(cycle) {
  var tappingString = new Array();
  var depth = xyzFormat.format(cycle.depth);
  tappingString.push("ThreadCutting");
  tappingString.push("pitch=" + xyzFormat.format(tool.threadPitch));
  tappingString.push("depth=" + depth);
  tappingString.push("strokeRapidZ=" + xyzFormat.format(cycle.clearance - cycle.retract));
  tappingString.push("strokeCuttingZ=" + xyzFormat.format(cycle.retract - cycle.stock));
  tappingString.push("threadRpm=" + rpmFormat.format(spindleSpeed));
  if (cycleType == "tapping-with-chip-breaking" || cycleType == "left-tapping-with-chip-breaking" || cycleType == "right-tapping-with-chip-breaking") {
    tappingString.push("breakChipInfeed=" + xyzFormat.format(cycle.incrementalDepth));
  }
  if (tool.type == TOOL_TAP_LEFT_HAND) {
    tappingString.push("direction=ThreadMillingDirection.LeftHandThread");
  } else {
    tappingString.push("direction=ThreadMillingDirection.RightHandThread");
  }
  writeBlock(tappingString.join(" "));
}

function formatCycleTime(cycleTime) {
  // cycleTime = cycleTime + 0.5; // round up
  var seconds = cycleTime % 60 | 0;
  var minutes = ((cycleTime - seconds) / 60 | 0) % 60;
  var hours = (cycleTime - minutes * 60 - seconds) / (60 * 60) | 0;
  if (hours > 0) {
    return subst(localize("%1h:%2m:%3s"), hours, minutes, seconds);
  } else if (minutes > 0) {
    return subst(localize("%1m:%2s"), minutes, seconds);
  } else {
    return subst(localize("%1s"), seconds);
  }
}

function dump(name, _arguments) {
  var result = getCurrentRecordId() + ": " + name + "(";
  for (var i = 0; i < _arguments.length; ++i) {
    if (i > 0) {
      result += ", ";
    }
    if (typeof _arguments[i] == "string") {
      result += "'" + _arguments[i] + "'";
    } else {
      result += _arguments[i];
    }
  }
  result += ")";
  writeln(result);
}

function onSectionEnd() {
  if (typeof inspectionProcessSectionEnd == "function") {
    inspectionProcessSectionEnd();
  }
  writeBlock("ToolCompensation Off");
  writeBlock("PathCorrection Off");
  if (getProperty("useZAxisOffset")) {
    writeBlock("ZAxisOffset = 0");
  }
  setTCPMode(TCP_OFF);

  if (getProperty("useSuction") && tool.type != TOOL_PROBE) {
    writeBlock("Suction Off");
  }
  if (currentSection.getTool().type == TOOL_PROBE && (hasNextSection() && getNextSection().getTool().type != TOOL_PROBE || isLastSection())) {
    writeBlock("UnprepareXyzSensor"); // disable probe
  }
  if (getProperty("useSequences") && !isProbeOperation(currentSection) && !isInspectionOperation(currentSection)) {
    if (!getProperty("useExternalSequencesFiles")) {
      resetWriteRedirection();
    }
    spacingDepth += 1;
  }

  writeBlock("EndBlock");

  spacingDepth -= 1;

  writeBlock("endprogram " + "# " + getOperationName(currentSection));
  resetWriteRedirection();
  SimPLProgram.operationList.push(currentOperation);
  forceAny();
}

function writeBuffer(buffer) {
  if (buffer.length > 0) {
    writeBlock(buffer.join(EOL) + EOL);
    writeBlock("");
  }
}

// after all the oiperation calls are set close the main program with all the calls
function finishMainProgram() {
  // write the main program footer
  setWriteRedirection(SimPLProgram.mainProgram);
  spacingDepth += 1;

  // write all subprogram calls in the main Program
  for (var i = 0; i < SimPLProgram.operationList.length; ++i) {
    if (SimPLProgram.operationList[i].operationCall != undefined) {
      writeBlock(SimPLProgram.operationList[i].operationCall);
    }
  }

  if (getProperty("useCoolant")) {
    writeBlock("SpraySystem Off");
  }

  if (multiAxisCommandsEnabled) {
    writeBlock("MultiAxisMode Off");
    setTCPMode(TCP_OFF);
  }

  setWorkPlane(new Vector(0, 0, 0)); // reset working plane
  if (getProperty("useParkPosition")) {
    writeBlock("Spindle Off");
    writeBlock("MoveToParkPosition");
  } else {
    writeBlock("MoveToSafetyPosition");
    zOutput.reset();
  }

  spacingDepth -= 1;
  writeBlock("endprogram #" + (programName ? (SP + formatComment(programName)) : "") + ((unit == MM) ? " MM" : " INCH"));
  resetWriteRedirection();
}

// add a varibale to the global declarations
function addVariable(value) {
  if (SimPLProgram.globalVariableList.indexOf(value) == -1) {
    SimPLProgram.globalVariableList.push(value);
  }
}
// add a varibale to the global declarations
function addModuleReference(value) {
  if (SimPLProgram.usingList.indexOf(value) == -1) {
    SimPLProgram.usingList.push(value);
  }
}

// Start of onRewindMachine logic
/** Allow user to override the onRewind logic. */
function onRewindMachineEntry(_a, _b, _c) {
  return false;
}

/** Retract to safe position before indexing rotaries. */
function onMoveToSafeRetractPosition() {
  writeBlock("MoveToSafetyPosition");
  // cancel TCP so that tool doesn't follow rotaries
  setTCPMode(TCP_OFF);
}

/** Rotate axes to new position above reentry position */
function onRotateAxes(_x, _y, _z, _a, _b, _c) {
  // position rotary axes
  xOutput.disable();
  yOutput.disable();
  zOutput.disable();
  invokeOnRapid5D(_x, _y, _z, _a, _b, _c);
  setCurrentABC(new Vector(_a, _b, _c));
  xOutput.enable();
  yOutput.enable();
  zOutput.enable();
}

/** Return from safe position after indexing rotaries. */
function onReturnFromSafeRetractPosition(_x, _y, _z) {
  // reinstate TCP / tool length compensation
  setTCPMode(TCP_ON);
  forceXYZ();
  onPrePositioning(_x, _y, _z);
}
// End of onRewindMachine logic

function onClose() {

  if (getProperty("waitAfterOperation")) {
    writeWaitProgram();
  }
  spacingDepth = 0;

  // check for additional subprograms
  if (programIncludesOperationType("inspection") && typeof writeInspectionProgram == "function") {
    writeInspectionProgram();
  }

  if (programIncludesOperationType("probe") && requiresProgramProbeActions) {
    writeProgramProbeActions();
  }

  writeBlock(SimPLProgram.moduleName);
  writeBlock("");
  writeBuffer(SimPLProgram.toolDescriptionList);
  writeBlock(SimPLProgram.workpieceGeometry);
  writeBlock("");
  writeBuffer(SimPLProgram.sequenceList);
  writeBuffer(SimPLProgram.usingList);
  writeBuffer(SimPLProgram.externalUsermodules);
  writeBuffer(SimPLProgram.globalVariableList);

  finishMainProgram();
  writeBlock(SimPLProgram.mainProgram);
  writeBlock("");

  for (var i = 0; i < SimPLProgram.operationList.length; ++i) {
    writeBlock(SimPLProgram.operationList[i].operationProgram);
  }

  writeBlock("end");

  if (getProperty("useSequences") && !getProperty("useExternalSequencesFiles")) {
    writeComment(spacing);
    writeBlock(sequenceBuffer.toString());
  }
}
// <<<<< INCLUDED FROM generic_posts/datron next.cps

capabilities |= CAPABILITY_INSPECTION;
description = "DATRON next Inspect Surface";
minimumRevision = minimumRevision < 45793 ? 45793 : minimumRevision;
longDescription = "Generic post for Datron next with Inspect Surface capabilities.";

// code for inspection support
properties.singleResultsFile = {
  title      : "Create Single Results File",
  description: "Set to false if you want to store the measurement results for each inspection toolpath in a seperate file",
  group      : "probing",
  type       : "boolean",
  value      : true,
  scope      : "post"
};
properties.toolOffsetType = {
  title      : "Tool offset type",
  description: "Select the which offsets are available on the tool offset page",
  group      : "probing",
  type       : "enum",
  values     : [
    {id:"geomWear", title:"Geometry & Wear"},
    {id:"geomOnly", title:"Geometry only"}
  ],
  value: "geomOnly",
  scope: "post"
};
properties.stopOnInspectionEnd = {
  title      : "Stop on Inspection End",
  description: "Set to ON to output M0 at the end of each inspection toolpath",
  group      : "probing",
  type       : "boolean",
  value      : true,
  scope      : "post"
};

var ijkInspectionFormat = createFormat({decimals:5, forceDecimal:true});
// inspection variables
var inspectionVariables = {
  localVariablePrefix   : "#",
  probeRadius           : 0,
  pointNumber           : 1,
  inspectionSections    : 0,
  inspectionSectionCount: 0,
  workpieceOffset       : "",
};

var macroFormat = createFormat({prefix:inspectionVariables.localVariablePrefix, decimals:0});
function inspectionProcessSectionStart() {
  // only write header once if user selects a single results file
  if (inspectionVariables.inspectionSectionCount == 0 || !getProperty("singleResultsFile") || (currentSection.workOffset != inspectionVariables.workpieceOffset)) {
    inspectionCreateResultsFileHeader();
  }
  inspectionVariables.inspectionSectionCount += 1;
  // write the toolpath name as a comment
  writeBlock("FileWriteLine filename=InspectionFilename value=\";TOOLPATH " + getParameter("operation-comment") + "\"");
  inspectionWriteWorkplaneTransform();
  if (getProperty("toolOffsetType") == "geomOnly") {
    writeComment("Geometry Only");
    // TAG fill
  } else {
    writeComment("Geometry and Wear");
    // TAG fill
  }
}

function inspectionCreateResultsFileHeader() {
  // add the filename to the global variables declarations
  addVariable("InspectionFilename:string");

  writeBlock("InspectionFilename = \"" + getInspectionFilename() + "\"");
  writeComment("delete existing old file");
  writeBlock("if FileExists filename=InspectionFilename");
  writeBlock("  FileDelete filename=InspectionFilename");
  writeBlock("endif");

  writeBlock("");
  if (inspectionVariables.inspectionSectionCount == 0 || !getProperty("singleResultsFile")) {
    writeBlock("FileWriteLine filename=InspectionFilename value=\"START\"");
    if (hasGlobalParameter("document-id")) {
      writeBlock("FileWriteLine filename=InspectionFilename value=\"DOCUMENTID " + getGlobalParameter("document-id") + "\"");
    }
    if (hasGlobalParameter("model-version")) {
      writeBlock("FileWriteLine filename=InspectionFilename value=\"MODELVERSION " + getGlobalParameter("model-version") + "\"");
    }
  }
  // write the toolpath id in the results file
  writeBlock("FileWriteLine filename=InspectionFilename value=\"TOOLPATHID " + getParameter("autodeskcam:operation-id") + "\"");
  inspectionWriteCADTransform();
  inspectionVariables.workpieceOffset = currentSection.workOffset;
}

function inspectionWriteCADTransform() {
  var cadOrigin = currentSection.getModelOrigin();
  var cadWorkPlane = currentSection.getModelPlane().getTransposed();
  var cadEuler = cadWorkPlane.getEuler2(EULER_XYZ_S);
  writeBlock(
    "FileWriteLine filename=InspectionFilename value=\"G331" +
    " N" + inspectionVariables.pointNumber +
    " A" + abcFormat.format(cadEuler.x) +
    " B" + abcFormat.format(cadEuler.y) +
    " C" + abcFormat.format(cadEuler.z) +
    " X" + xyzFormat.format(-cadOrigin.x) +
    " Y" + xyzFormat.format(-cadOrigin.y) +
    " Z" + xyzFormat.format(-cadOrigin.z) +
    "\""
  );
}

function inspectionWriteWorkplaneTransform() {
  var euler = currentSection.workPlane.getEuler2(EULER_XYZ_S);
  var abc = new Vector(euler.x, euler.y, euler.z);
  writeBlock("FileWriteLine filename=InspectionFilename value=\"G330" +
    " N" + inspectionVariables.pointNumber +
    " A" + abcFormat.format(abc.x) +
    " B" + abcFormat.format(abc.y) +
    " C" + abcFormat.format(abc.z) +
    " X0 Y0 Z0 I0 R0\""
  );
}

function inspectionProcessSectionEnd() {
  if (isInspectionOperation(currentSection)) {
    // close inspection results file if the NC has inspection toolpaths
    if ((!getProperty("singleResultsFile")) || (inspectionVariables.inspectionSectionCount == inspectionVariables.inspectionSections)) {
      // TAG add commisioning mode
    }
    writeBlock("FileWriteLine filename=InspectionFilename value=\"END\"");
    writeBlock(getProperty("stopOnInspectionEnd") == true ? "Dialog message=\"Finish Inspection\" Yes No caption=\"Inspection\" Info" : "");
  }
}

function onProbe(status) {
  if (status) {// probe ON
    writeBlock("PrepareXyzSensor"); // command for switching the probe on
  } else { // probe OFF
    writeBlock("UnprepareXyzSensor"); // command for switching the probe off
  }
}

function inspectionCycleInspect(cycle, epx, epy, epz) {
  if (getNumberOfCyclePoints() != 3) {
    error(localize("Missing Endpoint in Inspection Cycle, check Approach and Retract heights"));
  }

  if (!isLastCyclePoint()) {
    return;
  }

  forceFeed(); // ensure feed is always output - just in case.
  if (currentSection.isMultiAxis()) {
    error(localize("Multi axis inspect surface is not supported."));
    return;
  }

  var m = getRotation();
  var v = new Vector(cycle.nominalX, cycle.nominalY, cycle.nominalZ);
  var targetPoint = m.multiply(v);
  var pathVector = new Vector(cycle.nominalI, cycle.nominalJ, cycle.nominalK);
  var measureDirection = m.multiply(pathVector).normalized.getNegated();
  var searchDistance = cycle.probeClearance;

  // call inspection subprogram
  writeBlock("MeasurePoint(");
  spacingDepth += 1;
  writeBlock("pointID=" + cycle.pointID);
  writeBlock("surfacePos=" + vectorToDatronPosString(targetPoint));
  writeBlock("measureDirection=" + vectorToDatronPosString(measureDirection));
  writeBlock("searchDistance=" + xyzFormat.format(searchDistance));
  writeBlock("surfaceOffset=" + xyzFormat.format(getParameter("operation:inspectSurfaceOffset")));
  writeBlock("upper=" + xyzFormat.format(getParameter("operation:inspectUpperTolerance")));
  writeBlock("lower=" + xyzFormat.format(getParameter("operation:inspectLowerTolerance")) + ")");
  spacingDepth -= 1;
  writeBlock("");
  zOutput.reset();
}

// convert the hsm vector to the datron simpl position initilizer.
function vectorToDatronPosString(vec) {
  return "NewPos(" + xyzFormat.format(vec.x) + ", " + xyzFormat.format(vec.y) + ", " + xyzFormat.format(vec.z) + ")";
}

// adds the necessary references for inspection to the program header
function addInspectionReferences() {
  if (hasProgramInspectionOperations()) {
    SimPLProgram.usingList.push("using File, LinearAlgebra, LinearAlgebraHelper, MeasuringCyclesExecutor, XyzSensor, String");
    SimPLProgram.usingList.push("import AxisSystem");
  }
}

function hasProgramInspectionOperations() {
  var numberOfSections = getNumberOfSections();
  for (var i = 0; i < numberOfSections; ++i) {
    var section = getSection(i);
    if (isInspectionOperation(section)) {
      return true;
    }
  }
  return false;
}

// create the subprogram that makes the inspection probing and the output to the result file.
function writeInspectionProgram() {
  // not triggered is captured by the NEXT Control
  var inspectProgram = [
    "program MeasurePoint pointID:number surfacePos:Position measureDirection:Position searchDistance:number surfaceOffset:number upper:number lower:number",
    "",
    "  # measure",
    "  measureResult = GetMeasuringResultCompensation(",
    "    ArrangeMeasuring(",
    "    targetPosition=surfacePos",
    "    direction=measureDirection",
    "    distance=searchDistance),",
    "  AxisSystem::GetRcsMatrix)",
    "",
    "  # Trigger not found",
    "  if measureResult.active == false",
    "    Dialog message=\"Target Point not found\" caption=\"Inspection error\" Error",
    "  endif",
    "",

    " # write nominal values",
    "  measureNominalString = \"G800 N\" + ValueToString(pointID) + \" X\" + ValueToString(surfacePos.X)  + \" Y\" + ValueToString(surfacePos.Y) + \" Z\" + ValueToString(surfacePos.Z) + \" I\" + ValueToString(measureDirection.X * -1) + \" J\"+ ValueToString(measureDirection.Y * -1) + \" K\" + ValueToString(measureDirection.Z * -1) + \" O\" + ValueToString(surfaceOffset) + \" U\" + ValueToString(upper) + \" L\" + ValueToString(lower)",
    "  measureNominalString = StringReplace(measureNominalString, \",\", \".\")",
    "  FileWriteLine filename=InspectionFilename value=measureNominalString",
    "",

    "  # write result values",
    "   measureResultString = StringFormat(",
    "   baseString=\"G801 N{0} X{1:f3} Y{2:f3} Z{3:f3} R0\"",
    "   p0=pointID",
    "   p1=(measureResult.measuredPoint.X)",
    "   p2=(measureResult.measuredPoint.Y)",
    "   p3=(measureResult.measuredPoint.Z)",
    "   )",
    "  measureResultString = StringReplace(measureResultString, \",\", \".\")",
    "",

    "FileWriteLine filename=InspectionFilename value=measureResultString",
    "",
    "  # check result",
    "  measuredPosition = NewPos(",
    "    measureResult.measuredPoint.X,",
    "    measureResult.measuredPoint.Y,",
    "    measureResult.measuredPoint.Z)",
    "",
    "  measuredDifferenceVector = SubPosition(surfacePos, measuredPosition)",
    "  deltaDirection = Normalize(measuredDifferenceVector)",
    "  distance = GetPositionDistance(measuredDifferenceVector)",
    "",
    "  # check deviation direction",
    "  if(GetPositionDistance(SubPosition(Normalize(measuredDifferenceVector),measureDirection))>1)",
    "    distance = distance * -1",
    "  endif",
    "",
    "  if(distance > lower and distance < upper)",
    "    return",
    "  endif",
    "",
    "  # Position out of tolerance",
    "  Dialog message=\"Position out of tolerance\" caption=\"Position out of tolerance\" Error",
    "",
    "endprogram"
  ];

  inspectProgramOperation = {operationProgram:inspectProgram.join(EOL)};
  SimPLProgram.operationList.push(inspectProgramOperation);
}

function getInspectionFilename() {
  var resFile;
  if (getProperty("singleResultsFile")) {
    resFile = getParameter("job-description") + " RESULTS";
  } else {
    resFile = getParameter("operation-comment") + " RESULTS";
  }

  resFile = resFile.replace(/[^a-zA-Z0-9_ ]/g, "");
  resFile += ".txt";
  return resFile;
}