/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation. This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package org.cirqwizard.excellon; import org.cirqwizard.geom.Point; import org.cirqwizard.settings.ApplicationConstants; import org.cirqwizard.generation.toolpath.DrillPoint; import java.io.IOException; import java.io.LineNumberReader; import java.io.Reader; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ExcellonParser { public final static BigDecimal INCHES_MM_RATIO = new BigDecimal((int)(25.4 * ApplicationConstants.RESOLUTION)); public final static BigDecimal MM_MM_RATIO = new BigDecimal(ApplicationConstants.RESOLUTION); private final static Pattern TC_COMMAND_PATTERN = Pattern.compile("T(\\d+).*C(\\d*.\\d+).*"); private final static Pattern T_COMMAND_PATTERN = Pattern.compile("T(\\d+)"); private final static Pattern COORDINATES_PATTERN = Pattern.compile("(?:G01)?(X\\+?-?[0123456789\\.]+)?(Y\\+?-?[0123456789\\.]+)?"); private final static Pattern R_COMMAND_PATTERN = Pattern.compile("R(\\d+)(X-?[0123456789\\.]+)?(Y-?[0123456789\\.]+)?"); private final static Pattern MEASUREMENT_SYSTEM_PATTERN = Pattern.compile("(INCH|METRIC),?(LZ|TZ)?"); private HashMap<Integer, Integer> tools = new HashMap<>(); private Integer currentDiameter = 0; private ArrayList<DrillPoint> drillPoints = new ArrayList<>(); private boolean header = false; private BigDecimal coordinatesConversionRatio; private int integerPlaces; private int decimalPlaces; private boolean leadingZeros = false; private Integer x = null; private Integer y = null; private Reader reader; public ExcellonParser(Reader reader) { this(2, 4, INCHES_MM_RATIO, reader); } public ExcellonParser(int integerPlaces, int decimalPlaces, BigDecimal coordinatesConversionRatio, Reader reader) { this.integerPlaces = integerPlaces; this.decimalPlaces = decimalPlaces; this.coordinatesConversionRatio = coordinatesConversionRatio; this.reader = reader; } public List<DrillPoint> parse() throws IOException { LineNumberReader r = new LineNumberReader(reader); String str; while ((str = r.readLine()) != null) { if (header) parseHeaderLine(str); else parseBodyLine(str); } return drillPoints; } private boolean parseHeaderCommands(String line) { if (line.equals("%")) { header = false; return true; } if (line.equals("M48")) { header = true; return true; } return false; } private boolean parseToolDefinition(String line, boolean updateCurrentTool) { Matcher matcher = TC_COMMAND_PATTERN.matcher(line); if (matcher.matches()) { int toolNumber = Integer.parseInt(matcher.group(1)); int diameter = coordinatesConversionRatio.multiply(new BigDecimal(matcher.group(2))).intValue(); tools.put(toolNumber, diameter); if (updateCurrentTool) currentDiameter = diameter; return true; } return false; } private void parseHeaderLine(String line) { if (parseHeaderCommands(line)) return; if (parseToolDefinition(line, false)) return; Matcher matcher = MEASUREMENT_SYSTEM_PATTERN.matcher(line); if (matcher.matches()) { coordinatesConversionRatio = matcher.group(1).equals("METRIC") ? MM_MM_RATIO : INCHES_MM_RATIO; if (matcher.group(2) != null) leadingZeros = matcher.group(2).equals("LZ"); return; } } private void parseBodyLine(String line) { if (parseHeaderCommands(line)) return; if (parseToolDefinition(line, true)) return; Matcher matcher = T_COMMAND_PATTERN.matcher(line); if (matcher.matches()) { currentDiameter = tools.get(Integer.parseInt(matcher.group(1))); if (currentDiameter == null) currentDiameter = Integer.valueOf(matcher.group(1)) * ApplicationConstants.RESOLUTION / 10 + ApplicationConstants.RESOLUTION; return; } matcher = R_COMMAND_PATTERN.matcher(line); if (matcher.matches()) { int repetitions = Integer.valueOf(matcher.group(1)); Integer deltaX = null, deltaY = null; if (matcher.group(2) != null) deltaX = convertCoordinate(matcher.group(2).substring(1)); if (matcher.group(3) != null) deltaY = convertCoordinate(matcher.group(3).substring(1)); for (int i = 0; i < repetitions; i++) { if (deltaX != null) x += deltaX; if (deltaY != null) y += deltaY; drillPoints.add(new DrillPoint(new Point(x, y), currentDiameter)); } } matcher = COORDINATES_PATTERN.matcher(line); if (matcher.matches()) { if (matcher.group(1) != null) x = convertCoordinate(matcher.group(1).substring(1)); if (matcher.group(2) != null) y = convertCoordinate(matcher.group(2).substring(1)); if (x != null && y != null) { Point point = new Point(x, y); drillPoints.add(new DrillPoint(point, currentDiameter)); } } } private int convertCoordinate(String str) { boolean negative = str.startsWith("-"); if (negative) str = str.substring(1); if (str.indexOf('.') < 0) // Decimal point location needs to be deduced { while (str.length() < integerPlaces + decimalPlaces) { if (leadingZeros) str = str + "0"; else str = "0" + str; } } else return (new BigDecimal(str).multiply(coordinatesConversionRatio)).intValue() * (negative ? -1 : 1); long number = coordinatesConversionRatio.multiply(new BigDecimal(Long.valueOf(str.substring(integerPlaces)))).longValue(); for (int i = 0; i < decimalPlaces; i++) number /= 10; number += coordinatesConversionRatio.multiply(new BigDecimal(Long.valueOf(str.substring(0, integerPlaces)))).longValue(); return (int)(number * (negative ? -1 : 1)); } }