/** * Object to parse gcode one command at a time in a way that can be used by any * other class which needs to know about the current state at a given command. * * This object can be extended by adding in any number of ICommandProcessor * objects which are applied to each command in the order they were inserted * into the parser. These processors can be as simple as removing whitespace to * as complex as expanding a canned cycle or applying an leveling plane. */ /* Copywrite 2013-2016 Will Winder This file is part of Universal Gcode Sender (UGS). UGS is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. UGS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with UGS. If not, see <http://www.gnu.org/licenses/>. */ package com.willwinder.universalgcodesender.gcode; import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; import static com.willwinder.universalgcodesender.gcode.util.Plane.*; import com.willwinder.universalgcodesender.gcode.processors.ICommandProcessor; import com.willwinder.universalgcodesender.gcode.util.PlaneFormatter; import com.willwinder.universalgcodesender.types.PointSegment; import java.util.*; import javax.vecmath.Point3d; /** * * @author wwinder */ public class GcodeParser implements IGcodeParser { // Current state private GcodeState state; // Last two commands. private PointSegment latest; private PointSegment secondLatest; private final ArrayList<ICommandProcessor> processors = new ArrayList<>(); /** * An intermediate object with all metadata for a given point. */ public static class GcodeMeta { public String command; // Gcode state after processing the command. public GcodeState state; // PointSegments represent the endpoint of a given command. public PointSegment point; } /** * Constructor. */ public GcodeParser() { this.state = new GcodeState(); this.reset(); } /** * @return the number of command processors that have been added. */ @Override public int numCommandProcessors() { return this.processors.size(); } /** * Add a preprocessor to use with the preprocessCommand method. */ @Override public void addCommandProcessor(ICommandProcessor p) { this.processors.add(p); } /** * Clear out any processors that have been added. */ @Override public void resetCommandProcessors() { this.processors.clear(); } /** * Resets the current state. */ public void reset() { this.state.currentPoint = new Point3d(); this.state.commandNumber = -1; latest = new PointSegment(this.state.currentPoint, -1); secondLatest = null; } /** * Add a command to be processed with no line number association. */ @Override public List<PointSegment> addCommand(String command) throws GcodeParserException { return addCommand(command, ++this.state.commandNumber); } /** * Add a command to be processed with a line number. * @throws GcodeParserException If the command is too long throw an exception */ @Override public List<PointSegment> addCommand(String command, int line) throws GcodeParserException { List<PointSegment> results = new ArrayList<>(); // Add command get meta doesn't update the state, so we need to do that // manually. //List<String> processedCommands = this.preprocessCommand(command); Collection<GcodeMeta> metaObjects = processCommand(command, line, state); if (metaObjects != null) { for (GcodeMeta c : metaObjects) { if (c.point != null) results.add(c.point); if (c.state != null) this.state = c.state; } } for (PointSegment ps : results) { secondLatest = latest; latest = ps; } return results; } /** * Gets the point at the end of the list. */ @Override public GcodeState getCurrentState() { return this.state; } /** * Process commend given an initial state. This method will not modify its * input parameters. */ public static List<GcodeMeta> processCommand(String command, int line, final GcodeState inputState) { List<String> args = GcodePreprocessorUtils.splitCommand(command); if (args.isEmpty()) return null; List<GcodeMeta> results = new ArrayList<>(); GcodeState state = inputState.copy(); state.commandNumber = line; // handle M codes. //codes = GcodePreprocessorUtils.parseCodes(args, 'M'); //handleMCode(for each codes); List<String> fCodes = GcodePreprocessorUtils.parseCodes(args, 'F'); if (!fCodes.isEmpty()) { state.speed = Double.parseDouble(fCodes.remove(fCodes.size()-1)); } // handle G codes. List<String> gCodes = GcodePreprocessorUtils.parseCodes(args, 'G'); // If there was no command, add the implicit one to the party. if (gCodes.isEmpty() && state.lastGcodeCommand != null && !state.lastGcodeCommand.isEmpty()) { gCodes.add(state.lastGcodeCommand); } for (String i : gCodes) { GcodeMeta meta = handleGCode(i, args, line, state); meta.command = command; // Commands like 'G21' don't return a point segment. if (meta.point != null) { meta.point.setSpeed(state.speed); } results.add(meta); } return results; } /** * Create a PointSegment representing the linear command. */ private static PointSegment addLinearPointSegment(Point3d nextPoint, boolean fastTraverse, int line, GcodeState state) { PointSegment ps = new PointSegment(nextPoint, line); boolean zOnly = false; // Check for z-only if ((state.currentPoint.x == nextPoint.x) && (state.currentPoint.y == nextPoint.y) && (state.currentPoint.z != nextPoint.z)) { zOnly = true; } ps.setIsMetric(state.isMetric); ps.setIsZMovement(zOnly); ps.setIsFastTraverse(fastTraverse); // Save off the endpoint. state.currentPoint = nextPoint; return ps; } /** * Create a PointSegment representing the arc command. */ private static PointSegment addArcPointSegment(Point3d nextPoint, boolean clockwise, List<String> args, int line, GcodeState state) { PointSegment ps = new PointSegment(nextPoint, line); Point3d center = GcodePreprocessorUtils.updateCenterWithCommand( args, state.currentPoint, nextPoint, state.inAbsoluteIJKMode, clockwise, new PlaneFormatter(state.plane)); double radius = GcodePreprocessorUtils.parseCoord(args, 'R'); // Calculate radius if necessary. if (Double.isNaN(radius)) { radius = Math.sqrt( Math.pow(state.currentPoint.x - center.x, 2.0) + Math.pow(state.currentPoint.y - center.y, 2.0)); } ps.setIsMetric(state.isMetric); ps.setArcCenter(center); ps.setIsArc(true); ps.setRadius(radius); ps.setIsClockwise(clockwise); ps.setPlaneState(state.plane); // Save off the endpoint. state.currentPoint = nextPoint; return ps; } /** * Branch parser to handle specific gcode command. * * A copy of the state object should go in the resulting GcodeMeta object. */ private static GcodeMeta handleGCode(String code, List<String> args, int line, GcodeState state) { GcodeMeta meta = new GcodeMeta(); Point3d nextPoint = GcodePreprocessorUtils.updatePointWithCommand( args, state.currentPoint, state.inAbsoluteMode); if (code.length() > 1 && code.startsWith("0")) code = code.substring(1); switch (code) { case "0": meta.point = addLinearPointSegment(nextPoint, true, line, state); break; case "1": meta.point = addLinearPointSegment(nextPoint, false, line, state); break; // Arc command. case "2": meta.point = addArcPointSegment(nextPoint, true, args, line, state); break; case "3": meta.point = addArcPointSegment(nextPoint, false, args, line, state); break; case "17": state.plane = XY; break; case "18": state.plane = ZX; break; case "19": state.plane = YZ; break; case "17.1": state.plane = UV; break; case "18.1": state.plane = WU; break; case "19.1": state.plane = VW; break; case "20": //inch state.isMetric = false; break; case "21": //mm state.isMetric = true; break; case "90": state.inAbsoluteMode = true; break; case "90.1": state.inAbsoluteIJKMode = true; break; case "91": state.inAbsoluteMode = false; break; case "91.1": state.inAbsoluteIJKMode = false; break; default: break; } state.lastGcodeCommand = code; meta.state = state.copy(); return meta; } /** * Applies all command processors to a given command and returns the * resulting GCode. Does not change the parser state. */ @Override public List<String> preprocessCommand(String command) throws GcodeParserException { List<String> result = new ArrayList<>(); result.add(command); for (ICommandProcessor p : processors) { // Process each command in the list and add results to the end. // Don't re-process the results with the same preprocessor. for (int i = result.size(); i > 0; i--) { result.addAll(p.processCommand(result.remove(0), state)); } } return result; } }