diff --git a/diylc/catalog_full.json b/diylc/catalog_full.json index 0dfa62df2..30cdbd8b1 100644 --- a/diylc/catalog_full.json +++ b/diylc/catalog_full.json @@ -1712,7 +1712,7 @@ "name" : "Hole Type", "type" : "HoleType", "possibleValues" : [ "NONE", "PTH", "NTPH" ], - "defaultValue" : "NTPH" + "defaultValue" : "PTH" }, { "name" : "Layer", "type" : "PCBLayer", diff --git a/diylc/diylc-core/src/main/resources/update.xml b/diylc/diylc-core/src/main/resources/update.xml index b7a8ed9c7..18d97159f 100644 --- a/diylc/diylc-core/src/main/resources/update.xml +++ b/diylc/diylc-core/src/main/resources/update.xml @@ -667,7 +667,7 @@ IMPROVEMENT - 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 + A 'Solder Pad' with a plated through hole is now a double-sided pad, like the real thing: it has copper on both layers, joins top and bottom copper in netlists and 'Highlight Connected Areas', and Gerber export writes it to both copper and both solder mask layers, so its 'Layer' no longer applies. New solder pads have a plated through hole by default, which is what PCB fabs make; pads in existing projects keep their hole type IMPROVEMENT @@ -677,6 +677,10 @@ 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 + + IMPROVEMENT + Gerber export marks solder pads with a hole as component pads and pads without one as SMD pads, instead of marking every pad as a connector pad + 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/src/main/java/org/diylc/components/connectivity/SolderPad.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/SolderPad.java index 40ba75b2d..5d1e0f108 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/SolderPad.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/SolderPad.java @@ -75,7 +75,7 @@ public class SolderPad extends AbstractTransparentComponent private Type type = Type.ROUND; private Size holeSize = HOLE_SIZE; private PCBLayer layer = PCBLayer._1; - private HoleType holeType = HoleType.NTPH; + private HoleType holeType = HoleType.PTH; private String nodeName = ""; @Override @@ -97,8 +97,7 @@ public class SolderPad extends AbstractTransparentComponent : new GerberLayer[] {getLayer().toGerberCopperLayer()}; if (gerberDrawingObserver != null) { for (GerberLayer gerberCopperLayer : gerberCopperLayers) { - gerberDrawingObserver.startGerberOutput(gerberCopperLayer, GerberFunctions.CONNECTOR_PAD, - false); + gerberDrawingObserver.startGerberOutput(gerberCopperLayer, getGerberPadFunction(), false); gerberDrawingObserver.startGerberOutput(getMaskLayer(gerberCopperLayer), "Material", false); } } @@ -178,6 +177,7 @@ public class SolderPad extends AbstractTransparentComponent @EditableProperty(name = "Hole Type") public HoleType getHoleType() { + // pads saved before the hole type existed had non-plated holes; new pads are plated if (holeType == null) { holeType = HoleType.NTPH; } @@ -226,6 +226,12 @@ public class SolderPad extends AbstractTransparentComponent return HoleType.PTH.equals(getHoleType()); } + // a pad with a hole takes a part's lead; one without is a surface mount pad + private String getGerberPadFunction() { + return HoleType.NONE.equals(getHoleType()) ? GerberFunctions.SMDPAD_CU_DEF + : GerberFunctions.COMPONENT_PAD; + } + // the mask is opened on the side the copper is on private static GerberLayer getMaskLayer(GerberLayer gerberCopperLayer) { return gerberCopperLayer == GerberLayer.CopperTop ? GerberLayer.SolderMaskTop 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 ea50b3f3b..b38ba66df 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 @@ -97,6 +97,23 @@ public class SolderPadGerberTest { assertNoCutOut(drawGerber(new Via())); } + @Test + public void testPadWithHoleIsComponentPad() { + SolderPad pad = new SolderPad(); + pad.setHoleType(SolderPad.HoleType.NTPH); + String gerber = drawGerber(pad).getLayerMap().get(GerberLayer.CopperBot).dumpGerberToString(); + assertTrue(gerber.contains("TA.AperFunction,ComponentPad*")); + assertFalse(gerber.contains("ConnectorPad")); + } + + @Test + public void testPadWithoutHoleIsSmdPad() { + SolderPad pad = new SolderPad(); + pad.setHoleType(SolderPad.HoleType.NONE); + String gerber = drawGerber(pad).getLayerMap().get(GerberLayer.CopperBot).dumpGerberToString(); + assertTrue(gerber.contains("TA.AperFunction,SMDPad,CuDef*")); + } + @Test public void testViaIsDrilledOnceAsViaDrill() { String excellon = drawGerber(new Via()).getDrillLayerMap().get(GerberLayer.DrillPlated) diff --git a/diylc/diylc-library/src/test/java/org/diylc/serialization/SolderPadCompatibilityTest.java b/diylc/diylc-library/src/test/java/org/diylc/serialization/SolderPadCompatibilityTest.java new file mode 100644 index 000000000..243c2a65c --- /dev/null +++ b/diylc/diylc-library/src/test/java/org/diylc/serialization/SolderPadCompatibilityTest.java @@ -0,0 +1,65 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2025 held jointly by the individual authors. + + This file is part of DIYLC. + + DIYLC 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. + + DIYLC 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 DIYLC. If not, see . + +*/ +package org.diylc.serialization; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; + +import org.diylc.components.connectivity.SolderPad; +import org.junit.Assert; +import org.junit.Test; + +/** + * New solder pads are plated, but pads saved before the hole type existed had non-plated holes + * and must keep them, so that old projects export and net as before. + */ +public class SolderPadCompatibilityTest { + + private static XStream createXStream() { + XStream xStream = new XStream(new DomDriver("UTF-8")); + ProjectFileManager.configure(xStream); + return xStream; + } + + @Test + public void newPadIsPlated() { + Assert.assertEquals(SolderPad.HoleType.PTH, new SolderPad().getHoleType()); + } + + @Test + public void padSavedWithoutHoleTypeLoadsNonPlated() { + String xml = createXStream().toXML(new SolderPad()); + Assert.assertTrue(xml.contains("PTH")); + + SolderPad loaded = + (SolderPad) createXStream().fromXML(xml.replaceAll("\\s*PTH", "")); + Assert.assertEquals(SolderPad.HoleType.NTPH, loaded.getHoleType()); + } + + @Test + public void savedNonPlatedPadStaysNonPlated() { + SolderPad pad = new SolderPad(); + pad.setHoleType(SolderPad.HoleType.NTPH); + SolderPad loaded = (SolderPad) createXStream().fromXML(createXStream().toXML(pad)); + Assert.assertEquals(SolderPad.HoleType.NTPH, loaded.getHoleType()); + } +} diff --git a/diylc/docs/plans/bottom-side-components.md b/diylc/docs/plans/bottom-side-components.md index 2de980255..e4e0bff61 100644 --- a/diylc/docs/plans/bottom-side-components.md +++ b/diylc/docs/plans/bottom-side-components.md @@ -57,7 +57,9 @@ projects; the open questions below remain. | Bottom-side text is mirrored centrally in the Gerber output | Parts draw their labels readable from the top, which is backwards on the bottom silkscreen once the board is turned over. While `GerberExporter.drawComponent` draws a bottom-side part, `GerberG2DWrapper.setMirrorText` mirrors each string about the centre of its layout box, so no component changes and the canvas keeps readable labels. | | A part without a side placed in the mirror is placed unchanged | A jumper, eyelet or wire has no side to set, so it is placed at the reflected point like any other part rather than refused. | | A `SolderPad` opens the solder mask on its own side | It used to write `SolderMaskBot` whatever its layer, which was wrong for top-layer pads. It now writes `SolderMaskTop` for a pad on `_2`. | -| A `SolderPad` with a plated through hole is on both layers, and its Layer is ignored | A plated hole has a copper ring on each side joined by the barrel. The pad reports layer id `0`, which connects to copper on every layer, draws as both sides, and writes both copper and both mask layers. Projects where such a pad overlaps a trace on its other side now net differently; PTH is not the default and arrived with Gerber export, so few projects are affected. | +| A `SolderPad` with a plated through hole is on both layers, and its Layer is ignored | A plated hole has a copper ring on each side joined by the barrel. The pad reports layer id `0`, which connects to copper on every layer, draws as both sides, and writes both copper and both mask layers. Projects where such a pad overlaps a trace on its other side now net differently; PTH was not the default before and arrived with Gerber export, so few projects are affected. | +| New `SolderPad`s are PTH; a pad saved without a hole type stays NPTH | Fab-made boards mostly want plated holes. The getter still falls back to NPTH, which is what pads saved before the hole type existed had, so old projects export and net as before. | +| A `SolderPad` writes `ComponentPad` to Gerber when it has a hole and `SMDPad,CuDef` when it has none | `ConnectorPad`, written for every pad before, is meant for edge connector pads. | | `Via` is its own component, not a `SolderPad` option | It takes no lead and no node name, is tented (no mask opening), and has its own Gerber functions, `ViaPad` and `ViaDrill`. Like the PTH pad it reports layer id `0`. It has no Board Side, like `DrillHole`. | ## 3. Design