diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberExporter.java b/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberExporter.java index e485b273c..40bb8e614 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberExporter.java +++ b/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberExporter.java @@ -160,15 +160,10 @@ public class GerberExporter { gerberComponentCustom.draw(g2d, ComponentState.NORMAL, outlineMode, currentProject, g2d, g2d); } else if (gerberComponent instanceof IGerberComponentSimple) { IGerberComponentSimple gerberComponentSimple = (IGerberComponentSimple) gerberComponent; - GerberLayer layer = gerberComponentSimple.getGerberLayer(); - // components emit their top-side layer; a part mounted underneath goes to the bottom one - boolean bottomSide = IBoardSideComponent.sideOf(c) == PCBLayer._1; - if (bottomSide) { - layer = layer.flip(); - } - g2d.startGerberOutput(layer, gerberComponentSimple.getGerberFunction(), - gerberComponentSimple.isGerberNegative()); - g2d.setMirrorText(bottomSide); + g2d.startGerberOutput(gerberComponentSimple.getGerberLayer(), + gerberComponentSimple.getGerberFunction(), gerberComponentSimple.isGerberNegative()); + // text on the bottom silkscreen is read from below, so it is written mirrored + g2d.setMirrorText(IBoardSideComponent.sideOf(c) == PCBLayer._1); c.draw(g2d, ComponentState.NORMAL, outlineMode, currentProject, g2d); g2d.setMirrorText(false); g2d.stopGerberOutput(); diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberLayer.java b/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberLayer.java index e4aca640b..5dabcdf23 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberLayer.java +++ b/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberLayer.java @@ -55,29 +55,6 @@ public enum GerberLayer { 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(".") ? "" : ".") + boardName + "-" + getSuffix() + "." + getExtension(); diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/gerber/IGerberComponentSimple.java b/diylc/diylc-core/src/main/java/org/diylc/core/gerber/IGerberComponentSimple.java index 0f5921160..9489f9db1 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/core/gerber/IGerberComponentSimple.java +++ b/diylc/diylc-core/src/main/java/org/diylc/core/gerber/IGerberComponentSimple.java @@ -1,9 +1,18 @@ package org.diylc.core.gerber; +import org.diylc.common.PCBLayer; +import org.diylc.core.IBoardSideComponent; +import org.diylc.core.IDIYComponent; + public interface IGerberComponentSimple extends IGerberComponent { - + + /** + * @return the silkscreen on the side of the board the component is mounted on + */ default GerberLayer getGerberLayer() { - return GerberLayer.SilkscreenTop; + return this instanceof IDIYComponent component + && IBoardSideComponent.sideOf(component) == PCBLayer._1 + ? GerberLayer.SilkscreenBot : GerberLayer.SilkscreenTop; } default String getGerberFunction() { diff --git a/diylc/diylc-core/src/test/java/org/diylc/core/gerber/GerberLayerTest.java b/diylc/diylc-core/src/test/java/org/diylc/core/gerber/GerberLayerTest.java deleted file mode 100644 index a3b5da1b5..000000000 --- a/diylc/diylc-core/src/test/java/org/diylc/core/gerber/GerberLayerTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - - 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.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()); - } - } -} diff --git a/diylc/docs/plans/bottom-side-components.md b/diylc/docs/plans/bottom-side-components.md index ae636d8eb..9ef7b21f6 100644 --- a/diylc/docs/plans/bottom-side-components.md +++ b/diylc/docs/plans/bottom-side-components.md @@ -29,9 +29,10 @@ projects; the open questions below remain. use it. Copper that sits on a layer - `CopperTrace`, `CurvedTrace`, `GroundFill`, `SolderPad` and `PCBText` - implements `IPCBLayerComponent` (`getLayer`/`setLayer`), which `PCBLayerFilter`, the trace-mask print-out and `MirrorPlacement` use instead of looking the methods up by name. -- One method decides the layer for a simple component: `GerberExporter.drawComponent`. It is - package-private so that `GerberExporterTest` can call it. It uses `getGerberLayer()`, flipped - with `GerberLayer.flip()` when the component is an `IBoardSideComponent` on `_1` (Bottom). +- A simple component's Gerber layer follows its side: the default + `IGerberComponentSimple.getGerberLayer()` returns `SilkscreenBot` for an `IBoardSideComponent` on + `_1` (Bottom) and `SilkscreenTop` otherwise. `GerberExporter.drawComponent`, package-private so + that `GerberExporterTest` can call it, mirrors the text of bottom-side parts. - **`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. @@ -47,7 +48,7 @@ projects; the open questions below remain. | The side is the physical fact. Z-order stays paint order and is not tied to it | Boards are drawn opaque, so painting bottom parts beneath the board would hide them. Bottom-layer traces already follow this rule. | | The canvas shows the side visually and does not rely on z-order for it | That is what removes the ambiguity of a "bottom" part drawn above the board. | | The side is a plain property, and the app never mirrors a part for the user | Seen from above, a part under the board is a mirror image, so a part with an asymmetric footprint moved to the bottom also needs Mirror Horizontally. Doing that for the user was tried as a Flip to Other Side action and dropped as menu clutter; the user sets the side and mirrors the part where needed. | -| The Gerber layer is swapped centrally, not inside each component | Components keep emitting their `*Top` layer. One place maps it to the other side. | +| The Gerber layer follows the side in the `IGerberComponentSimple` default, not in each component or in the exporter | Side and layer are decided in one place on the part, with no adjustment afterwards. None of the sided parts overrides `getGerberLayer()`. | | Default `_2` (Top), with `null` meaning Top | Fields added to existing classes deserialize as `null` in old files. | | 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. | @@ -154,7 +155,7 @@ order. ## 5. Verification -- `GerberLayerTest` covers `flip()`. `GerberExporterTest` covers the dispatch for simple components, +- `GerberExporterTest` covers the layer and text direction of simple components, including a stub `IBoardSideComponent` on top, bottom and `null`. - `MirrorPlacementTest` covers what placing through the mirror changes, and `PresenterTests.testPlaceThroughUndersideMirror` and `testPlaceUnsidedPartThroughUndersideMirror`