Gerber export no longer cuts the holes of solder pads and vias out of the copper layers; the drill files alone define the holes

This commit is contained in:
Bane Stojkovic
2026-11-12 11:08:52 +01:00
parent 2439510f35
commit 34490b50b2
4 changed files with 37 additions and 9 deletions
@@ -669,6 +669,10 @@
<changeType>IMPROVEMENT</changeType> <changeType>IMPROVEMENT</changeType>
<description>A 'Solder Pad' with a plated through hole now has copper on both layers, like the real thing, and joins top and bottom copper in netlists and 'Highlight Connected Areas'; Gerber export writes it to both copper and both solder mask layers</description> <description>A 'Solder Pad' with a plated through hole now has copper on both layers, like the real thing, and joins top and bottom copper in netlists and 'Highlight Connected Areas'; Gerber export writes it to both copper and both solder mask layers</description>
</org.diylc.appframework.update.Change> </org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>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</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change> <org.diylc.appframework.update.Change>
<changeType>BUG_FIX</changeType> <changeType>BUG_FIX</changeType>
<description>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</description> <description>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</description>
@@ -125,16 +125,16 @@ public class SolderPad extends AbstractTransparentComponent<Void>
drawingObserver.stopDrawingSide(); drawingObserver.stopDrawingSide();
drawingObserver.stopTracking(); drawingObserver.stopTracking();
drawingObserver.stopTrackingContinuityArea(); drawingObserver.stopTrackingContinuityArea();
// the hole goes to the drill file only; cutting it out of the copper as well is advised against
// by the Gerber specification
if (gerberDrawingObserver != null) { if (gerberDrawingObserver != null) {
for (GerberLayer gerberCopperLayer : gerberCopperLayers) { for (GerberLayer gerberCopperLayer : gerberCopperLayers) {
gerberDrawingObserver.stopGerberOutput(gerberCopperLayer);
gerberDrawingObserver.stopGerberOutput(getMaskLayer(gerberCopperLayer)); gerberDrawingObserver.stopGerberOutput(getMaskLayer(gerberCopperLayer));
} }
} }
if (getHoleSize().getValue() > 0 && !HoleType.NONE.equals(getHoleType())) { if (getHoleSize().getValue() > 0 && !HoleType.NONE.equals(getHoleType())) {
if (gerberDrawingObserver != null) { if (gerberDrawingObserver != null) {
for (GerberLayer gerberCopperLayer : gerberCopperLayers) {
gerberDrawingObserver.setGerberNegative(gerberCopperLayer, true);
}
GerberLayer holeLayer = HoleType.PTH.equals(getHoleType()) ? GerberLayer.DrillPlated : GerberLayer.DrillNonPlated; GerberLayer holeLayer = HoleType.PTH.equals(getHoleType()) ? GerberLayer.DrillPlated : GerberLayer.DrillNonPlated;
gerberDrawingObserver.startGerberOutput(holeLayer, GerberFunctions.COMPONENT_DRILL, false); gerberDrawingObserver.startGerberOutput(holeLayer, GerberFunctions.COMPONENT_DRILL, false);
} }
@@ -107,9 +107,11 @@ public class Via extends AbstractTransparentComponent<Void>
diameter, diameter)); diameter, diameter));
drawingObserver.stopTracking(); drawingObserver.stopTracking();
drawingObserver.stopTrackingContinuityArea(); drawingObserver.stopTrackingContinuityArea();
// the hole goes to the drill file only; cutting it out of the copper as well is advised against
// by the Gerber specification
if (gerberDrawingObserver != null) { if (gerberDrawingObserver != null) {
gerberDrawingObserver.setGerberNegative(GerberLayer.CopperTop, true); gerberDrawingObserver.stopGerberOutput(GerberLayer.CopperTop);
gerberDrawingObserver.setGerberNegative(GerberLayer.CopperBot, true); gerberDrawingObserver.stopGerberOutput(GerberLayer.CopperBot);
gerberDrawingObserver.startGerberOutput(GerberLayer.DrillPlated, VIA_DRILL, false); gerberDrawingObserver.startGerberOutput(GerberLayer.DrillPlated, VIA_DRILL, false);
} }
g2d.setColor(Constants.CANVAS_COLOR); g2d.setColor(Constants.CANVAS_COLOR);
@@ -21,13 +21,17 @@
*/ */
package org.diylc.components.connectivity; package org.diylc.components.connectivity;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.Map;
import java.util.Set; import java.util.Set;
import com.bancika.gerberwriter.DataLayer;
import org.diylc.common.PCBLayer; import org.diylc.common.PCBLayer;
import org.diylc.core.ComponentState; import org.diylc.core.ComponentState;
import org.diylc.core.Project; import org.diylc.core.Project;
@@ -75,7 +79,7 @@ public class SolderPadGerberTest {
@Test @Test
public void testViaIsOnBothLayersAndTented() { public void testViaIsOnBothLayersAndTented() {
Via via = new Via(); Via via = new Via();
Set<GerberLayer> layers = drawGerber(via); Set<GerberLayer> layers = drawGerber(via).keySet();
assertTrue(layers.contains(GerberLayer.CopperTop)); assertTrue(layers.contains(GerberLayer.CopperTop));
assertTrue(layers.contains(GerberLayer.CopperBot)); assertTrue(layers.contains(GerberLayer.CopperBot));
assertTrue(layers.contains(GerberLayer.DrillPlated)); assertTrue(layers.contains(GerberLayer.DrillPlated));
@@ -83,6 +87,24 @@ public class SolderPadGerberTest {
assertTrue(!layers.contains(GerberLayer.SolderMaskBot)); assertTrue(!layers.contains(GerberLayer.SolderMaskBot));
} }
@Test
public void testHolesAreNotCutOutOfCopper() {
SolderPad pad = new SolderPad();
pad.setHoleType(SolderPad.HoleType.PTH);
assertNoCutOut(drawGerber(pad));
assertNoCutOut(drawGerber(new Via()));
}
// a cut-out is drawn with clear polarity, %LPC*%
private static void assertNoCutOut(Map<GerberLayer, DataLayer> layers) {
for (GerberLayer copperLayer : new GerberLayer[] {GerberLayer.CopperTop,
GerberLayer.CopperBot}) {
String gerber = layers.get(copperLayer).dumpGerberToString();
assertTrue(gerber.contains("D03*"));
assertFalse(gerber.contains("%LPC*%"));
}
}
private static Set<GerberLayer> drawGerber(PCBLayer layer) { private static Set<GerberLayer> drawGerber(PCBLayer layer) {
return drawGerber(layer, SolderPad.HoleType.NTPH); return drawGerber(layer, SolderPad.HoleType.NTPH);
} }
@@ -91,10 +113,10 @@ public class SolderPadGerberTest {
SolderPad pad = new SolderPad(); SolderPad pad = new SolderPad();
pad.setLayer(layer); pad.setLayer(layer);
pad.setHoleType(holeType); pad.setHoleType(holeType);
return drawGerber(pad); return drawGerber(pad).keySet();
} }
private static Set<GerberLayer> drawGerber(IGerberComponentCustom component) { private static Map<GerberLayer, DataLayer> drawGerber(IGerberComponentCustom component) {
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics(); Graphics2D graphics = img.createGraphics();
GerberG2DWrapper g2d = GerberG2DWrapper g2d =
@@ -102,6 +124,6 @@ public class SolderPadGerberTest {
g2d.startedDrawingComponent(); g2d.startedDrawingComponent();
component.draw(g2d, ComponentState.NORMAL, false, new Project(), g2d, g2d); component.draw(g2d, ComponentState.NORMAL, false, new Project(), g2d, g2d);
graphics.dispose(); graphics.dispose();
return g2d.getLayerMap().keySet(); return g2d.getLayerMap();
} }
} }