mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-13 02:32:09 +10:00
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:
@@ -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 com.bancika.gerberwriter.DataLayer;
|
||||||
|
|
||||||
|
import org.diylc.common.PCBLayer;
|
||||||
import org.diylc.core.ComponentState;
|
import org.diylc.core.ComponentState;
|
||||||
import org.diylc.core.IBoard;
|
import org.diylc.core.IBoard;
|
||||||
|
import org.diylc.core.IBoardSideComponent;
|
||||||
import org.diylc.core.IDIYComponent;
|
import org.diylc.core.IDIYComponent;
|
||||||
import org.diylc.core.IView;
|
import org.diylc.core.IView;
|
||||||
import org.diylc.core.Project;
|
import org.diylc.core.Project;
|
||||||
@@ -158,8 +160,14 @@ public class GerberExporter {
|
|||||||
gerberComponentCustom.draw(g2d, ComponentState.NORMAL, outlineMode, currentProject, g2d, g2d);
|
gerberComponentCustom.draw(g2d, ComponentState.NORMAL, outlineMode, currentProject, g2d, g2d);
|
||||||
} else if (gerberComponent instanceof IGerberComponentSimple) {
|
} else if (gerberComponent instanceof IGerberComponentSimple) {
|
||||||
IGerberComponentSimple gerberComponentSimple = (IGerberComponentSimple) gerberComponent;
|
IGerberComponentSimple gerberComponentSimple = (IGerberComponentSimple) gerberComponent;
|
||||||
g2d.startGerberOutput(gerberComponentSimple.getGerberLayer(),
|
GerberLayer layer = gerberComponentSimple.getGerberLayer();
|
||||||
gerberComponentSimple.getGerberFunction(), gerberComponentSimple.isGerberNegative());
|
// 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);
|
c.draw(g2d, ComponentState.NORMAL, outlineMode, currentProject, g2d);
|
||||||
g2d.stopGerberOutput();
|
g2d.stopGerberOutput();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ import java.awt.geom.Rectangle2D;
|
|||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import org.diylc.common.PCBLayer;
|
||||||
import org.diylc.core.ComponentState;
|
import org.diylc.core.ComponentState;
|
||||||
|
import org.diylc.core.IBoardSideComponent;
|
||||||
import org.diylc.core.IDrawingObserver;
|
import org.diylc.core.IDrawingObserver;
|
||||||
import org.diylc.core.Project;
|
import org.diylc.core.Project;
|
||||||
import org.diylc.testcomponents.MockDIYComponent;
|
import org.diylc.testcomponents.MockDIYComponent;
|
||||||
@@ -51,6 +53,27 @@ public class GerberExporterTest {
|
|||||||
assertEquals(EnumSet.of(GerberLayer.CopperBot), EnumSet.copyOf(g2d.getLayerMap().keySet()));
|
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) {
|
private static GerberG2DWrapper draw(SimpleGerberComponent 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();
|
||||||
@@ -85,4 +108,22 @@ public class GerberExporterTest {
|
|||||||
g2d.fill(new Rectangle2D.Double(10, 10, 20, 20));
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
Let a component be mounted on the bottom of a PCB, so that its silkscreen goes to the bottom
|
Let a component be mounted on the bottom of a PCB, so that its silkscreen goes to the bottom
|
||||||
Gerber layers and the canvas shows it as a part on the far side of the board.
|
Gerber layers and the canvas shows it as a part on the far side of the board.
|
||||||
|
|
||||||
Status: the Gerber dispatch is a single method and `GerberLayer.flip()` exists, with tests; the
|
Status: the core Gerber side is done, with tests: `IBoardSideComponent` exists and the export
|
||||||
next steps below remain.
|
sends a bottom-side part to the bottom layers. No component implements it yet; the next steps below
|
||||||
|
remain.
|
||||||
|
|
||||||
## 1. Current state
|
## 1. Current state
|
||||||
|
|
||||||
@@ -20,10 +21,12 @@ next steps below remain.
|
|||||||
default (resistors, film and radial capacitors, diodes, LEDs, transistor packages, DIL/SIL ICs,
|
default (resistors, film and radial capacitors, diodes, LEDs, transistor packages, DIL/SIL ICs,
|
||||||
SMD passives, switches, jacks, relays, transformers, tube sockets, ...). None of them emit copper.
|
SMD passives, switches, jacks, relays, transformers, tube sockets, ...). None of them emit copper.
|
||||||
Their pads are separate `SolderPad` components, which already have a layer.
|
Their pads are separate `SolderPad` components, which already have a layer.
|
||||||
- One method decides the layer for a simple component: `GerberExporter.drawComponent`, which
|
- One method decides the layer for a simple component: `GerberExporter.drawComponent`. It is
|
||||||
calls `g2d.startGerberOutput(getGerberLayer(), ...)`. It is package-private so that
|
package-private so that `GerberExporterTest` can call it. It uses `getGerberLayer()`, flipped
|
||||||
`GerberExporterTest` can call it. `GerberLayer.flip()` maps a layer to the other side of the
|
with `GerberLayer.flip()` when the component is an `IBoardSideComponent` on `_1` (Bottom).
|
||||||
board.
|
- **`org.diylc.core.IBoardSideComponent`**: `PCBLayer getBoardSide()`, returning `_1` or `_2`
|
||||||
|
only. It is deliberately separate from `ILayeredComponent` (see Decisions). Core knows a part's
|
||||||
|
side only through this interface.
|
||||||
- Z-order is paint order only. A trace on the bottom layer already paints above the board
|
- Z-order is paint order only. A trace on the bottom layer already paints above the board
|
||||||
(`zOrder = TRACE`), so the codebase already treats the physical side as separate from z-order.
|
(`zOrder = TRACE`), so the codebase already treats the physical side as separate from z-order.
|
||||||
|
|
||||||
@@ -43,11 +46,6 @@ next steps below remain.
|
|||||||
|
|
||||||
### diylc-core
|
### diylc-core
|
||||||
|
|
||||||
- **`org.diylc.core.IBoardSideComponent`**: `PCBLayer getBoardSide();` with Javadoc and
|
|
||||||
`@author`, in the same shape as `ILayeredComponent` but deliberately separate from it (see
|
|
||||||
Decisions). It returns `_1` or `_2` only. Core knows the side only through this interface.
|
|
||||||
- **Gerber dispatch**: in the simple-component branch of `GerberExporter.drawComponent`, use
|
|
||||||
`getGerberLayer().flip()` when the component is an `IBoardSideComponent` on `_1`.
|
|
||||||
- **Canvas rendering**: bottom-side components use the far-side fade from `copper-side-view.md`,
|
- **Canvas rendering**: bottom-side components use the far-side fade from `copper-side-view.md`,
|
||||||
wrapping their painting in `startDrawingSide(getBoardSide())`. They are then faded in the main
|
wrapping their painting in `startDrawingSide(getBoardSide())`. They are then faded in the main
|
||||||
view and solid in the Underside Display mirror, the same as bottom copper.
|
view and solid in the Underside Display mirror, the same as bottom copper.
|
||||||
@@ -133,8 +131,8 @@ order.
|
|||||||
|
|
||||||
## 5. Verification
|
## 5. Verification
|
||||||
|
|
||||||
- `GerberLayerTest` covers `flip()`. `GerberExporterTest` covers the dispatch for simple components;
|
- `GerberLayerTest` covers `flip()`. `GerberExporterTest` covers the dispatch for simple components,
|
||||||
extend it with a stub `IBoardSideComponent` (top, bottom and `null`).
|
including a stub `IBoardSideComponent` on top, bottom and `null`.
|
||||||
- Load an old `.diy` with PCB parts and confirm every component reports `_2` (Top) and that the Gerber
|
- Load an old `.diy` with PCB parts and confirm every component reports `_2` (Top) and that the Gerber
|
||||||
output is byte-identical to before.
|
output is byte-identical to before.
|
||||||
- Export a board with one part on each side and open the files in a Gerber viewer: the bottom part
|
- Export a board with one part on each side and open the files in a Gerber viewer: the bottom part
|
||||||
@@ -148,15 +146,14 @@ order.
|
|||||||
|
|
||||||
## 6. Next steps
|
## 6. Next steps
|
||||||
|
|
||||||
1. Add `IBoardSideComponent` and swap the Gerber layer in the dispatch.
|
1. Add the field to the library components, putting it on shared bases where they exist. Settle
|
||||||
2. Add the field to the library components, putting it on shared bases where they exist. Settle
|
|
||||||
the silkscreen-text question while doing so.
|
the silkscreen-text question while doing so.
|
||||||
3. Wrap bottom-side painting in `startDrawingSide(getBoardSide())`.
|
2. Wrap bottom-side painting in `startDrawingSide(getBoardSide())`.
|
||||||
4. Add the Flip to Other Side action.
|
3. Add the Flip to Other Side action.
|
||||||
5. Split the silkscreen print-out by side.
|
4. Split the silkscreen print-out by side.
|
||||||
6. Extract the mirror geometry helper, with tests, and use it in `drawMirroredComponent`.
|
5. Extract the mirror geometry helper, with tests, and use it in `drawMirroredComponent`.
|
||||||
7. Mirror stage 1: selecting through the mirror. It does not depend on steps 1-5 and can ship
|
6. Mirror stage 1: selecting through the mirror. It does not depend on steps 1-4 and can ship
|
||||||
early.
|
early.
|
||||||
8. Mirror stage 2: dragging through the mirror.
|
7. Mirror stage 2: dragging through the mirror.
|
||||||
9. Mirror stage 3: placing in the mirror, after Flip to Other Side exists.
|
8. Mirror stage 3: placing in the mirror, after Flip to Other Side exists.
|
||||||
10. Run the regression suite.
|
9. Run the regression suite.
|
||||||
|
|||||||
Reference in New Issue
Block a user