A component can report which side of the board it is mounted on, and Gerber export puts the silkscreen of a part mounted underneath on the bottom layer

This commit is contained in:
Bane Stojkovic
2026-11-11 14:48:57 +01:00
parent 324cb20634
commit 3506c97403
4 changed files with 113 additions and 25 deletions
@@ -0,0 +1,42 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
package org.diylc.core;
import org.diylc.common.PCBLayer;
/**
* Interface for components mounted on one side of a board, such as a resistor or an IC. The side
* says where the part sits physically; it is separate from {@link ILayeredComponent}, which drives
* continuity, because the leads of a through-hole part connect to copper on both sides whichever
* side the part is mounted on. Components whose copper already has a layer (traces, pads, ground
* fills) do not implement it.
*
* @author Branislav Stojkovic
*/
public interface IBoardSideComponent {
/**
* @return {@link PCBLayer#_2} for the top of the board or {@link PCBLayer#_1} for the bottom,
* never an inner layer or null
*/
PCBLayer getBoardSide();
}
@@ -16,8 +16,10 @@ import org.apache.log4j.Logger;
import com.bancika.gerberwriter.DataLayer;
import org.diylc.common.PCBLayer;
import org.diylc.core.ComponentState;
import org.diylc.core.IBoard;
import org.diylc.core.IBoardSideComponent;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IView;
import org.diylc.core.Project;
@@ -158,8 +160,14 @@ public class GerberExporter {
gerberComponentCustom.draw(g2d, ComponentState.NORMAL, outlineMode, currentProject, g2d, g2d);
} else if (gerberComponent instanceof IGerberComponentSimple) {
IGerberComponentSimple gerberComponentSimple = (IGerberComponentSimple) gerberComponent;
g2d.startGerberOutput(gerberComponentSimple.getGerberLayer(),
gerberComponentSimple.getGerberFunction(), gerberComponentSimple.isGerberNegative());
GerberLayer layer = gerberComponentSimple.getGerberLayer();
// components emit their top-side layer; a part mounted underneath goes to the bottom one
if (c instanceof IBoardSideComponent
&& ((IBoardSideComponent) c).getBoardSide() == PCBLayer._1) {
layer = layer.flip();
}
g2d.startGerberOutput(layer, gerberComponentSimple.getGerberFunction(),
gerberComponentSimple.isGerberNegative());
c.draw(g2d, ComponentState.NORMAL, outlineMode, currentProject, g2d);
g2d.stopGerberOutput();
}
@@ -29,7 +29,9 @@ import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.EnumSet;
import org.diylc.common.PCBLayer;
import org.diylc.core.ComponentState;
import org.diylc.core.IBoardSideComponent;
import org.diylc.core.IDrawingObserver;
import org.diylc.core.Project;
import org.diylc.testcomponents.MockDIYComponent;
@@ -51,6 +53,27 @@ public class GerberExporterTest {
assertEquals(EnumSet.of(GerberLayer.CopperBot), EnumSet.copyOf(g2d.getLayerMap().keySet()));
}
@Test
public void testTopSideComponentKeepsItsLayer() {
GerberG2DWrapper g2d = draw(new SidedGerberComponent(PCBLayer._2));
assertEquals(EnumSet.of(GerberLayer.SilkscreenTop), EnumSet.copyOf(g2d.getLayerMap().keySet()));
}
@Test
public void testBottomSideComponentGoesToTheOtherSide() {
GerberG2DWrapper g2d = draw(new SidedGerberComponent(PCBLayer._1));
assertEquals(EnumSet.of(GerberLayer.SilkscreenBot), EnumSet.copyOf(g2d.getLayerMap().keySet()));
}
@Test
public void testComponentWithoutSideStaysOnTop() {
GerberG2DWrapper g2d = draw(new SidedGerberComponent(null));
assertEquals(EnumSet.of(GerberLayer.SilkscreenTop), EnumSet.copyOf(g2d.getLayerMap().keySet()));
}
private static GerberG2DWrapper draw(SimpleGerberComponent component) {
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics();
@@ -85,4 +108,22 @@ public class GerberExporterTest {
g2d.fill(new Rectangle2D.Double(10, 10, 20, 20));
}
}
public static class SidedGerberComponent extends SimpleGerberComponent
implements IBoardSideComponent {
private static final long serialVersionUID = 1L;
private PCBLayer side;
public SidedGerberComponent(PCBLayer side) {
super(null);
this.side = side;
}
@Override
public PCBLayer getBoardSide() {
return side;
}
}
}