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",
"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",
@@ -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
*/
@@ -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);
}
}
@@ -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);
}
}
}
@@ -657,6 +657,10 @@
<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>
</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>
<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>
@@ -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<Void>
this.thickness = thickness;
}
@EditableProperty
@EditableProperty(name = "Board Side")
@Override
public PCBLayer getLayer() {
if (layer == null) {
@@ -105,7 +105,7 @@ public class CurvedTrace extends AbstractCurvedComponent<Void>
this.size = size;
}
@EditableProperty
@EditableProperty(name = "Board Side")
@Override
public PCBLayer getLayer() {
if (layer == null) {
@@ -152,7 +152,7 @@ public class GroundFill extends AbstractTransparentComponent<Void>
this.pointCount = pointCount;
}
@EditableProperty
@EditableProperty(name = "Board Side")
@Override
public PCBLayer getLayer() {
if (layer == null) {
@@ -190,7 +190,7 @@ public class SolderPad extends AbstractTransparentComponent<Void>
this.holeSize = holeSize;
}
@EditableProperty
@EditableProperty(name = "Board Side")
@Override
public PCBLayer getLayer() {
if (layer == null) {
@@ -325,7 +325,7 @@ public class PCBText extends AbstractComponent<Void>
this.horizontalAlignment = alignment;
}
@EditableProperty
@EditableProperty(name = "Board Side")
@Override
public PCBLayer getLayer() {
if (layer == null) {
@@ -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<IDIYComponent<?>> createBoardSideComponents() throws Exception {
List<IDIYComponent<?>> components = new ArrayList<IDIYComponent<?>>();
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());
}
@@ -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<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"));
}
+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
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.