diff --git a/diylc/diylc-core/pom.xml b/diylc/diylc-core/pom.xml
index f924d9382..db468fc76 100644
--- a/diylc/diylc-core/pom.xml
+++ b/diylc/diylc-core/pom.xml
@@ -97,10 +97,10 @@
org.diylc.local
- gerber-writer-api-1.1.0
+ gerber-writer-api-1.2.0
1.0
system
- ${project.basedir}/../lib/gerber-writer-api-1.1.0.jar
+ ${project.basedir}/../lib/gerber-writer-api-1.2.0.jar
org.diylc.local
diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberExporter.java b/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberExporter.java
index 40bb8e614..340bf2dbf 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberExporter.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberExporter.java
@@ -15,6 +15,7 @@ import java.util.stream.Collectors;
import org.apache.log4j.Logger;
import com.bancika.gerberwriter.DataLayer;
+import com.bancika.gerberwriter.DrillFormat;
import org.diylc.common.PCBLayer;
import org.diylc.core.ComponentState;
@@ -32,6 +33,9 @@ public class GerberExporter {
public static final double OUTLINE_THICKNESS = new Size(0.3d, SizeUnit.mm).convertToPixels();
private static final Logger LOG = Logger.getLogger(GerberExporter.class);
+ // Excellon is the drill format every fab accepts; Gerber drill files are not read by all of them
+ private static final DrillFormat DRILL_FORMAT = DrillFormat.EXCELLON;
+
public static void exportGerber(String fileNameBase, Project currentProject, IView view,
Graphics2D graphics2d, String diylcVersion) {
@@ -137,6 +141,23 @@ public class GerberExporter {
return;
}
});
+
+ g2d.getDrillLayerMap().forEach((layer, drillLayer) -> {
+ try {
+ String fileName = layer.formatFileName(fileNameBase, boardComponent.getName(),
+ DRILL_FORMAT == DrillFormat.EXCELLON ? "drl" : "gbr");
+ LOG.info("Exporting layer: " + layer + " for board " + boardComponent.getName()
+ + " to file: " + fileName);
+ drillLayer.dumpToFile(fileName, DRILL_FORMAT);
+ generatedFiles.add(fileName);
+ } catch (IOException e) {
+ LOG.error("Error writing drill file: " + e.getMessage());
+ view.showMessage(
+ LangUtil.translate(
+ "Failed to export the project to gerber. Please check the log for details"),
+ LangUtil.translate("Gerber Export"), IView.ERROR_MESSAGE);
+ }
+ });
});
view.showMessage(
String.format(
diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberG2DWrapper.java b/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberG2DWrapper.java
index 2ad489d79..6bc7ebb4b 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberG2DWrapper.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberG2DWrapper.java
@@ -21,6 +21,7 @@ import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
@@ -32,6 +33,7 @@ import java.util.HashMap;
import java.util.Map;
import com.bancika.gerberwriter.DataLayer;
+import com.bancika.gerberwriter.DrillLayer;
import com.bancika.gerberwriter.Point;
import com.bancika.gerberwriter.padmasters.Circle;
@@ -45,6 +47,7 @@ public class GerberG2DWrapper extends Graphics2D
private static final double CURVE_APPROXIMATION_TOLERANCE = 1d;
private Map layerMap;
+ private Map drillLayerMap;
private boolean trackingGerber = false;
private Map currentLayers =
new HashMap();
@@ -65,6 +68,7 @@ public class GerberG2DWrapper extends Graphics2D
this.graphics2d = graphics2d;
this.boardRect = boardRect;
this.layerMap = new HashMap();
+ this.drillLayerMap = new HashMap();
this.diylcVersion = diylcVersion;
}
@@ -115,7 +119,11 @@ public class GerberG2DWrapper extends Graphics2D
@Override
public void startGerberOutput(GerberLayer layer, String function, boolean negative) {
- layerMap.computeIfAbsent(layer, k -> layer.buildLayer(diylcVersion));
+ if (layer.isDrill()) {
+ drillLayerMap.computeIfAbsent(layer, k -> layer.buildDrillLayer(diylcVersion));
+ } else {
+ layerMap.computeIfAbsent(layer, k -> layer.buildLayer(diylcVersion));
+ }
currentLayers.put(layer, new GerberProperties(function, negative));
trackingGerber = true;
}
@@ -155,6 +163,10 @@ public class GerberG2DWrapper extends Graphics2D
return layerMap;
}
+ public Map getDrillLayerMap() {
+ return drillLayerMap;
+ }
+
/**
* Sets whether text is written mirrored, each string about its own centre. The output is in top
* view, so text on the bottom side has to be mirrored to read correctly once the board is turned
@@ -168,6 +180,11 @@ public class GerberG2DWrapper extends Graphics2D
public void draw(Shape s) {
if (trackingGerber && !getColor().equals(Constants.TRANSPARENT_COLOR)) {
currentLayers.entrySet().forEach(entry -> {
+ // only round fills are holes; outlines, such as a drill hole marker's border and
+ // crosshair, are not drilled
+ if (entry.getKey().isDrill()) {
+ return;
+ }
double width = 1;
if (stroke != null && stroke instanceof BasicStroke) {
width = ((BasicStroke) stroke).getLineWidth();
@@ -222,6 +239,10 @@ public class GerberG2DWrapper extends Graphics2D
public void fillShape(Shape s) {
if (trackingGerber && !getColor().equals(Constants.TRANSPARENT_COLOR)) {
currentLayers.entrySet().forEach(entry -> {
+ // only round fills are holes
+ if (entry.getKey().isDrill()) {
+ return;
+ }
DataLayer dataLayer = layerMap.get(entry.getKey());
AffineTransform finalTx = getFinalTx(entry.getKey().isMirrored());
GerberPathRenderer.outputPathArea(s.getPathIterator(finalTx), dataLayer,
@@ -234,6 +255,10 @@ public class GerberG2DWrapper extends Graphics2D
private void fillFlash(Ellipse2D c) {
if (trackingGerber && !getColor().equals(Constants.TRANSPARENT_COLOR)) {
currentLayers.entrySet().forEach(entry -> {
+ if (entry.getKey().isDrill()) {
+ addHole(c, entry.getKey(), entry.getValue().function);
+ return;
+ }
DataLayer dataLayer = layerMap.get(entry.getKey());
AffineTransform finalTx = getFinalTx(entry.getKey().isMirrored());
if (Math.abs(c.getWidth() - c.getHeight()) < 0.1 && finalTx.getShearX() == 0
@@ -255,6 +280,17 @@ public class GerberG2DWrapper extends Graphics2D
}
}
+ private void addHole(Ellipse2D c, GerberLayer layer, String function) {
+ AffineTransform finalTx = getFinalTx(layer.isMirrored());
+ Point2D center = finalTx.transform(new Point2D.Double(c.getCenterX(), c.getCenterY()), null);
+ double factor = SizeUnit.px.getFactor();
+ // the drawing transform scales evenly, so the circle stays a circle, scaled by the square root
+ // of the determinant whether or not it is also rotated
+ double diameter = c.getWidth() * Math.sqrt(Math.abs(finalTx.getDeterminant())) * factor;
+ drillLayerMap.get(layer).addHole(new Point(center.getX() * factor, center.getY() * factor),
+ diameter, function);
+ }
+
private AffineTransform getFinalTx(boolean isMirrored) {
AffineTransform finalTx = new AffineTransform();
if (isMirrored) {
diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberLayer.java b/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberLayer.java
index 5dabcdf23..70bc9b7a8 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberLayer.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberLayer.java
@@ -1,6 +1,7 @@
package org.diylc.core.gerber;
import com.bancika.gerberwriter.DataLayer;
+import com.bancika.gerberwriter.DrillLayer;
import com.bancika.gerberwriter.GenerationSoftware;
public enum GerberLayer {
@@ -30,9 +31,18 @@ public enum GerberLayer {
}
public DataLayer buildLayer(String diylcVersion) {
- GenerationSoftware genSoftware =
- new GenerationSoftware("bancika", "DIY Layout Creator", diylcVersion);
- return new DataLayer(function, negative, genSoftware);
+ return new DataLayer(function, negative, createGenerationSoftware(diylcVersion));
+ }
+
+ public DrillLayer buildDrillLayer(String diylcVersion) {
+ return new DrillLayer(this == DrillPlated, createGenerationSoftware(diylcVersion));
+ }
+
+ /**
+ * @return true for the drill layers, which hold holes rather than drawn copper or graphics
+ */
+ public boolean isDrill() {
+ return this == DrillPlated || this == DrillNonPlated;
}
public String getExtension() {
@@ -56,7 +66,15 @@ public enum GerberLayer {
}
public String formatFileName(String fileNameBase, String boardName) {
+ return formatFileName(fileNameBase, boardName, getExtension());
+ }
+
+ public String formatFileName(String fileNameBase, String boardName, String extension) {
return fileNameBase + (fileNameBase.endsWith(".") ? "" : ".")
- + boardName + "-" + getSuffix() + "." + getExtension();
+ + boardName + "-" + getSuffix() + "." + extension;
+ }
+
+ private static GenerationSoftware createGenerationSoftware(String diylcVersion) {
+ return new GenerationSoftware("bancika", "DIY Layout Creator", diylcVersion);
}
}
diff --git a/diylc/diylc-core/src/main/resources/update.xml b/diylc/diylc-core/src/main/resources/update.xml
index 78f693609..b7a8ed9c7 100644
--- a/diylc/diylc-core/src/main/resources/update.xml
+++ b/diylc/diylc-core/src/main/resources/update.xml
@@ -673,6 +673,10 @@
IMPROVEMENT
Gerber export no longer cuts the holes of solder pads and vias out of the copper layers; the holes are defined by the drill files alone, as the Gerber specification recommends
+
+ IMPROVEMENT
+ Gerber export writes the drill files in Excellon format, the drill format every PCB fab accepts, instead of Gerber format; a 'Drill Hole' no longer adds its outline and crosshair to the drill file
+
BUG_FIX
Rotated labels in pasted components and building blocks were drawn in the wrong place and at the wrong angle while following the mouse, until the components were placed
diff --git a/diylc/diylc-library/pom.xml b/diylc/diylc-library/pom.xml
index ca8b072b5..aae9cd5c0 100644
--- a/diylc/diylc-library/pom.xml
+++ b/diylc/diylc-library/pom.xml
@@ -73,10 +73,10 @@
org.diylc.local
- gerber-writer-api-1.1.0
+ gerber-writer-api-1.2.0
1.0
system
- ${project.basedir}/../lib/gerber-writer-api-1.1.0.jar
+ ${project.basedir}/../lib/gerber-writer-api-1.2.0.jar
org.diylc.local
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Via.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Via.java
index 01261a6e4..79c9b15f0 100644
--- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Via.java
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Via.java
@@ -71,9 +71,6 @@ public class Via extends AbstractTransparentComponent
public static Size HOLE_SIZE = new Size(0.4d, SizeUnit.mm);
public static Color COLOR = Color.black;
- // gerber-writer has no constant for it
- private static final String VIA_DRILL = "ViaDrill";
-
private Size size = SIZE;
private Size holeSize = HOLE_SIZE;
private Color color = COLOR;
@@ -112,7 +109,8 @@ public class Via extends AbstractTransparentComponent
if (gerberDrawingObserver != null) {
gerberDrawingObserver.stopGerberOutput(GerberLayer.CopperTop);
gerberDrawingObserver.stopGerberOutput(GerberLayer.CopperBot);
- gerberDrawingObserver.startGerberOutput(GerberLayer.DrillPlated, VIA_DRILL, false);
+ gerberDrawingObserver.startGerberOutput(GerberLayer.DrillPlated, GerberFunctions.VIA_DRILL,
+ false);
}
g2d.setColor(Constants.CANVAS_COLOR);
g2d.fill(new Ellipse2D.Double(point.getX() - holeDiameter / 2,
diff --git a/diylc/diylc-library/src/test/java/org/diylc/components/connectivity/SolderPadGerberTest.java b/diylc/diylc-library/src/test/java/org/diylc/components/connectivity/SolderPadGerberTest.java
index 89b7361f7..ea50b3f3b 100644
--- a/diylc/diylc-library/src/test/java/org/diylc/components/connectivity/SolderPadGerberTest.java
+++ b/diylc/diylc-library/src/test/java/org/diylc/components/connectivity/SolderPadGerberTest.java
@@ -21,18 +21,20 @@
*/
package org.diylc.components.connectivity;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
-import java.util.Map;
+import java.util.EnumSet;
import java.util.Set;
-import com.bancika.gerberwriter.DataLayer;
+import com.bancika.gerberwriter.DrillFormat;
import org.diylc.common.PCBLayer;
+import org.diylc.components.electromechanical.DrillHole;
import org.diylc.core.ComponentState;
import org.diylc.core.Project;
import org.diylc.core.gerber.GerberG2DWrapper;
@@ -79,7 +81,7 @@ public class SolderPadGerberTest {
@Test
public void testViaIsOnBothLayersAndTented() {
Via via = new Via();
- Set layers = drawGerber(via).keySet();
+ Set layers = layersOf(drawGerber(via));
assertTrue(layers.contains(GerberLayer.CopperTop));
assertTrue(layers.contains(GerberLayer.CopperBot));
assertTrue(layers.contains(GerberLayer.DrillPlated));
@@ -95,11 +97,32 @@ public class SolderPadGerberTest {
assertNoCutOut(drawGerber(new Via()));
}
+ @Test
+ public void testViaIsDrilledOnceAsViaDrill() {
+ String excellon = drawGerber(new Via()).getDrillLayerMap().get(GerberLayer.DrillPlated)
+ .dumpToString(DrillFormat.EXCELLON);
+ assertTrue(excellon.contains("; #@! TA.AperFunction,Plated,PTH,ViaDrill\nT1C0.4\n"));
+ assertEquals(1, countHoles(excellon));
+ }
+
+ @Test
+ public void testDrillHoleMarkerIsDrilledOnce() {
+ // the marker's border and crosshair are drawn too, but they are not holes
+ String excellon = drawGerber(new DrillHole()).getDrillLayerMap()
+ .get(GerberLayer.DrillNonPlated).dumpToString(DrillFormat.EXCELLON);
+ assertTrue(excellon.contains("T1C3.0\n"));
+ assertEquals(1, countHoles(excellon));
+ }
+
+ private static long countHoles(String excellon) {
+ return excellon.lines().filter(line -> line.startsWith("X")).count();
+ }
+
// a cut-out is drawn with clear polarity, %LPC*%
- private static void assertNoCutOut(Map layers) {
+ private static void assertNoCutOut(GerberG2DWrapper g2d) {
for (GerberLayer copperLayer : new GerberLayer[] {GerberLayer.CopperTop,
GerberLayer.CopperBot}) {
- String gerber = layers.get(copperLayer).dumpGerberToString();
+ String gerber = g2d.getLayerMap().get(copperLayer).dumpGerberToString();
assertTrue(gerber.contains("D03*"));
assertFalse(gerber.contains("%LPC*%"));
}
@@ -113,10 +136,17 @@ public class SolderPadGerberTest {
SolderPad pad = new SolderPad();
pad.setLayer(layer);
pad.setHoleType(holeType);
- return drawGerber(pad).keySet();
+ return layersOf(drawGerber(pad));
}
- private static Map drawGerber(IGerberComponentCustom component) {
+ private static Set layersOf(GerberG2DWrapper g2d) {
+ Set layers = EnumSet.noneOf(GerberLayer.class);
+ layers.addAll(g2d.getLayerMap().keySet());
+ layers.addAll(g2d.getDrillLayerMap().keySet());
+ return layers;
+ }
+
+ private static GerberG2DWrapper drawGerber(IGerberComponentCustom component) {
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics();
GerberG2DWrapper g2d =
@@ -124,6 +154,6 @@ public class SolderPadGerberTest {
g2d.startedDrawingComponent();
component.draw(g2d, ComponentState.NORMAL, false, new Project(), g2d, g2d);
graphics.dispose();
- return g2d.getLayerMap();
+ return g2d;
}
}
diff --git a/diylc/diylc-swing/pom.xml b/diylc/diylc-swing/pom.xml
index 58833ef53..e81c992c1 100644
--- a/diylc/diylc-swing/pom.xml
+++ b/diylc/diylc-swing/pom.xml
@@ -140,10 +140,10 @@
org.diylc.local
- gerber-writer-api-1.1.0
+ gerber-writer-api-1.2.0
1.0
system
- ${project.basedir}/../lib/gerber-writer-api-1.1.0.jar
+ ${project.basedir}/../lib/gerber-writer-api-1.2.0.jar
org.diylc.local
diff --git a/diylc/lib/gerber-writer-api-1.1.0.jar b/diylc/lib/gerber-writer-api-1.1.0.jar
deleted file mode 100644
index 502ca7919..000000000
Binary files a/diylc/lib/gerber-writer-api-1.1.0.jar and /dev/null differ
diff --git a/diylc/lib/gerber-writer-api-1.2.0.jar b/diylc/lib/gerber-writer-api-1.2.0.jar
new file mode 100644
index 000000000..a69a45c2e
Binary files /dev/null and b/diylc/lib/gerber-writer-api-1.2.0.jar differ