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'

This commit is contained in:
Bane Stojkovic
2026-11-11 21:15:26 +01:00
parent 7ae773ab77
commit aef300d902
13 changed files with 87 additions and 51 deletions
+26 -26
View File
@@ -1011,12 +1011,17 @@
"namePrefix" : "Trace", "namePrefix" : "Trace",
"description" : "Straight copper trace", "description" : "Straight copper trace",
"creationMethod" : "POINT_BY_POINT", "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" : [ { "properties" : [ {
"name" : "Alpha", "name" : "Alpha",
"type" : "Percentage", "type" : "Percentage",
"format" : "number without the % sign, e.g. 100", "format" : "number without the % sign, e.g. 100",
"defaultValue" : "100" "defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, { }, {
"name" : "Color", "name" : "Color",
"type" : "Color", "type" : "Color",
@@ -1032,11 +1037,6 @@
"type" : "LineEndingStyle", "type" : "LineEndingStyle",
"possibleValues" : [ "Round", "Flat" ], "possibleValues" : [ "Round", "Flat" ],
"defaultValue" : "Round" "defaultValue" : "Round"
}, {
"name" : "Layer",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, { }, {
"name" : "Name", "name" : "Name",
"type" : "String" "type" : "String"
@@ -1064,6 +1064,11 @@
"type" : "Percentage", "type" : "Percentage",
"format" : "number without the % sign, e.g. 100", "format" : "number without the % sign, e.g. 100",
"defaultValue" : "100" "defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, { }, {
"name" : "Color", "name" : "Color",
"type" : "Color", "type" : "Color",
@@ -1074,11 +1079,6 @@
"type" : "LineEndingStyle", "type" : "LineEndingStyle",
"possibleValues" : [ "Round", "Flat" ], "possibleValues" : [ "Round", "Flat" ],
"defaultValue" : "Round" "defaultValue" : "Round"
}, {
"name" : "Layer",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, { }, {
"name" : "Name", "name" : "Name",
"type" : "String" "type" : "String"
@@ -1269,6 +1269,11 @@
"type" : "Percentage", "type" : "Percentage",
"format" : "number without the % sign, e.g. 100", "format" : "number without the % sign, e.g. 100",
"defaultValue" : "100" "defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, { }, {
"name" : "Color", "name" : "Color",
"type" : "Color", "type" : "Color",
@@ -1279,11 +1284,6 @@
"type" : "PointCount", "type" : "PointCount",
"possibleValues" : [ "_3", "_4", "_5", "_6", "_7", "_8" ], "possibleValues" : [ "_3", "_4", "_5", "_6", "_7", "_8" ],
"defaultValue" : "_4" "defaultValue" : "_4"
}, {
"name" : "Layer",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, { }, {
"name" : "Name", "name" : "Name",
"type" : "String" "type" : "String"
@@ -1697,6 +1697,11 @@
"type" : "Percentage", "type" : "Percentage",
"format" : "number without the % sign, e.g. 100", "format" : "number without the % sign, e.g. 100",
"defaultValue" : "100" "defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, { }, {
"name" : "Color", "name" : "Color",
"type" : "Color", "type" : "Color",
@@ -1713,11 +1718,6 @@
"type" : "HoleType", "type" : "HoleType",
"possibleValues" : [ "NONE", "PTH", "NTPH" ], "possibleValues" : [ "NONE", "PTH", "NTPH" ],
"defaultValue" : "NTPH" "defaultValue" : "NTPH"
}, {
"name" : "Layer",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, { }, {
"name" : "Node Name", "name" : "Node Name",
"type" : "String" "type" : "String"
@@ -12013,6 +12013,11 @@
"description" : "Mirrored text for PCB artwork", "description" : "Mirrored text for PCB artwork",
"creationMethod" : "SINGLE_CLICK", "creationMethod" : "SINGLE_CLICK",
"properties" : [ { "properties" : [ {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, {
"name" : "Color", "name" : "Color",
"type" : "Color", "type" : "Color",
"format" : "hex", "format" : "hex",
@@ -12037,11 +12042,6 @@
"type" : "HorizontalAlignment", "type" : "HorizontalAlignment",
"possibleValues" : [ "LEFT", "CENTER", "RIGHT" ], "possibleValues" : [ "LEFT", "CENTER", "RIGHT" ],
"defaultValue" : "CENTER" "defaultValue" : "CENTER"
}, {
"name" : "Layer",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, { }, {
"name" : "Orientation", "name" : "Orientation",
"type" : "Orientation", "type" : "Orientation",
@@ -24,11 +24,11 @@ package org.diylc.core;
import org.diylc.common.PCBLayer; 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 * 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 * 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 * side the part is mounted on. Copper implements it through {@link IPCBLayerComponent}, whose layer
* fills) do not implement it. * is its side and also drives its continuity.
* *
* @author Branislav Stojkovic * @author Branislav Stojkovic
*/ */
@@ -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. * 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 * The layer is the copper's board side, shown to the user as 'Board Side', but unlike the side of a
* which other copper it connects to. * mounted part it also decides which other copper the copper connects to.
* *
* @author Branislav Stojkovic * @author Branislav Stojkovic
*/ */
public interface IPCBLayerComponent { public interface IPCBLayerComponent extends IBoardSideComponent {
PCBLayer getLayer(); PCBLayer getLayer();
void setLayer(PCBLayer layer); 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);
}
} }
@@ -26,12 +26,11 @@ import java.util.Collection;
import org.diylc.common.PCBLayer; import org.diylc.common.PCBLayer;
import org.diylc.core.IBoardSideComponent; import org.diylc.core.IBoardSideComponent;
import org.diylc.core.IDIYComponent; 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 * Puts components placed through an Underside Display mirror on the bottom of the board. Parts and
* that is mounted on a side goes to the bottom, copper with a {@link PCBLayer} goes to the bottom * copper that have a board side go to the bottom, and anything else is left as it is. Nothing is
* layer, and anything else is left as it is. Nothing is mirrored. * mirrored.
*/ */
public class MirrorPlacement { public class MirrorPlacement {
@@ -42,8 +41,6 @@ public class MirrorPlacement {
for (IDIYComponent<?> component : components) { for (IDIYComponent<?> component : components) {
if (component instanceof IBoardSideComponent sided) { if (component instanceof IBoardSideComponent sided) {
sided.setBoardSide(PCBLayer._1); sided.setBoardSide(PCBLayer._1);
} else if (component instanceof IPCBLayerComponent copper) {
copper.setLayer(PCBLayer._1);
} }
} }
} }
@@ -657,6 +657,10 @@
<changeType>IMPROVEMENT</changeType> <changeType>IMPROVEMENT</changeType>
<description>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</description> <description>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</description>
</org.diylc.appframework.update.Change> </org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>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</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change> <org.diylc.appframework.update.Change>
<changeType>BUG_FIX</changeType> <changeType>BUG_FIX</changeType>
<description>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</description> <description>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</description>
@@ -52,7 +52,7 @@ import org.diylc.core.measures.Size;
import org.diylc.core.measures.SizeUnit; 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(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") @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", @ComponentDescriptor(name = "Copper Trace", author = "Branislav Stojkovic", category = "Connectivity",
creationMethod = CreationMethod.POINT_BY_POINT, instanceNamePrefix = "Trace", creationMethod = CreationMethod.POINT_BY_POINT, instanceNamePrefix = "Trace",
@@ -117,7 +117,7 @@ public class CopperTrace extends AbstractLeadedComponent<Void>
this.thickness = thickness; this.thickness = thickness;
} }
@EditableProperty @EditableProperty(name = "Board Side")
@Override @Override
public PCBLayer getLayer() { public PCBLayer getLayer() {
if (layer == null) { if (layer == null) {
@@ -105,7 +105,7 @@ public class CurvedTrace extends AbstractCurvedComponent<Void>
this.size = size; this.size = size;
} }
@EditableProperty @EditableProperty(name = "Board Side")
@Override @Override
public PCBLayer getLayer() { public PCBLayer getLayer() {
if (layer == null) { if (layer == null) {
@@ -152,7 +152,7 @@ public class GroundFill extends AbstractTransparentComponent<Void>
this.pointCount = pointCount; this.pointCount = pointCount;
} }
@EditableProperty @EditableProperty(name = "Board Side")
@Override @Override
public PCBLayer getLayer() { public PCBLayer getLayer() {
if (layer == null) { if (layer == null) {
@@ -190,7 +190,7 @@ public class SolderPad extends AbstractTransparentComponent<Void>
this.holeSize = holeSize; this.holeSize = holeSize;
} }
@EditableProperty @EditableProperty(name = "Board Side")
@Override @Override
public PCBLayer getLayer() { public PCBLayer getLayer() {
if (layer == null) { if (layer == null) {
@@ -325,7 +325,7 @@ public class PCBText extends AbstractComponent<Void>
this.horizontalAlignment = alignment; this.horizontalAlignment = alignment;
} }
@EditableProperty @EditableProperty(name = "Board Side")
@Override @Override
public PCBLayer getLayer() { public PCBLayer getLayer() {
if (layer == null) { if (layer == null) {
@@ -29,9 +29,11 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.diylc.common.PCBLayer; import org.diylc.common.PCBLayer;
import org.diylc.components.connectivity.CopperTrace;
import org.diylc.components.misc.Label; import org.diylc.components.misc.Label;
import org.diylc.core.IBoardSideComponent; import org.diylc.core.IBoardSideComponent;
import org.diylc.core.IDIYComponent; import org.diylc.core.IDIYComponent;
import org.diylc.core.IPCBLayerComponent;
import org.diylc.core.gerber.GerberLayer; import org.diylc.core.gerber.GerberLayer;
import org.diylc.core.gerber.IGerberComponentSimple; import org.diylc.core.gerber.IGerberComponentSimple;
import org.diylc.utils.ReflectionUtils; 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<IDIYComponent<?>> createBoardSideComponents() throws Exception { private static List<IDIYComponent<?>> createBoardSideComponents() throws Exception {
List<IDIYComponent<?>> components = new ArrayList<IDIYComponent<?>>(); List<IDIYComponent<?>> components = new ArrayList<IDIYComponent<?>>();
for (Class<?> type : ReflectionUtils.getIndexedClasses(IDIYComponent.class)) { for (Class<?> type : ReflectionUtils.getIndexedClasses(IDIYComponent.class)) {
if (IBoardSideComponent.class.isAssignableFrom(type) if (IBoardSideComponent.class.isAssignableFrom(type)
&& !IPCBLayerComponent.class.isAssignableFrom(type)
&& !Modifier.isAbstract(type.getModifiers())) { && !Modifier.isAbstract(type.getModifiers())) {
components.add((IDIYComponent<?>) type.getDeclaredConstructor().newInstance()); components.add((IDIYComponent<?>) type.getDeclaredConstructor().newInstance());
} }
@@ -98,7 +98,7 @@ public class ComponentCatalogGeneratorTest {
assertTrue("a switch carries its switching table", variants.get(0).containsKey("switching")); assertTrue("a switch carries its switching table", variants.get(0).containsKey("switching"));
// the deprecated inner layers are kept only so that old files load // the deprecated inner layers are kept only so that old files load
Map<String, Object> layer = findProperty(find(components, "connectivity.CopperTrace"), "Layer"); Map<String, Object> layer = findProperty(find(components, "connectivity.CopperTrace"), "Board Side");
assertEquals(List.of("_1", "_2"), layer.get("possibleValues")); assertEquals(List.of("_1", "_2"), layer.get("possibleValues"));
} }
+10 -6
View File
@@ -27,8 +27,12 @@ projects; the open questions below remain.
- `IBoardSideComponent.sideOf(component)` is the one place that answers which side a component is - `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 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 use it. Copper that sits on a layer - `CopperTrace`, `CurvedTrace`, `GroundFill`, `SolderPad` and
`PCBText` - implements `IPCBLayerComponent` (`getLayer`/`setLayer`), which `PCBLayerFilter`, the `PCBText` - implements `IPCBLayerComponent` (`getLayer`/`setLayer`), which `PCBLayerFilter` and
trace-mask print-out and `MirrorPlacement` use instead of looking the methods up by name. 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 - A simple component's Gerber layer follows its side: the default
`IGerberComponentSimple.getGerberLayer()` returns `SilkscreenBot` for an `IBoardSideComponent` on `IGerberComponentSimple.getGerberLayer()` returns `SilkscreenBot` for an `IBoardSideComponent` on
`_1` (Bottom) and `SilkscreenTop` otherwise. `GerberExporter.drawComponent`, package-private so `_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 - `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 that every part writing `SilkscreenTop` has a side, so a new part cannot be left out by
accident. accident.
- `CopperTrace`, `CurvedTrace`, `GroundFill`, `SolderPad`, `PCBText` and `DrillHole` do **not** - `CopperTrace`, `CurvedTrace`, `GroundFill`, `SolderPad` and `PCBText` implement it through
implement it. Their `PCBLayer` already says where they are. `IPCBLayerComponent`, and start on the bottom. `DrillHole` goes through the board and has no
side.
### diylc-swing ### diylc-swing
@@ -137,8 +142,7 @@ order.
the part lands on the grid, and `DrawingManager.setSlotMirrorBoard` draws the part being placed 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 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 forgotten when a project is loaded. On placement, `MirrorPlacement.placeOnBottom` sets an
`IBoardSideComponent` to `_1` (Bottom) and an `IPCBLayerComponent` to layer `_1`; nothing is `IBoardSideComponent`, part or copper, to `_1` (Bottom); nothing is mirrored. Anything else, such as
mirrored. Anything else, such as
a jumper, eyelet or wire, is placed at the reflected point unchanged. A part dragged in from 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, 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. which pick the mirror and put the part on the bottom the same way.