From aef300d90203c13fa2c49210c27f880d1e938b01 Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Wed, 11 Nov 2026 21:15:26 +0100 Subject: [PATCH] Traces, curved traces, ground fills, solder pads and PCB text call their layer 'Board Side', like every other part, and count as Board Side components in code; the saved field is still 'layer' --- diylc/catalog_full.json | 52 +++++++++---------- .../org/diylc/core/IBoardSideComponent.java | 6 +-- .../org/diylc/core/IPCBLayerComponent.java | 19 +++++-- .../org/diylc/presenter/MirrorPlacement.java | 9 ++-- .../diylc-core/src/main/resources/update.xml | 4 ++ .../components/connectivity/CopperTrace.java | 4 +- .../components/connectivity/CurvedTrace.java | 2 +- .../components/connectivity/GroundFill.java | 2 +- .../components/connectivity/SolderPad.java | 2 +- .../org/diylc/components/misc/PCBText.java | 2 +- .../components/BoardSideComponentsTest.java | 18 +++++++ .../ComponentCatalogGeneratorTest.java | 2 +- diylc/docs/plans/bottom-side-components.md | 16 +++--- 13 files changed, 87 insertions(+), 51 deletions(-) diff --git a/diylc/catalog_full.json b/diylc/catalog_full.json index b55f97ce8..4fd7d6473 100644 --- a/diylc/catalog_full.json +++ b/diylc/catalog_full.json @@ -1011,12 +1011,17 @@ "namePrefix" : "Trace", "description" : "Straight copper trace", "creationMethod" : "POINT_BY_POINT", - "notes" : [ "Use for copper on PCB layouts; for a wire link use Jumper or Hookup Wire", "A single straight segment on the copper side chosen by 'Layer'; chain segments end to end for bends, or use Curved Trace", "The whole length is copper: any terminal or copper on the same layer that it touches or crosses is connected, not just its ends, so traces of different nets must not cross" ], + "notes" : [ "Use for copper on PCB layouts; for a wire link use Jumper or Hookup Wire", "A single straight segment on the copper side chosen by 'Board Side'; chain segments end to end for bends, or use Curved Trace", "The whole length is copper: any terminal or copper on the same layer that it touches or crosses is connected, not just its ends, so traces of different nets must not cross" ], "properties" : [ { "name" : "Alpha", "type" : "Percentage", "format" : "number without the % sign, e.g. 100", "defaultValue" : "100" + }, { + "name" : "Board Side", + "type" : "PCBLayer", + "possibleValues" : [ "_1", "_2" ], + "defaultValue" : "_1" }, { "name" : "Color", "type" : "Color", @@ -1032,11 +1037,6 @@ "type" : "LineEndingStyle", "possibleValues" : [ "Round", "Flat" ], "defaultValue" : "Round" - }, { - "name" : "Layer", - "type" : "PCBLayer", - "possibleValues" : [ "_1", "_2" ], - "defaultValue" : "_1" }, { "name" : "Name", "type" : "String" @@ -1064,6 +1064,11 @@ "type" : "Percentage", "format" : "number without the % sign, e.g. 100", "defaultValue" : "100" + }, { + "name" : "Board Side", + "type" : "PCBLayer", + "possibleValues" : [ "_1", "_2" ], + "defaultValue" : "_1" }, { "name" : "Color", "type" : "Color", @@ -1074,11 +1079,6 @@ "type" : "LineEndingStyle", "possibleValues" : [ "Round", "Flat" ], "defaultValue" : "Round" - }, { - "name" : "Layer", - "type" : "PCBLayer", - "possibleValues" : [ "_1", "_2" ], - "defaultValue" : "_1" }, { "name" : "Name", "type" : "String" @@ -1269,6 +1269,11 @@ "type" : "Percentage", "format" : "number without the % sign, e.g. 100", "defaultValue" : "100" + }, { + "name" : "Board Side", + "type" : "PCBLayer", + "possibleValues" : [ "_1", "_2" ], + "defaultValue" : "_1" }, { "name" : "Color", "type" : "Color", @@ -1279,11 +1284,6 @@ "type" : "PointCount", "possibleValues" : [ "_3", "_4", "_5", "_6", "_7", "_8" ], "defaultValue" : "_4" - }, { - "name" : "Layer", - "type" : "PCBLayer", - "possibleValues" : [ "_1", "_2" ], - "defaultValue" : "_1" }, { "name" : "Name", "type" : "String" @@ -1697,6 +1697,11 @@ "type" : "Percentage", "format" : "number without the % sign, e.g. 100", "defaultValue" : "100" + }, { + "name" : "Board Side", + "type" : "PCBLayer", + "possibleValues" : [ "_1", "_2" ], + "defaultValue" : "_1" }, { "name" : "Color", "type" : "Color", @@ -1713,11 +1718,6 @@ "type" : "HoleType", "possibleValues" : [ "NONE", "PTH", "NTPH" ], "defaultValue" : "NTPH" - }, { - "name" : "Layer", - "type" : "PCBLayer", - "possibleValues" : [ "_1", "_2" ], - "defaultValue" : "_1" }, { "name" : "Node Name", "type" : "String" @@ -12013,6 +12013,11 @@ "description" : "Mirrored text for PCB artwork", "creationMethod" : "SINGLE_CLICK", "properties" : [ { + "name" : "Board Side", + "type" : "PCBLayer", + "possibleValues" : [ "_1", "_2" ], + "defaultValue" : "_1" + }, { "name" : "Color", "type" : "Color", "format" : "hex", @@ -12037,11 +12042,6 @@ "type" : "HorizontalAlignment", "possibleValues" : [ "LEFT", "CENTER", "RIGHT" ], "defaultValue" : "CENTER" - }, { - "name" : "Layer", - "type" : "PCBLayer", - "possibleValues" : [ "_1", "_2" ], - "defaultValue" : "_1" }, { "name" : "Orientation", "type" : "Orientation", diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/IBoardSideComponent.java b/diylc/diylc-core/src/main/java/org/diylc/core/IBoardSideComponent.java index 98410ad12..bb7a529b9 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/core/IBoardSideComponent.java +++ b/diylc/diylc-core/src/main/java/org/diylc/core/IBoardSideComponent.java @@ -24,11 +24,11 @@ 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 + * Interface for components on one side of a board, such as a resistor, an IC or a trace. 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. + * side the part is mounted on. Copper implements it through {@link IPCBLayerComponent}, whose layer + * is its side and also drives its continuity. * * @author Branislav Stojkovic */ diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/IPCBLayerComponent.java b/diylc/diylc-core/src/main/java/org/diylc/core/IPCBLayerComponent.java index f2a33327f..6ab1bf735 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/core/IPCBLayerComponent.java +++ b/diylc/diylc-core/src/main/java/org/diylc/core/IPCBLayerComponent.java @@ -25,14 +25,27 @@ import org.diylc.common.PCBLayer; /** * Interface for copper drawn on a layer of a PCB, such as a trace, pad, ground fill or PCB text. - * Unlike {@link IBoardSideComponent}, the layer is where the copper itself is, and it also decides - * which other copper it connects to. + * The layer is the copper's board side, shown to the user as 'Board Side', but unlike the side of a + * mounted part it also decides which other copper the copper connects to. * * @author Branislav Stojkovic */ -public interface IPCBLayerComponent { +public interface IPCBLayerComponent extends IBoardSideComponent { PCBLayer getLayer(); void setLayer(PCBLayer layer); + + /** + * @return the layer, with the deprecated inner layers counted as the bottom + */ + @Override + default PCBLayer getBoardSide() { + return getLayer() == PCBLayer._2 ? PCBLayer._2 : PCBLayer._1; + } + + @Override + default void setBoardSide(PCBLayer boardSide) { + setLayer(boardSide); + } } diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/MirrorPlacement.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/MirrorPlacement.java index bc8975b04..0b22655a7 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/presenter/MirrorPlacement.java +++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/MirrorPlacement.java @@ -26,12 +26,11 @@ import java.util.Collection; import org.diylc.common.PCBLayer; import org.diylc.core.IBoardSideComponent; import org.diylc.core.IDIYComponent; -import org.diylc.core.IPCBLayerComponent; /** - * Puts components placed through an Underside Display mirror on the bottom of the board. A part - * that is mounted on a side goes to the bottom, copper with a {@link PCBLayer} goes to the bottom - * layer, and anything else is left as it is. Nothing is mirrored. + * Puts components placed through an Underside Display mirror on the bottom of the board. Parts and + * copper that have a board side go to the bottom, and anything else is left as it is. Nothing is + * mirrored. */ public class MirrorPlacement { @@ -42,8 +41,6 @@ public class MirrorPlacement { for (IDIYComponent component : components) { if (component instanceof IBoardSideComponent sided) { sided.setBoardSide(PCBLayer._1); - } else if (component instanceof IPCBLayerComponent copper) { - copper.setLayer(PCBLayer._1); } } } diff --git a/diylc/diylc-core/src/main/resources/update.xml b/diylc/diylc-core/src/main/resources/update.xml index 540d33282..8f48d8684 100644 --- a/diylc/diylc-core/src/main/resources/update.xml +++ b/diylc/diylc-core/src/main/resources/update.xml @@ -657,6 +657,10 @@ IMPROVEMENT Inner layers 1 to 6 are no longer offered for traces, pads and other copper, since boards are two-sided; projects that use them still open unchanged + + IMPROVEMENT + The 'Layer' property of traces, curved traces, ground fills, solder pads and PCB text is now called 'Board Side', the same as on resistors, ICs and other parts + 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/CopperTrace.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java index 5ae46797b..3b85b312a 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java @@ -52,7 +52,7 @@ import org.diylc.core.measures.Size; import org.diylc.core.measures.SizeUnit; @AiNote(value = "Use for copper on PCB layouts; for a wire link use Jumper or Hookup Wire", scope = Scope.SELECTION) -@AiNote("A single straight segment on the copper side chosen by 'Layer'; chain segments end to end for bends, or use Curved Trace") +@AiNote("A single straight segment on the copper side chosen by 'Board Side'; chain segments end to end for bends, or use Curved Trace") @AiNote("The whole length is copper: any terminal or copper on the same layer that it touches or crosses is connected, not just its ends, so traces of different nets must not cross") @ComponentDescriptor(name = "Copper Trace", author = "Branislav Stojkovic", category = "Connectivity", creationMethod = CreationMethod.POINT_BY_POINT, instanceNamePrefix = "Trace", @@ -117,7 +117,7 @@ public class CopperTrace extends AbstractLeadedComponent this.thickness = thickness; } - @EditableProperty + @EditableProperty(name = "Board Side") @Override public PCBLayer getLayer() { if (layer == null) { diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CurvedTrace.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CurvedTrace.java index 11eda202e..84aeab040 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CurvedTrace.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CurvedTrace.java @@ -105,7 +105,7 @@ public class CurvedTrace extends AbstractCurvedComponent this.size = size; } - @EditableProperty + @EditableProperty(name = "Board Side") @Override public PCBLayer getLayer() { if (layer == null) { diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/GroundFill.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/GroundFill.java index edbb56fe7..6a2f11544 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/GroundFill.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/GroundFill.java @@ -152,7 +152,7 @@ public class GroundFill extends AbstractTransparentComponent this.pointCount = pointCount; } - @EditableProperty + @EditableProperty(name = "Board Side") @Override public PCBLayer getLayer() { if (layer == null) { 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 179a8a95a..4532f468b 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 @@ -190,7 +190,7 @@ public class SolderPad extends AbstractTransparentComponent this.holeSize = holeSize; } - @EditableProperty + @EditableProperty(name = "Board Side") @Override public PCBLayer getLayer() { if (layer == null) { diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/misc/PCBText.java b/diylc/diylc-library/src/main/java/org/diylc/components/misc/PCBText.java index b525381e3..b406fbca5 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/misc/PCBText.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/misc/PCBText.java @@ -325,7 +325,7 @@ public class PCBText extends AbstractComponent this.horizontalAlignment = alignment; } - @EditableProperty + @EditableProperty(name = "Board Side") @Override public PCBLayer getLayer() { if (layer == null) { diff --git a/diylc/diylc-library/src/test/java/org/diylc/components/BoardSideComponentsTest.java b/diylc/diylc-library/src/test/java/org/diylc/components/BoardSideComponentsTest.java index dfde8231e..b3cef7834 100644 --- a/diylc/diylc-library/src/test/java/org/diylc/components/BoardSideComponentsTest.java +++ b/diylc/diylc-library/src/test/java/org/diylc/components/BoardSideComponentsTest.java @@ -29,9 +29,11 @@ import java.util.ArrayList; import java.util.List; import org.diylc.common.PCBLayer; +import org.diylc.components.connectivity.CopperTrace; import org.diylc.components.misc.Label; import org.diylc.core.IBoardSideComponent; import org.diylc.core.IDIYComponent; +import org.diylc.core.IPCBLayerComponent; import org.diylc.core.gerber.GerberLayer; import org.diylc.core.gerber.IGerberComponentSimple; import org.diylc.utils.ReflectionUtils; @@ -73,10 +75,26 @@ public class BoardSideComponentsTest { } } + // copper starts on the bottom, where single-sided boards carry it + @Test + public void testCopperBoardSideIsItsLayer() { + CopperTrace trace = new CopperTrace(); + assertEquals(PCBLayer._1, trace.getBoardSide()); + + trace.setBoardSide(PCBLayer._2); + assertEquals(PCBLayer._2, trace.getLayer()); + + // the deprecated inner layers are inside the board, which counts as the bottom + trace.setLayer(PCBLayer._3); + assertEquals(PCBLayer._1, trace.getBoardSide()); + } + + // parts, which start on top; copper starts on the bottom and is covered above private static List> createBoardSideComponents() throws Exception { List> components = new ArrayList>(); for (Class type : ReflectionUtils.getIndexedClasses(IDIYComponent.class)) { if (IBoardSideComponent.class.isAssignableFrom(type) + && !IPCBLayerComponent.class.isAssignableFrom(type) && !Modifier.isAbstract(type.getModifiers())) { components.add((IDIYComponent) type.getDeclaredConstructor().newInstance()); } diff --git a/diylc/diylc-library/src/test/java/org/diylc/plugins/chatbot/service/ComponentCatalogGeneratorTest.java b/diylc/diylc-library/src/test/java/org/diylc/plugins/chatbot/service/ComponentCatalogGeneratorTest.java index 77df436f3..75d5ad9ed 100644 --- a/diylc/diylc-library/src/test/java/org/diylc/plugins/chatbot/service/ComponentCatalogGeneratorTest.java +++ b/diylc/diylc-library/src/test/java/org/diylc/plugins/chatbot/service/ComponentCatalogGeneratorTest.java @@ -98,7 +98,7 @@ public class ComponentCatalogGeneratorTest { assertTrue("a switch carries its switching table", variants.get(0).containsKey("switching")); // the deprecated inner layers are kept only so that old files load - Map layer = findProperty(find(components, "connectivity.CopperTrace"), "Layer"); + Map layer = findProperty(find(components, "connectivity.CopperTrace"), "Board Side"); assertEquals(List.of("_1", "_2"), layer.get("possibleValues")); } diff --git a/diylc/docs/plans/bottom-side-components.md b/diylc/docs/plans/bottom-side-components.md index 9ef7b21f6..9f896cf9b 100644 --- a/diylc/docs/plans/bottom-side-components.md +++ b/diylc/docs/plans/bottom-side-components.md @@ -27,8 +27,12 @@ projects; the open questions below remain. - `IBoardSideComponent.sideOf(component)` is the one place that answers which side a component is on (Top for components without a side); drawing, the Gerber export and the silkscreen filter all 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. + `PCBText` - implements `IPCBLayerComponent` (`getLayer`/`setLayer`), which `PCBLayerFilter` and + the trace-mask print-out use to tell copper from parts. `IPCBLayerComponent` extends + `IBoardSideComponent`, reading and writing the side through the layer, with the deprecated inner + layers counted as the bottom, so copper has one side like any part. The copper's layer property + is shown as **Board Side**, the same as on parts; its field is still `layer`, so files are + unaffected. - 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 @@ -89,8 +93,9 @@ projects; the open questions below remain. - `BoardSideComponentsTest` checks that every such part starts on Top and takes Bottom, and that every part writing `SilkscreenTop` has a side, so a new part cannot be left out by accident. -- `CopperTrace`, `CurvedTrace`, `GroundFill`, `SolderPad`, `PCBText` and `DrillHole` do **not** - implement it. Their `PCBLayer` already says where they are. +- `CopperTrace`, `CurvedTrace`, `GroundFill`, `SolderPad` and `PCBText` implement it through + `IPCBLayerComponent`, and start on the bottom. `DrillHole` goes through the board and has no + side. ### diylc-swing @@ -137,8 +142,7 @@ order. the part lands on the grid, and `DrawingManager.setSlotMirrorBoard` draws the part being placed through that mirror, under the cursor, seen from below like the parts already there. The board is forgotten when a project is loaded. On placement, `MirrorPlacement.placeOnBottom` sets an - `IBoardSideComponent` to `_1` (Bottom) and an `IPCBLayerComponent` to layer `_1`; nothing is - mirrored. Anything else, such as + `IBoardSideComponent`, part or copper, to `_1` (Bottom); nothing is mirrored. Anything else, such as a jumper, eyelet or wire, is placed at the reflected point unchanged. A part dragged in from the component tree or the toolbox goes through `Presenter.dragOver` and `dragEnded` instead, which pick the mirror and put the part on the bottom the same way.