mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-13 10:42:09 +10:00
Split trace layer
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
* 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<Integer> 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<Integer> layers) {
|
||||
if (layers.contains(BOTTOM_TRACES) && layers.contains(TOP_TRACES)) {
|
||||
layers.add(LEGACY_TRACES);
|
||||
} else {
|
||||
layers.remove(LEGACY_TRACES);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Integer>();
|
||||
}
|
||||
LayerIds.migrateLoaded(lockedLayers);
|
||||
LayerIds.migrateLoaded(getHiddenLayers());
|
||||
return this;
|
||||
}
|
||||
|
||||
public VersionNumber getFileVersion() {
|
||||
return fileVersion;
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
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<Integer> 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<Integer> 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -488,16 +488,8 @@ public class Presenter implements IPlugInPort {
|
||||
configManager.writeValue(IPlugInPort.DISABLED_CATEGORIES_KEY, new ArrayList<String>());
|
||||
}
|
||||
|
||||
@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<IDIYComponent<?>> 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<Integer> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -665,6 +665,10 @@
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Gerber 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</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>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</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>
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
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<Integer> migrate(Integer... layers) {
|
||||
Set<Integer> set = set(layers);
|
||||
LayerIds.migrateLoaded(set);
|
||||
return set;
|
||||
}
|
||||
|
||||
private static Set<Integer> sync(Integer... layers) {
|
||||
Set<Integer> set = set(layers);
|
||||
LayerIds.syncLegacy(set);
|
||||
return set;
|
||||
}
|
||||
|
||||
private static Set<Integer> set(Integer... layers) {
|
||||
return new HashSet<Integer>(Arrays.asList(layers));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user