diff --git a/diylc/diylc-core/src/main/java/org/diylc/common/IPlugInPort.java b/diylc/diylc-core/src/main/java/org/diylc/common/IPlugInPort.java
index a707dc42e..feb042b8e 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/common/IPlugInPort.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/common/IPlugInPort.java
@@ -364,22 +364,24 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
void setNewComponentTypeSlot(ComponentType componentType, Template template, String[] model, boolean forceInstatiate);
/**
- * Locks or unlocks the specified layer. All components within +- 0.5 range will be affected by
- * the change as well.
+ * Locks or unlocks the specified layer: the components whose z-order rounds to it, or the
+ * components on one side of the board for a trace layer.
*
- * @param layerZOrder
+ * @param layerId a z-order such as {@link org.diylc.core.IDIYComponent#BOARD}, or one of the
+ * trace layers in {@link org.diylc.core.LayerIds}
* @param locked
*/
- void setLayerLocked(int layerZOrder, boolean locked);
+ void setLayerLocked(int layerId, boolean locked);
/**
- * Shows or hides the specified layer. All components within +- 0.5 range will be affected by the
- * change as well.
+ * Shows or hides the specified layer: the components whose z-order rounds to it, or the
+ * components on one side of the board for a trace layer.
*
- * @param layerZOrder
+ * @param layerId a z-order such as {@link org.diylc.core.IDIYComponent#BOARD}, or one of the
+ * trace layers in {@link org.diylc.core.LayerIds}
* @param visible
*/
- void setLayerVisibility(int layerZOrder, boolean visible);
+ void setLayerVisibility(int layerId, boolean visible);
/**
* @return selection size expressed in both inches or centimeters, respectively
diff --git a/diylc/diylc-core/src/main/java/org/diylc/common/ISelectionProcessor.java b/diylc/diylc-core/src/main/java/org/diylc/common/ISelectionProcessor.java
index 250a19cb7..20669c8b0 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/common/ISelectionProcessor.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/common/ISelectionProcessor.java
@@ -77,7 +77,8 @@ public interface ISelectionProcessor extends IGroupProcessor {
/**
* Selects all components in the project.
*
- * @param int layer if > 0, designates which layer to select. If <= 0 we should select all
+ * @param int layer if > 0, the id of the layer to select, as for
+ * {@link IPlugInPort#setLayerVisibility(int, boolean)}. If <= 0 we should select all
* regardless of layer
*/
void selectAll(int layer);
diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/LayerIds.java b/diylc/diylc-core/src/main/java/org/diylc/core/LayerIds.java
new file mode 100644
index 000000000..40e9687e4
--- /dev/null
+++ b/diylc/diylc-core/src/main/java/org/diylc/core/LayerIds.java
@@ -0,0 +1,72 @@
+/*
+
+ 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;
+
+import java.util.Set;
+
+/**
+ * Ids of the layers that can be hidden or locked in a project. A layer is a rounded z-order, such
+ * as {@link IDIYComponent#BOARD}, except that the traces are split into a bottom and a top layer.
+ *
+ * The traces used to be one layer, {@link IDIYComponent#TRACE}, and older DIYLC versions still
+ * know only that one. Project files therefore keep {@link #LEGACY_TRACES} in a hidden or locked
+ * set exactly when both trace layers are in it, so that an older version hides or locks all
+ * traces when the new one has both sides hidden or locked.
+ *
+ * @author Branislav Stojkovic
+ */
+public final class LayerIds {
+
+ public static final int LEGACY_TRACES = IDIYComponent.TRACE;
+ public static final int BOTTOM_TRACES = IDIYComponent.TRACE * 10 + 1;
+ public static final int TOP_TRACES = IDIYComponent.TRACE * 10 + 2;
+
+ private LayerIds() {}
+
+ /**
+ * Brings a hidden or locked layer set read from a file up to date. The legacy trace layer, from
+ * an old file or written by an older version, stands for both trace layers. Both trace layers
+ * without it can only mean that an older version showed or unlocked its single trace layer, so
+ * neither side is hidden or locked any more.
+ */
+ public static void migrateLoaded(Set layers) {
+ if (layers.contains(LEGACY_TRACES)) {
+ layers.add(BOTTOM_TRACES);
+ layers.add(TOP_TRACES);
+ } else if (layers.contains(BOTTOM_TRACES) && layers.contains(TOP_TRACES)) {
+ layers.remove(BOTTOM_TRACES);
+ layers.remove(TOP_TRACES);
+ }
+ }
+
+ /**
+ * Keeps the legacy trace layer in a hidden or locked set after it has been changed: present
+ * exactly when both trace layers are.
+ */
+ public static void syncLegacy(Set layers) {
+ if (layers.contains(BOTTOM_TRACES) && layers.contains(TOP_TRACES)) {
+ layers.add(LEGACY_TRACES);
+ } else {
+ layers.remove(LEGACY_TRACES);
+ }
+ }
+}
diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/Project.java b/diylc/diylc-core/src/main/java/org/diylc/core/Project.java
index 90c4da51d..a8a18ed3b 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/core/Project.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/core/Project.java
@@ -196,6 +196,17 @@ public class Project implements Serializable, Cloneable {
return hiddenLayers;
}
+ // called by XStream after loading, whichever way the project is opened; the trace layer was split
+ // in two, and files from older versions name only the single legacy one
+ private Object readResolve() {
+ if (lockedLayers == null) {
+ lockedLayers = new HashSet();
+ }
+ LayerIds.migrateLoaded(lockedLayers);
+ LayerIds.migrateLoaded(getHiddenLayers());
+ return this;
+ }
+
public VersionNumber getFileVersion() {
return fileVersion;
}
diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/ComponentLayers.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/ComponentLayers.java
new file mode 100644
index 000000000..ee36b1c7a
--- /dev/null
+++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/ComponentLayers.java
@@ -0,0 +1,109 @@
+/*
+
+ 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.presenter;
+
+import java.util.Set;
+
+import org.diylc.common.ComponentType;
+import org.diylc.common.PCBLayer;
+import org.diylc.components.composite.CompositeComponent;
+import org.diylc.core.IBoardSideComponent;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.ILayeredComponent;
+import org.diylc.core.LayerIds;
+
+/**
+ * Decides which layer of the Layers menu a component is on, and with that whether it is hidden or
+ * locked. A component is on the layer of its rounded z-order, except in the trace band, which is
+ * split by board side: copper and other components with a side are on the bottom or top trace
+ * layer, a component without a side is on the top one like the rendering assumes, and a component
+ * that goes through the board, reporting layer id 0, is on both.
+ *
+ * @author Branislav Stojkovic
+ */
+public class ComponentLayers {
+
+ // the component is on both trace layers
+ private static final int BOTH_TRACES = -1;
+ // internal components without a descriptor are on no layer
+ private static final int NONE = 0;
+
+ private ComponentLayers() {}
+
+ /**
+ * @return true when every layer the component is on is hidden
+ */
+ public static boolean isHidden(Set hiddenLayers, IDIYComponent> component) {
+ int layer = getLayer(component);
+ if (layer == BOTH_TRACES) {
+ return hiddenLayers.contains(LayerIds.BOTTOM_TRACES)
+ && hiddenLayers.contains(LayerIds.TOP_TRACES);
+ }
+ return layer != NONE && hiddenLayers.contains(layer);
+ }
+
+ /**
+ * @return true when any layer the component is on is locked; moving a component that is on both
+ * trace layers changes the routing of both sides
+ */
+ public static boolean isLocked(Set lockedLayers, IDIYComponent> component) {
+ int layer = getLayer(component);
+ if (layer == BOTH_TRACES) {
+ return lockedLayers.contains(LayerIds.BOTTOM_TRACES)
+ || lockedLayers.contains(LayerIds.TOP_TRACES);
+ }
+ return layer != NONE && lockedLayers.contains(layer);
+ }
+
+ /**
+ * @return true when the component is on the layer, including a component on both trace layers
+ * when the layer is one of them
+ */
+ public static boolean isOnLayer(IDIYComponent> component, int layerId) {
+ int layer = getLayer(component);
+ if (layer == BOTH_TRACES) {
+ return layerId == LayerIds.BOTTOM_TRACES || layerId == LayerIds.TOP_TRACES;
+ }
+ return layer != NONE && layer == layerId;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static int getLayer(IDIYComponent> component) {
+ ComponentProcessor processor = ComponentProcessor.getInstance();
+ ComponentType componentType = processor
+ .extractComponentTypeFrom((Class extends IDIYComponent>>) component.getClass());
+ if (componentType == null) {
+ return NONE;
+ }
+ int layer = (int) Math.round(processor.getZOrder(component));
+ if (layer != IDIYComponent.TRACE) {
+ return layer;
+ }
+ // a building block may hold copper of both sides
+ if (component instanceof CompositeComponent
+ || (component instanceof ILayeredComponent layered && layered.getLayerId() == 0)) {
+ return BOTH_TRACES;
+ }
+ return IBoardSideComponent.sideOf(component) == PCBLayer._1 ? LayerIds.BOTTOM_TRACES
+ : LayerIds.TOP_TRACES;
+ }
+}
diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/GroupAndLockIndex.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/GroupAndLockIndex.java
index 18c0aa783..d11159cf3 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/presenter/GroupAndLockIndex.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/GroupAndLockIndex.java
@@ -94,8 +94,7 @@ public class GroupAndLockIndex {
if (componentType == null) {
return false;
}
- return project.getLockedLayers()
- .contains((int) Math.round(ComponentProcessor.getInstance().getZOrder(component)))
+ return ComponentLayers.isLocked(project.getLockedLayers(), component)
|| project.getLockedComponents().contains(component);
}
diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java
index 1b7ded933..615fe42d3 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java
@@ -488,16 +488,8 @@ public class Presenter implements IPlugInPort {
configManager.writeValue(IPlugInPort.DISABLED_CATEGORIES_KEY, new ArrayList());
}
- @SuppressWarnings({"unchecked"})
private boolean isComponentVisible(IDIYComponent> component) {
- ComponentType componentType =
- ComponentProcessor.getInstance().extractComponentTypeFrom(
- (Class extends IDIYComponent>>) component.getClass());
- // for internal types
- if (componentType == null)
- return true;
- return !currentProject.getHiddenLayers()
- .contains((int) Math.round(ComponentProcessor.getInstance().getZOrder(component)));
+ return !ComponentLayers.isHidden(currentProject.getHiddenLayers(), component);
}
@Override
@@ -1162,7 +1154,7 @@ public class Presenter implements IPlugInPort {
Iterator> i = newSelection.iterator();
while (i.hasNext()) {
IDIYComponent> c = i.next();
- if ((int) ComponentProcessor.getInstance().getZOrder(c) != layer) {
+ if (!ComponentLayers.isOnLayer(c, layer)) {
i.remove();
}
}
@@ -2285,14 +2277,10 @@ public class Presenter implements IPlugInPort {
}
@Override
- public void setLayerLocked(int layerZOrder, boolean locked) {
- LOG.info(String.format("setLayerLocked(%s, %s)", layerZOrder, locked));
+ public void setLayerLocked(int layerId, boolean locked) {
+ LOG.info(String.format("setLayerLocked(%s, %s)", layerId, locked));
Project oldProject = currentProject.clone();
- if (locked) {
- currentProject.getLockedLayers().add(layerZOrder);
- } else {
- currentProject.getLockedLayers().remove(layerZOrder);
- }
+ setLayerInSet(currentProject.getLockedLayers(), layerId, locked);
groupAndLockIndex.invalidate();
updateSelection(EMPTY_SELECTION);
messageDispatcher.dispatchMessage(EventType.LAYER_STATE_CHANGED, currentProject.getLockedLayers());
@@ -2301,14 +2289,10 @@ public class Presenter implements IPlugInPort {
}
@Override
- public void setLayerVisibility(int layerZOrder, boolean visible) {
- LOG.info(String.format("setLayerVisibility(%s, %s)", layerZOrder, visible));
+ public void setLayerVisibility(int layerId, boolean visible) {
+ LOG.info(String.format("setLayerVisibility(%s, %s)", layerId, visible));
Project oldProject = currentProject.clone();
- if (visible) {
- currentProject.getHiddenLayers().remove(layerZOrder);
- } else {
- currentProject.getHiddenLayers().add(layerZOrder);
- }
+ setLayerInSet(currentProject.getHiddenLayers(), layerId, !visible);
updateSelection(EMPTY_SELECTION);
messageDispatcher.dispatchMessage(EventType.LAYER_VISIBILITY_CHANGED, currentProject.getHiddenLayers());
@@ -3225,8 +3209,22 @@ public class Presenter implements IPlugInPort {
}
private boolean isComponentLayerLocked(IDIYComponent> component) {
- return currentProject.getLockedLayers()
- .contains((int) Math.round(ComponentProcessor.getInstance().getZOrder(component)));
+ return ComponentLayers.isLocked(currentProject.getLockedLayers(), component);
+ }
+
+ // the legacy trace layer stands for both trace layers
+ private static void setLayerInSet(Set layers, int layerId, boolean inSet) {
+ int[] ids = layerId == LayerIds.LEGACY_TRACES
+ ? new int[] {LayerIds.BOTTOM_TRACES, LayerIds.TOP_TRACES}
+ : new int[] {layerId};
+ for (int id : ids) {
+ if (inSet) {
+ layers.add(id);
+ } else {
+ layers.remove(id);
+ }
+ }
+ LayerIds.syncLegacy(layers);
}
/**
diff --git a/diylc/diylc-core/src/main/resources/update.xml b/diylc/diylc-core/src/main/resources/update.xml
index 4299c2ee5..8288001ac 100644
--- a/diylc/diylc-core/src/main/resources/update.xml
+++ b/diylc/diylc-core/src/main/resources/update.xml
@@ -665,6 +665,10 @@
IMPROVEMENTGerber export produces files ready for any PCB fab: drill files are written in Excellon format, which every fab accepts, instead of Gerber format; holes are defined by the drill files alone instead of also being cut out of the copper layers; and solder pads are marked as component pads, or SMD pads when they have no hole, instead of as connector pads
+
+ IMPROVEMENT
+ The 'Trace' layer in the Layers menu is split into 'Bottom Traces' and 'Top Traces', so the copper of one side can be hidden, locked or selected on its own; vias, eyelets, turrets and plated pads go through the board and belong to both, and jumpers are on the top. The layer shortcuts follow the menu order now, Ctrl+1 to Ctrl+7. Projects with the old 'Trace' layer hidden or locked open with both sides hidden or locked
+ BUG_FIXRotated 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-core/src/test/java/org/diylc/core/LayerIdsTest.java b/diylc/diylc-core/src/test/java/org/diylc/core/LayerIdsTest.java
new file mode 100644
index 000000000..2a33de17c
--- /dev/null
+++ b/diylc/diylc-core/src/test/java/org/diylc/core/LayerIdsTest.java
@@ -0,0 +1,94 @@
+/*
+
+ 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;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.DomDriver;
+
+import org.diylc.serialization.ProjectFileManager;
+import org.junit.Test;
+
+public class LayerIdsTest {
+
+ private static final int LEGACY = LayerIds.LEGACY_TRACES;
+ private static final int BOTTOM = LayerIds.BOTTOM_TRACES;
+ private static final int TOP = LayerIds.TOP_TRACES;
+
+ @Test
+ public void testLegacyTraceLayerStandsForBothSides() {
+ assertEquals(set(LEGACY, BOTTOM, TOP, IDIYComponent.BOARD),
+ migrate(LEGACY, IDIYComponent.BOARD));
+ }
+
+ @Test
+ public void testBothSidesWithoutLegacyMeansAnOlderVersionClearedTraces() {
+ assertEquals(set(IDIYComponent.BOARD), migrate(BOTTOM, TOP, IDIYComponent.BOARD));
+ }
+
+ @Test
+ public void testOneSideIsKept() {
+ assertEquals(set(TOP), migrate(TOP));
+ }
+
+ @Test
+ public void testLegacyFollowsBothSides() {
+ assertEquals(set(LEGACY, BOTTOM, TOP), sync(BOTTOM, TOP));
+ assertEquals(set(TOP), sync(LEGACY, TOP));
+ assertEquals(set(), sync(LEGACY));
+ }
+
+ @Test
+ public void testLoadedProjectIsMigrated() {
+ Project project = new Project();
+ project.getHiddenLayers().add(LEGACY);
+ project.getLockedLayers().addAll(set(BOTTOM, TOP));
+
+ XStream xStream = new XStream(new DomDriver("UTF-8"));
+ ProjectFileManager.configure(xStream);
+ Project loaded = (Project) xStream.fromXML(xStream.toXML(project));
+
+ assertEquals(set(LEGACY, BOTTOM, TOP), loaded.getHiddenLayers());
+ assertEquals(set(), loaded.getLockedLayers());
+ }
+
+ private static Set migrate(Integer... layers) {
+ Set set = set(layers);
+ LayerIds.migrateLoaded(set);
+ return set;
+ }
+
+ private static Set sync(Integer... layers) {
+ Set set = set(layers);
+ LayerIds.syncLegacy(set);
+ return set;
+ }
+
+ private static Set set(Integer... layers) {
+ return new HashSet(Arrays.asList(layers));
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Eyelet.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Eyelet.java
index 6edb53e88..e66bafbb0 100644
--- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Eyelet.java
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Eyelet.java
@@ -33,6 +33,7 @@ import org.diylc.core.ComponentState;
import org.diylc.core.IConnective;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IDrawingObserver;
+import org.diylc.core.ILayeredComponent;
import org.diylc.core.Project;
import org.diylc.core.VisibilityPolicy;
import org.diylc.core.annotations.AiNote;
@@ -52,7 +53,8 @@ import org.diylc.utils.Constants;
zOrder = IDIYComponent.TRACE + 0.1, bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false,
keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME, transformer = SimpleComponentTransformer.class,
enableCache = true)
-public class Eyelet extends AbstractTransparentComponent implements IConnective {
+public class Eyelet extends AbstractTransparentComponent
+ implements IConnective, ILayeredComponent {
private static final long serialVersionUID = 1L;
@@ -132,6 +134,12 @@ public class Eyelet extends AbstractTransparentComponent implements ICon
return super.getName();
}
+ // goes through the board, so it is on every copper layer
+ @Override
+ public int getLayerId() {
+ return 0;
+ }
+
@Override
public int getControlPointCount() {
return 1;
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Turret.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Turret.java
index 571559dae..0d0b77a11 100644
--- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Turret.java
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Turret.java
@@ -33,6 +33,7 @@ import org.diylc.core.ComponentState;
import org.diylc.core.IConnective;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IDrawingObserver;
+import org.diylc.core.ILayeredComponent;
import org.diylc.core.Project;
import org.diylc.core.VisibilityPolicy;
import org.diylc.core.annotations.AiNote;
@@ -50,7 +51,8 @@ import org.diylc.utils.Constants;
zOrder = IDIYComponent.TRACE + 0.1, bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false,
keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME, transformer = SimpleComponentTransformer.class,
enableCache = true)
-public class Turret extends AbstractTransparentComponent implements IConnective {
+public class Turret extends AbstractTransparentComponent
+ implements IConnective, ILayeredComponent {
private static final long serialVersionUID = 1L;
@@ -127,6 +129,12 @@ public class Turret extends AbstractTransparentComponent implements ICon
return super.getName();
}
+ // goes through the board, so it is on every copper layer
+ @Override
+ public int getLayerId() {
+ return 0;
+ }
+
@Override
public int getControlPointCount() {
return 1;
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Via.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Via.java
index 79c9b15f0..d3b2564c8 100644
--- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Via.java
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Via.java
@@ -36,6 +36,7 @@ import org.diylc.core.ComponentState;
import org.diylc.core.IConnective;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IDrawingObserver;
+import org.diylc.core.ILayeredComponent;
import org.diylc.core.Project;
import org.diylc.core.VisibilityPolicy;
import org.diylc.core.annotations.AiNote;
@@ -62,7 +63,7 @@ import org.diylc.utils.Constants;
bomPolicy = BomPolicy.NEVER_SHOW, keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "PCB",
transformer = SimpleComponentTransformer.class, enableCache = true)
public class Via extends AbstractTransparentComponent
- implements IConnective, IGerberComponentCustom {
+ implements IConnective, ILayeredComponent, IGerberComponentCustom {
private static final long serialVersionUID = 1L;
@@ -175,6 +176,12 @@ public class Via extends AbstractTransparentComponent
this.color = color;
}
+ // goes through the board, so it is on every copper layer
+ @Override
+ public int getLayerId() {
+ return 0;
+ }
+
@Override
public int getControlPointCount() {
return 1;
diff --git a/diylc/diylc-library/src/test/java/org/diylc/presenter/ComponentLayersTest.java b/diylc/diylc-library/src/test/java/org/diylc/presenter/ComponentLayersTest.java
new file mode 100644
index 000000000..9ab6f6047
--- /dev/null
+++ b/diylc/diylc-library/src/test/java/org/diylc/presenter/ComponentLayersTest.java
@@ -0,0 +1,142 @@
+/*
+
+ 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.presenter;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.diylc.common.PCBLayer;
+import org.diylc.components.composite.CompositeComponent;
+import org.diylc.components.connectivity.CopperTrace;
+import org.diylc.components.connectivity.Eyelet;
+import org.diylc.components.connectivity.Jumper;
+import org.diylc.components.connectivity.SolderPad;
+import org.diylc.components.connectivity.Turret;
+import org.diylc.components.connectivity.Via;
+import org.diylc.components.passive.Resistor;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.LayerIds;
+import org.junit.Test;
+
+public class ComponentLayersTest {
+
+ private static final int BOTTOM = LayerIds.BOTTOM_TRACES;
+ private static final int TOP = LayerIds.TOP_TRACES;
+
+ @Test
+ public void testCopperIsOnTheLayerOfItsSide() {
+ assertOnlyOn(trace(PCBLayer._1), BOTTOM);
+ assertOnlyOn(trace(PCBLayer._2), TOP);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void testInnerLayerCopperIsOnTheBottom() {
+ assertOnlyOn(trace(PCBLayer._5), BOTTOM);
+ }
+
+ @Test
+ public void testComponentsThroughTheBoardAreOnBothTraceLayers() {
+ assertOnBoth(pad(SolderPad.HoleType.PTH));
+ assertOnBoth(new Via());
+ assertOnBoth(new Eyelet());
+ assertOnBoth(new Turret());
+ }
+
+ @Test
+ public void testNonPlatedPadIsOnItsSide() {
+ assertOnlyOn(pad(SolderPad.HoleType.NTPH), BOTTOM);
+ }
+
+ @Test
+ public void testJumperIsOnTopTraces() {
+ assertOnlyOn(new Jumper(), TOP);
+ }
+
+ @Test
+ public void testTraceBlockIsOnBothTraceLayers() {
+ CompositeComponent block = new CompositeComponent();
+ block.getChildComponents().addAll(Arrays.asList(trace(PCBLayer._1), trace(PCBLayer._2)));
+ assertOnBoth(block);
+ }
+
+ @Test
+ public void testOtherComponentsKeepTheirZOrderLayer() {
+ Resistor resistor = new Resistor();
+ assertTrue(ComponentLayers.isOnLayer(resistor, IDIYComponent.COMPONENT));
+ assertFalse(ComponentLayers.isOnLayer(resistor, TOP));
+ assertTrue(ComponentLayers.isHidden(set(IDIYComponent.COMPONENT), resistor));
+ assertTrue(ComponentLayers.isLocked(set(IDIYComponent.COMPONENT), resistor));
+ }
+
+ @Test
+ public void testComponentOnBothIsHiddenWithBothAndLockedWithEither() {
+ Via via = new Via();
+ assertFalse(ComponentLayers.isHidden(set(BOTTOM), via));
+ assertTrue(ComponentLayers.isHidden(set(BOTTOM, TOP), via));
+ assertTrue(ComponentLayers.isLocked(set(BOTTOM), via));
+ assertTrue(ComponentLayers.isLocked(set(TOP), via));
+ assertFalse(ComponentLayers.isLocked(set(IDIYComponent.COMPONENT), via));
+ }
+
+ @Test
+ public void testJumperStaysUnlockedWithOnlyBottomTracesLocked() {
+ assertFalse(ComponentLayers.isLocked(set(BOTTOM), new Jumper()));
+ }
+
+ private static void assertOnlyOn(IDIYComponent> component, int layer) {
+ int other = layer == BOTTOM ? TOP : BOTTOM;
+ assertTrue(ComponentLayers.isOnLayer(component, layer));
+ assertFalse(ComponentLayers.isOnLayer(component, other));
+ assertFalse(ComponentLayers.isOnLayer(component, IDIYComponent.TRACE));
+ assertTrue(ComponentLayers.isHidden(set(layer), component));
+ assertFalse(ComponentLayers.isHidden(set(other), component));
+ assertTrue(ComponentLayers.isLocked(set(layer), component));
+ assertFalse(ComponentLayers.isLocked(set(other), component));
+ }
+
+ private static void assertOnBoth(IDIYComponent> component) {
+ assertTrue(ComponentLayers.isOnLayer(component, BOTTOM));
+ assertTrue(ComponentLayers.isOnLayer(component, TOP));
+ }
+
+ private static CopperTrace trace(PCBLayer layer) {
+ CopperTrace trace = new CopperTrace();
+ trace.setLayer(layer);
+ return trace;
+ }
+
+ private static SolderPad pad(SolderPad.HoleType holeType) {
+ SolderPad pad = new SolderPad();
+ pad.setLayer(PCBLayer._1);
+ pad.setHoleType(holeType);
+ return pad;
+ }
+
+ private static Set set(Integer... layers) {
+ return new HashSet(Arrays.asList(layers));
+ }
+}
diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/layers/LayersMenuPlugin.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/layers/LayersMenuPlugin.java
index 2719d82d2..2f16f37a0 100644
--- a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/layers/LayersMenuPlugin.java
+++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/layers/LayersMenuPlugin.java
@@ -37,6 +37,7 @@ import org.diylc.common.IPlugIn;
import org.diylc.common.IPlugInPort;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IView;
+import org.diylc.core.LayerIds;
import org.diylc.swing.ISwingUI;
public class LayersMenuPlugin implements IPlugIn {
@@ -53,14 +54,14 @@ public class LayersMenuPlugin implements IPlugIn {
visibleActionMap = new HashMap();
selectAllActionMap = new HashMap();
for (Layer layer : Layer.values()) {
- final int zOrder = layer.getZOrder();
+ final int layerId = layer.getLayerId();
AbstractAction lockAction = new AbstractAction("Lock") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
- LayersMenuPlugin.this.plugInPort.setLayerLocked(zOrder, (Boolean) getValue(Action.SELECTED_KEY));
+ LayersMenuPlugin.this.plugInPort.setLayerLocked(layerId, (Boolean) getValue(Action.SELECTED_KEY));
// selectAllActionMap.get(zOrder).setEnabled(!(Boolean) getValue(Action.SELECTED_KEY));
}
};
@@ -73,13 +74,15 @@ public class LayersMenuPlugin implements IPlugIn {
@Override
public void actionPerformed(ActionEvent e) {
- LayersMenuPlugin.this.plugInPort.setLayerVisibility(zOrder, getValue(Action.SELECTED_KEY) == null ? true : (Boolean) getValue(Action.SELECTED_KEY));
+ LayersMenuPlugin.this.plugInPort.setLayerVisibility(layerId, getValue(Action.SELECTED_KEY) == null ? true : (Boolean) getValue(Action.SELECTED_KEY));
// selectAllActionMap.get(zOrder).setEnabled(!(Boolean) getValue(Action.SELECTED_KEY));
}
};
visibleAction.putValue(IView.CHECK_BOX_MENU_ITEM, true);
- visibleAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_0 + zOrder,
- Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
+ // the shortcuts follow the menu order, Ctrl+1 for the first layer
+ visibleAction.putValue(AbstractAction.ACCELERATOR_KEY,
+ KeyStroke.getKeyStroke(KeyEvent.VK_1 + layer.ordinal(),
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
visibleActionMap.put(layer, visibleAction);
AbstractAction selectAllAction = new AbstractAction("Select All") {
@@ -88,10 +91,10 @@ public class LayersMenuPlugin implements IPlugIn {
@Override
public void actionPerformed(ActionEvent e) {
- LayersMenuPlugin.this.plugInPort.selectAll(zOrder);
+ LayersMenuPlugin.this.plugInPort.selectAll(layerId);
}
};
- selectAllActionMap.put(zOrder, selectAllAction);
+ selectAllActionMap.put(layerId, selectAllAction);
swingUI.injectSubmenu(layer.title, null, LOCK_LAYERS_TITLE);
swingUI.injectMenuAction(visibleAction, layer.title);
@@ -116,34 +119,38 @@ public class LayersMenuPlugin implements IPlugIn {
if (eventType == EventType.LAYER_STATE_CHANGED) {
Set lockedLayers = (Set) params[0];
for (Layer layer : Layer.values()) {
- lockActionMap.get(layer).putValue(Action.SELECTED_KEY, lockedLayers.contains(layer.getZOrder()));
+ lockActionMap.get(layer).putValue(Action.SELECTED_KEY, lockedLayers.contains(layer.getLayerId()));
}
} else if (eventType == EventType.LAYER_VISIBILITY_CHANGED) {
Set hiddenLayers = (Set) params[0];
for (Layer layer : Layer.values()) {
- visibleActionMap.get(layer).putValue(Action.SELECTED_KEY, !hiddenLayers.contains(layer.getZOrder()));
+ visibleActionMap.get(layer).putValue(Action.SELECTED_KEY, !hiddenLayers.contains(layer.getLayerId()));
}
}
}
+ // in menu order, which also gives the shortcuts
static enum Layer {
- CHASSIS("Chassis", IDIYComponent.CHASSIS), BOARD("Board", IDIYComponent.BOARD), TRACE("Trace", IDIYComponent.TRACE), COMPONENT(
- "Component", IDIYComponent.COMPONENT), WIRING("Wiring", IDIYComponent.WIRING), TEXT("Text", IDIYComponent.TEXT);
+ CHASSIS("Chassis", IDIYComponent.CHASSIS), BOARD("Board", IDIYComponent.BOARD),
+ BOTTOM_TRACES("Bottom Traces", LayerIds.BOTTOM_TRACES),
+ TOP_TRACES("Top Traces", LayerIds.TOP_TRACES),
+ COMPONENT("Component", IDIYComponent.COMPONENT), WIRING("Wiring", IDIYComponent.WIRING),
+ TEXT("Text", IDIYComponent.TEXT);
String title;
- int zOrder;
+ int layerId;
- private Layer(String title, int order) {
+ private Layer(String title, int layerId) {
this.title = title;
- zOrder = order;
+ this.layerId = layerId;
}
public String getTitle() {
return title;
}
- public int getZOrder() {
- return zOrder;
+ public int getLayerId() {
+ return layerId;
}
}
}
diff --git a/diylc/diylc-swing/src/main/resources/lang/Deutsch.txt b/diylc/diylc-swing/src/main/resources/lang/Deutsch.txt
index d977878d8..da66ba834 100644
--- a/diylc/diylc-swing/src/main/resources/lang/Deutsch.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/Deutsch.txt
@@ -734,3 +734,5 @@ Decimal (4.7K, 0.22uF, 6.3V)|Dezimal (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
Fade Copper Under Board|Kupfer unter der Platine abblenden
Board Side|Platinenseite
+Bottom Traces|Leiterbahnen unten
+Top Traces|Leiterbahnen oben
diff --git a/diylc/diylc-swing/src/main/resources/lang/English.txt b/diylc/diylc-swing/src/main/resources/lang/English.txt
index 4b28622a8..7022dee51 100644
--- a/diylc/diylc-swing/src/main/resources/lang/English.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/English.txt
@@ -727,3 +727,5 @@ Decimal (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22, 6V3)
Fade Copper Under Board
Board Side
+Bottom Traces
+Top Traces
diff --git a/diylc/diylc-swing/src/main/resources/lang/Español.txt b/diylc/diylc-swing/src/main/resources/lang/Español.txt
index 965d5f0c8..8e6187194 100644
--- a/diylc/diylc-swing/src/main/resources/lang/Español.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/Español.txt
@@ -730,3 +730,5 @@ Decimal (4.7K, 0.22uF, 6.3V)|Decimal (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
Fade Copper Under Board|Atenuar cobre bajo la placa
Board Side|Lado de la placa
+Bottom Traces|Trazos inferiores
+Top Traces|Trazos superiores
diff --git a/diylc/diylc-swing/src/main/resources/lang/Français.txt b/diylc/diylc-swing/src/main/resources/lang/Français.txt
index 760a357b0..71c2100d6 100644
--- a/diylc/diylc-swing/src/main/resources/lang/Français.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/Français.txt
@@ -729,3 +729,5 @@ Decimal (4.7K, 0.22uF, 6.3V)|Décimale (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
Fade Copper Under Board|Estomper le cuivre sous la carte
Board Side|Côté de la carte
+Bottom Traces|Pistes du dessous
+Top Traces|Pistes du dessus
diff --git a/diylc/diylc-swing/src/main/resources/lang/Italiano.txt b/diylc/diylc-swing/src/main/resources/lang/Italiano.txt
index 563549bc8..3c9d9873f 100644
--- a/diylc/diylc-swing/src/main/resources/lang/Italiano.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/Italiano.txt
@@ -731,3 +731,5 @@ Decimal (4.7K, 0.22uF, 6.3V)|Decimale (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
Fade Copper Under Board|Attenua il rame sotto la scheda
Board Side|Lato della scheda
+Bottom Traces|Tracce inferiori
+Top Traces|Tracce superiori
diff --git a/diylc/diylc-swing/src/main/resources/lang/Nederlands.txt b/diylc/diylc-swing/src/main/resources/lang/Nederlands.txt
index 63ab0e8f9..3b62d33e7 100644
--- a/diylc/diylc-swing/src/main/resources/lang/Nederlands.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/Nederlands.txt
@@ -735,3 +735,5 @@ Decimal (4.7K, 0.22uF, 6.3V)|Decimaal (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
Fade Copper Under Board|Koper onder de print vervagen
Board Side|Printzijde
+Bottom Traces|Sporen onderkant
+Top Traces|Sporen bovenkant
diff --git a/diylc/diylc-swing/src/main/resources/lang/Português.txt b/diylc/diylc-swing/src/main/resources/lang/Português.txt
index b9a6b670f..998ad8d87 100644
--- a/diylc/diylc-swing/src/main/resources/lang/Português.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/Português.txt
@@ -730,3 +730,5 @@ Decimal (4.7K, 0.22uF, 6.3V)|Decimal (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
Fade Copper Under Board|Esmaecer cobre sob a placa
Board Side|Lado da placa
+Bottom Traces|Traçados inferiores
+Top Traces|Traçados superiores
diff --git a/diylc/diylc-swing/src/main/resources/lang/Srpski.txt b/diylc/diylc-swing/src/main/resources/lang/Srpski.txt
index cc9ab8e46..c6e2f2725 100644
--- a/diylc/diylc-swing/src/main/resources/lang/Srpski.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/Srpski.txt
@@ -726,3 +726,5 @@ Decimal (4.7K, 0.22uF, 6.3V)|Decimalni (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
Fade Copper Under Board|Prigušen Bakar Ispod Ploče
Board Side|Strana Ploče
+Bottom Traces|Donji Vodovi
+Top Traces|Gornji Vodovi
diff --git a/diylc/diylc-swing/src/main/resources/lang/हिंदी.txt b/diylc/diylc-swing/src/main/resources/lang/हिंदी.txt
index f0d502b53..d7e296d19 100644
--- a/diylc/diylc-swing/src/main/resources/lang/हिंदी.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/हिंदी.txt
@@ -728,3 +728,5 @@ Decimal (4.7K, 0.22uF, 6.3V)|दशमलव (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
Fade Copper Under Board|बोर्ड के नीचे तांबा धुंधला करें
Board Side|बोर्ड का पक्ष
+Bottom Traces|नीचे के ट्रेस
+Top Traces|ऊपर के ट्रेस
diff --git a/diylc/diylc-swing/src/main/resources/lang/简体中文.txt b/diylc/diylc-swing/src/main/resources/lang/简体中文.txt
index 8c6dabd5e..2f7f31f58 100644
--- a/diylc/diylc-swing/src/main/resources/lang/简体中文.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/简体中文.txt
@@ -728,3 +728,5 @@ Decimal (4.7K, 0.22uF, 6.3V)|小数 (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
Fade Copper Under Board|淡化板下铜箔
Board Side|板面
+Bottom Traces|底层走线
+Top Traces|顶层走线
diff --git a/diylc/diylc-swing/src/test/java/org/diylc/TraceLayerTests.java b/diylc/diylc-swing/src/test/java/org/diylc/TraceLayerTests.java
new file mode 100644
index 000000000..3ebd30ee9
--- /dev/null
+++ b/diylc/diylc-swing/src/test/java/org/diylc/TraceLayerTests.java
@@ -0,0 +1,116 @@
+/*
+
+ 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+import org.diylc.common.PCBLayer;
+import org.diylc.common.PropertyWrapper;
+import org.diylc.components.connectivity.CopperTrace;
+import org.diylc.components.connectivity.Jumper;
+import org.diylc.components.connectivity.Via;
+import org.diylc.core.LayerIds;
+import org.diylc.core.Project;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TraceLayerTests extends TestBase {
+
+ private CopperTrace bottomTrace;
+ private CopperTrace topTrace;
+ private Via via;
+ private Jumper jumper;
+
+ @Before
+ public void setUp() {
+ bottomTrace = trace(PCBLayer._1);
+ topTrace = trace(PCBLayer._2);
+ via = new Via();
+ jumper = new Jumper();
+ presenter.getCurrentProject().getComponents()
+ .addAll(Arrays.asList(bottomTrace, topTrace, via, jumper));
+ }
+
+ @Test
+ public void testSelectAllOfATraceLayer() {
+ presenter.selectAll(LayerIds.BOTTOM_TRACES);
+ assertEquals(new HashSet