Split trace layer

This commit is contained in:
Bane Stojkovic
2026-11-12 13:42:04 +01:00
parent 4aab04f751
commit 6e3f72dfb6
26 changed files with 684 additions and 83 deletions
+30 -27
View File
@@ -5,7 +5,8 @@ that copper on one side of the board can be hidden, locked or selected without t
Two-sided boards, the Underside Display editing and the Via made one trace layer too coarse: a user
routing the bottom in the Underside Display wants to lock the top, and the other way round.
Status: planned, nothing built yet.
Status: done. Core, library and swing tests pass; the regression suite has not been run, and no
change is expected there (section 5).
## 1. Background
@@ -37,6 +38,8 @@ Status: planned, nothing built yet.
| The Visible shortcuts are renumbered to follow the menu: Ctrl+1 Chassis, Ctrl+2 Board, Ctrl+3 Bottom Traces, Ctrl+4 Top Traces, Ctrl+5 Component, Ctrl+6 Wiring, Ctrl+7 Text | Shortcuts that follow the menu order are easier to learn than a new one added out of order. Only the shortcuts move; the saved layer ids do not, so files are unaffected. |
| New ids: Bottom Traces `31`, Top Traces `32` (`TRACE * 10 + PCBLayer.getId()`) | They cannot collide with a z-order. Reusing `3` for one side would make an older DIYLC treat a file with only one side hidden as all traces hidden. |
| `3` stays as the legacy id meaning both trace layers, and the saved sets keep the invariant "`3` is present exactly when `31` and `32` both are" | A file saved with both sides hidden or locked still hides or locks all traces in an older DIYLC, and an older DIYLC that unhides or unlocks Trace is understood by the new one (section 4). |
| The ids and the set rules live in `org.diylc.core.LayerIds`; which layer a component is on lives in `org.diylc.presenter.ComponentLayers` | `Project` applies the rules on load and must not depend on the presenter package; layer membership needs the z-order from `ComponentProcessor`. |
| Two set rules, not one: `migrateLoaded` after a load, `syncLegacy` after a change | "Both sides set, no `3`" means opposite things in the two cases: written by an older version that cleared Trace, or just set by the user. |
| Copper with a side is on the layer of that side; the deprecated inner layers count as Bottom | `IPCBLayerComponent.getBoardSide()` already maps them that way. |
| A Trace-band component that reports layer id `0` is on both trace layers: a PTH `SolderPad`, and `Via`, `Eyelet` and `Turret`, which implement `ILayeredComponent` to say so. A composite whose z-order falls in the Trace band is on both as well | Layer id `0` already means "every layer" to continuity, and the three go through the board. A composite may hold copper of both sides. Core names no concrete component. |
| Every other Trace-band component is on the layer of `IBoardSideComponent.sideOf`, so a component without a side, such as `Jumper`, is on Top Traces | `sideOf` already treats a component without a side as top, the same rule the rendering uses. A jumper sits on top of the board: locking Bottom Traces to work on the top must leave it movable. |
@@ -48,22 +51,23 @@ Status: planned, nothing built yet.
### diylc-core
- `org.diylc.presenter.ComponentLayers` (new, not in `Presenter`, which stays lean):
- constants `BOTTOM_TRACES = 31`, `TOP_TRACES = 32`, and `LEGACY_TRACES = IDIYComponent.TRACE`;
- `Set<Integer> getLayers(IDIYComponent<?>)`: the rounded z-order for everything outside the
Trace band; inside it, both trace ids for layer id `0` and for a composite, otherwise the id
of `IBoardSideComponent.sideOf`;
- `isHidden(Project, component)` (all its layers hidden), `isLocked(Project, component)` (any of
its layers locked) and `isOnLayer(component, layerId)` for Select All;
- `normalize(Set<Integer>)`, which applies the invariant of section 4 and is used both after
loading and after every change.
- `org.diylc.core.LayerIds` (new): `LEGACY_TRACES = IDIYComponent.TRACE`, `BOTTOM_TRACES = 31`,
`TOP_TRACES = 32`, `migrateLoaded(Set<Integer>)` for a set read from a file and
`syncLegacy(Set<Integer>)` for a set just changed (section 4).
- `org.diylc.presenter.ComponentLayers` (new, not in `Presenter`, which stays lean): the layer of a
component is the rounded z-order outside the Trace band; inside it, both trace layers for layer
id `0` and for a composite, otherwise the layer of `IBoardSideComponent.sideOf`. It answers
`isHidden(hiddenLayers, component)` (all its layers hidden), `isLocked(lockedLayers, component)`
(any of its layers locked) and `isOnLayer(component, layerId)` for Select All, without
allocating, since drawing asks for every component on every repaint.
- `Presenter.isComponentVisible`, `isComponentLayerLocked`, `selectAll(int)` and
`GroupAndLockIndex.isLocked` call the helper. Internal types without a descriptor stay visible
and unlocked, as today.
- `Presenter.setLayerLocked` and `setLayerVisibility` call `normalize` on the changed set.
- `Project.readResolve()` (new) normalizes `lockedLayers` and `hiddenLayers` whenever XStream
loads a project, so the migration does not depend on which code path opened the file. XStream
calls `readResolve` on any class that declares it.
- `Presenter.setLayerLocked` and `setLayerVisibility` call `syncLegacy` on the changed set; given
the legacy id `3`, they change both trace layers.
- `Project.readResolve()` (new) runs `migrateLoaded` on `lockedLayers` and `hiddenLayers`
whenever XStream loads a project, so the migration does not depend on which code path opened
the file. XStream calls `readResolve` on any class that declares it.
- `Via`, `Eyelet` and `Turret` (diylc-library) implement `ILayeredComponent` with `getLayerId()`
returning `0`. Continuity does not change: a component that is not layered already counts as
layer `0`.
@@ -87,8 +91,8 @@ Status: planned, nothing built yet.
Nothing about components changes, and no field is added, renamed or retyped. The only saved data
involved are the two `Set<Integer>` fields of `Project`, whose type stays the same.
**Invariant.** In a saved set, `3` is present exactly when `31` and `32` both are. `normalize`
enforces it:
**Invariant.** In a saved set, `3` is present exactly when `31` and `32` both are. `migrateLoaded`
applies the first three rows after a load, `syncLegacy` the last one after a change:
| Set as loaded or changed | Normalized | Why |
| --- | --- | --- |
@@ -117,22 +121,21 @@ clipboard carries components, not layer sets.
## 5. Verification
- `ComponentLayersTest`: a top and a bottom trace, an inner-layer trace (Bottom), PTH and NPTH
pads, a `Via`, an `Eyelet`, a `Jumper` (Top Traces) and a Trace-band composite each land on the
expected layers; a
Component-band part keeps its z-order layer.
- `ComponentLayersTest` (library): a top and a bottom trace, an inner-layer trace (Bottom), PTH and
NPTH pads, a `Via`, an `Eyelet`, a `Jumper` (Top Traces) and a Trace-band composite each land on
the expected layers; a Component-band part keeps its z-order layer.
- Hidden and locked rules: a via is hidden only with both sides hidden and locked with either side
locked; a jumper stays unlocked when only Bottom Traces is locked.
- Continuity: the netlist of a project with vias, eyelets and turrets is unchanged.
- `normalize`: each row of the table in section 4, and that a loaded project goes through it
(`readResolve`), using an XML project with `<int>3</int>` in `hiddenLayers` and `lockedLayers`.
- Presenter: Select All per trace layer; locking Bottom Traces and then moving a selected top trace
to the bottom leaves it locked.
- `LayerIdsTest` (core): each row of the table in section 4, and a project saved and loaded through
XStream, so that `readResolve` runs.
- `TraceLayerTests` (swing): Select All per trace layer; locking Bottom Traces locks the via but
not the top trace or the jumper; both sides hidden keeps `3`; a top trace moved to the locked
bottom through its Layer property is locked.
- Regression suite: no image or netlist change expected, since no regression project hides or
locks the Trace layer.
## 6. Next steps
1. Build the core helper with its tests, the `ILayeredComponent` declarations on `Via`, `Eyelet`
and `Turret`, then the menu and the language keys.
2. Changelog entry in the unreleased version, including the renumbered shortcuts.
1. Check the menu, the shortcuts and the locking in the running app, and open a project saved with
the old Trace layer hidden or locked.