mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-13 02:32:09 +10:00
Gerber export decides each component's layer in one place, and Gerber layers can be mapped to the other side of the board
This commit is contained in:
@@ -145,8 +145,12 @@ public class GerberExporter {
|
||||
LOG.info("Completed export to gerber");
|
||||
}
|
||||
|
||||
private static void drawComponent(Project currentProject, GerberG2DWrapper g2d,
|
||||
IDIYComponent<?> c, boolean outlineMode) {
|
||||
/**
|
||||
* Draws the specified component into the Gerber layers it belongs to. This is the only place that
|
||||
* decides which layer a component's output goes to.
|
||||
*/
|
||||
static void drawComponent(Project currentProject, GerberG2DWrapper g2d,
|
||||
IDIYComponent<?> c, boolean outlineMode) {
|
||||
IGerberComponent gerberComponent = (IGerberComponent) c;
|
||||
g2d.startedDrawingComponent();
|
||||
if (gerberComponent instanceof IGerberComponentCustom) {
|
||||
|
||||
@@ -54,6 +54,29 @@ public enum GerberLayer {
|
||||
public boolean isMirrored() {
|
||||
return mirrored;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the same layer on the other side of the board; the outline and the drill layers go
|
||||
* through the whole board and are returned unchanged
|
||||
*/
|
||||
public GerberLayer flip() {
|
||||
switch (this) {
|
||||
case CopperBot:
|
||||
return CopperTop;
|
||||
case CopperTop:
|
||||
return CopperBot;
|
||||
case SilkscreenBot:
|
||||
return SilkscreenTop;
|
||||
case SilkscreenTop:
|
||||
return SilkscreenBot;
|
||||
case SolderMaskBot:
|
||||
return SolderMaskTop;
|
||||
case SolderMaskTop:
|
||||
return SolderMaskBot;
|
||||
default:
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public String formatFileName(String fileNameBase, String boardName) {
|
||||
return fileNameBase + (fileNameBase.endsWith(".") ? "" : ".")
|
||||
|
||||
@@ -25,29 +25,10 @@ import com.bancika.gerberwriter.Point;
|
||||
import com.bancika.gerberwriter.path.MoveTo;
|
||||
import com.bancika.gerberwriter.path.Path;
|
||||
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.Project;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
|
||||
public class GerberPathRenderer {
|
||||
|
||||
public static void drawComponent(Project currentProject, GerberG2DWrapper g2d, IDIYComponent<?> c,
|
||||
boolean outlineMode) {
|
||||
IGerberComponent gerberComponent = (IGerberComponent) c;
|
||||
g2d.startedDrawingComponent();
|
||||
if (gerberComponent instanceof IGerberComponentCustom) {
|
||||
IGerberComponentCustom gerberComponentCustom = (IGerberComponentCustom) gerberComponent;
|
||||
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());
|
||||
c.draw(g2d, ComponentState.NORMAL, outlineMode, currentProject, g2d);
|
||||
g2d.stopGerberOutput();
|
||||
}
|
||||
}
|
||||
|
||||
public static void outputPathArea(PathIterator pathIterator, DataLayer dataLayer, double d,
|
||||
boolean isNegative, String function) {
|
||||
List<PathWithNegative> outputPath =
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
|
||||
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.gerber;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.EnumSet;
|
||||
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
import org.diylc.core.Project;
|
||||
import org.diylc.testcomponents.MockDIYComponent;
|
||||
import org.junit.Test;
|
||||
|
||||
public class GerberExporterTest {
|
||||
|
||||
@Test
|
||||
public void testSimpleComponentGoesToSilkscreenTopByDefault() {
|
||||
GerberG2DWrapper g2d = draw(new SimpleGerberComponent(null));
|
||||
|
||||
assertEquals(EnumSet.of(GerberLayer.SilkscreenTop), EnumSet.copyOf(g2d.getLayerMap().keySet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleComponentGoesToItsOwnLayer() {
|
||||
GerberG2DWrapper g2d = draw(new SimpleGerberComponent(GerberLayer.CopperBot));
|
||||
|
||||
assertEquals(EnumSet.of(GerberLayer.CopperBot), 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();
|
||||
GerberG2DWrapper g2d =
|
||||
new GerberG2DWrapper(graphics, "test", new Rectangle2D.Double(0, 0, 100, 100));
|
||||
GerberExporter.drawComponent(new Project(), g2d, component, false);
|
||||
graphics.dispose();
|
||||
return g2d;
|
||||
}
|
||||
|
||||
public static class SimpleGerberComponent extends MockDIYComponent
|
||||
implements IGerberComponentSimple {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// null uses the interface default
|
||||
private GerberLayer layer;
|
||||
|
||||
public SimpleGerberComponent(GerberLayer layer) {
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GerberLayer getGerberLayer() {
|
||||
return layer == null ? IGerberComponentSimple.super.getGerberLayer() : layer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode,
|
||||
Project project, IDrawingObserver drawingObserver) {
|
||||
g2d.setColor(Color.black);
|
||||
g2d.fill(new Rectangle2D.Double(10, 10, 20, 20));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
|
||||
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.gerber;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class GerberLayerTest {
|
||||
|
||||
@Test
|
||||
public void testFlipSwapsSides() {
|
||||
assertEquals(GerberLayer.CopperBot, GerberLayer.CopperTop.flip());
|
||||
assertEquals(GerberLayer.CopperTop, GerberLayer.CopperBot.flip());
|
||||
assertEquals(GerberLayer.SilkscreenBot, GerberLayer.SilkscreenTop.flip());
|
||||
assertEquals(GerberLayer.SilkscreenTop, GerberLayer.SilkscreenBot.flip());
|
||||
assertEquals(GerberLayer.SolderMaskBot, GerberLayer.SolderMaskTop.flip());
|
||||
assertEquals(GerberLayer.SolderMaskTop, GerberLayer.SolderMaskBot.flip());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlipKeepsLayersThroughTheBoard() {
|
||||
assertEquals(GerberLayer.Outline, GerberLayer.Outline.flip());
|
||||
assertEquals(GerberLayer.DrillPlated, GerberLayer.DrillPlated.flip());
|
||||
assertEquals(GerberLayer.DrillNonPlated, GerberLayer.DrillNonPlated.flip());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlipTwiceReturnsLayer() {
|
||||
for (GerberLayer layer : GerberLayer.values()) {
|
||||
assertEquals(layer, layer.flip().flip());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
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.
|
||||
|
||||
Status: not started.
|
||||
Status: the Gerber dispatch is a single method and `GerberLayer.flip()` exists, with tests; the
|
||||
next steps below remain.
|
||||
|
||||
## 1. Current state
|
||||
|
||||
@@ -19,9 +20,10 @@ Status: not started.
|
||||
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.
|
||||
Their pads are separate `SolderPad` components, which already have a layer.
|
||||
- Two copies of the same dispatch decide the layer for a simple component:
|
||||
`GerberExporter.drawComponent` and `GerberPathRenderer.drawComponent`. Both call
|
||||
`g2d.startGerberOutput(getGerberLayer(), ...)`.
|
||||
- One method decides the layer for a simple component: `GerberExporter.drawComponent`, which
|
||||
calls `g2d.startGerberOutput(getGerberLayer(), ...)`. It is package-private so that
|
||||
`GerberExporterTest` can call it. `GerberLayer.flip()` maps a layer to the other side of 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.
|
||||
|
||||
@@ -44,12 +46,8 @@ Status: not started.
|
||||
- **`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.
|
||||
- **`GerberLayer.flip()`**: maps `CopperTop`/`CopperBot`, `SilkscreenTop`/`SilkscreenBot` and
|
||||
`SolderMaskTop`/`SolderMaskBot` to each other. `Outline` and the drill layers map to themselves.
|
||||
- **Gerber dispatch**: in the simple-component branch, use
|
||||
`getGerberLayer().flip()` when the component is an `IBoardSideComponent` on `_1`. Merge
|
||||
`GerberExporter.drawComponent` and `GerberPathRenderer.drawComponent` into one method first, so
|
||||
the rule is written once.
|
||||
- **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`,
|
||||
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.
|
||||
@@ -135,8 +133,8 @@ order.
|
||||
|
||||
## 5. Verification
|
||||
|
||||
- Unit tests in `diylc-core` for `GerberLayer.flip()` and for the merged dispatch method, using a
|
||||
stub `IBoardSideComponent` (top, bottom and `null`).
|
||||
- `GerberLayerTest` covers `flip()`. `GerberExporterTest` covers the dispatch for simple components;
|
||||
extend it with a stub `IBoardSideComponent` (top, bottom and `null`).
|
||||
- Load an old `.diy` with PCB parts and confirm every component reports `_2` (Top) and that the Gerber
|
||||
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
|
||||
@@ -150,16 +148,15 @@ order.
|
||||
|
||||
## 6. Next steps
|
||||
|
||||
1. Merge the two `drawComponent` copies in `diylc-core` and add `GerberLayer.flip()` with tests.
|
||||
2. Add `IBoardSideComponent` and swap the Gerber layer in the dispatch.
|
||||
3. Add the field to the library components, putting it on shared bases where they exist. Settle
|
||||
1. Add `IBoardSideComponent` and swap the Gerber layer in the dispatch.
|
||||
2. Add the field to the library components, putting it on shared bases where they exist. Settle
|
||||
the silkscreen-text question while doing so.
|
||||
4. Wrap bottom-side painting in `startDrawingSide(getBoardSide())`.
|
||||
5. Add the Flip to Other Side action.
|
||||
6. Split the silkscreen print-out by side.
|
||||
7. Extract the mirror geometry helper, with tests, and use it in `drawMirroredComponent`.
|
||||
8. Mirror stage 1: selecting through the mirror. It does not depend on steps 1-6 and can ship
|
||||
3. Wrap bottom-side painting in `startDrawingSide(getBoardSide())`.
|
||||
4. Add the Flip to Other Side action.
|
||||
5. Split the silkscreen print-out by side.
|
||||
6. 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
|
||||
early.
|
||||
9. Mirror stage 2: dragging through the mirror.
|
||||
10. Mirror stage 3: placing in the mirror, after Flip to Other Side exists.
|
||||
11. Run the regression suite.
|
||||
8. Mirror stage 2: dragging through the mirror.
|
||||
9. Mirror stage 3: placing in the mirror, after Flip to Other Side exists.
|
||||
10. Run the regression suite.
|
||||
|
||||
Reference in New Issue
Block a user