Resistors, capacitors, diodes, ICs, transistors and other PCB parts can be placed on the bottom of the board with the new 'Board Side' property; Gerber export puts their silkscreen on the bottom layer with the text mirrored

This commit is contained in:
Bane Stojkovic
2026-11-11 15:15:19 +01:00
parent 16f54fa479
commit df873df04a
36 changed files with 498 additions and 54 deletions
+32 -31
View File
@@ -3,9 +3,10 @@
Let a component be mounted on the bottom of a PCB, so that its silkscreen goes to the bottom
Gerber layers and the canvas shows it as a part on the far side of the board.
Status: the core Gerber side is done, with tests: `IBoardSideComponent` exists and the export
sends a bottom-side part to the bottom layers, but no component implements it yet. Parts can be
selected, dragged and stretched through the Underside Display mirror. The next steps below remain.
Status: 32 parts have a Board Side property, and Gerber export sends a bottom-side part's
silkscreen to the bottom layer with its text mirrored. Parts can be selected, dragged and stretched
through the Underside Display mirror. The canvas does not show the side yet, and the Flip to Other
Side action does not exist; the next steps below remain.
## 1. Current state
@@ -41,6 +42,9 @@ selected, dragged and stretched through the Underside Display mirror. The next s
| Flipping a part is an action that sets the side, mirrors the part and sends it back | A part seen from above through the board is a mirror image. Changing the side without mirroring gives a footprint that is wrong for fabrication. |
| The Gerber layer is swapped centrally, not inside each component | Components keep emitting their `*Top` layer. One place maps it to the other side. |
| Default `_2` (Top), with `null` meaning Top | Fields added to existing classes deserialize as `null` in old files. |
| The side is both an editable property and the Flip to Other Side action | The action is the normal way to move a part, because it also mirrors it. The property stays for a part that is already drawn mirrored and only needs its side set. |
| Bottom-side text is mirrored centrally in the Gerber output | Parts draw their labels readable from the top, which is backwards on the bottom silkscreen once the board is turned over. While `GerberExporter.drawComponent` draws a bottom-side part, `GerberG2DWrapper.setMirrorText` mirrors each string about the centre of its layout box, so no component changes and the canvas keeps readable labels. |
| A `SolderPad` opens the solder mask on its own side | It used to write `SolderMaskBot` whatever its layer, which was wrong for top-layer pads. It now writes `SolderMaskTop` for a pad on `_2`. |
## 3. Design
@@ -52,18 +56,28 @@ selected, dragged and stretched through the Underside Display mirror. The next s
### diylc-library
- The ~35 classes that use the `SilkscreenTop` default implement `IBoardSideComponent`. Each adds a
nullable `PCBLayer boardSide` field and a getter/setter pair, with
`@EditableProperty(name = "Board Side")` on the getter. The getter returns `_2` (Top) when the
field is `null`. This duplicates the field across classes in the same way the `PCBLayer` field is
duplicated today. Where several of these classes share an abstract base
(`AbstractRadialComponent`, `AbstractFilmCapacitor`, `AbstractTransistorPackage`, ...), put it on
the base.
- **Done.** Every part that uses the `SilkscreenTop` default and whose transformer can mirror it
implements `IBoardSideComponent`: a nullable `PCBLayer boardSide` field and a getter/setter pair
with `@EditableProperty(name = "Board Side")` on the getter, which returns `_2` (Top) when the
field is `null`. The key is in every language file.
- On a shared base, covering every subclass: `AbstractFilmCapacitor`, `AbstractPotentiometer`,
`AbstractRadialComponent`, `PassiveSMDComponent`.
- On the class itself: `Resistor`, `HeatSinkResistor`, `AxialElectrolyticCapacitor`,
`CrystalOscillator`, `ToroidalInductor`, `DiodePlastic`, `DiodeGlass`, `DIL_IC`, `SIL_IC`,
`TransistorTO1`, `TransistorTO92`, `TransistorTO220`, `CliffJack1_4`, `CliffJack1_8`,
`ClosedJack1_4`, `MiniToggleSwitch`, `PCBTerminalBlock`. The transistors are not done on
`AbstractTransistorPackage` because `TransistorTO126` cannot be mirrored, and a property on the
base cannot be hidden from one subclass.
- Left out because they cannot be mirrored: `TubeSocket`, `RotarySwitchOpen`,
`RotarySwitchSealed`, `RotarySelectorSwitch`, `SlideSwitch`, `PlasticDCJack`,
`TactileMicroSwitch`, `MiniRelay`, `AudioTransformer`, `MultiSectionCapacitor`, `MultiwattIC`,
`BridgeRectifier`, `TransistorTO126`. Making one of them mirrorable is what it takes to give it
a side.
- `Label` is left out too: it is text, not a mounted part, and `PCBText` already has a layer.
- `BoardSideComponentsTest` checks that every such part starts on Top, takes Bottom, and can be
mirrored.
- `CopperTrace`, `CurvedTrace`, `GroundFill`, `SolderPad`, `PCBText` and `DrillHole` do **not**
implement it. Their `PCBLayer` already says where they are.
- Before allowing Bottom on a class, check that its transformer can mirror it
(`canMirror`). A class that cannot be mirrored must not offer Bottom, or the footprint would be
wrong.
### diylc-swing
@@ -115,17 +129,6 @@ order.
## 4. Open questions
- **Bottom silkscreen text.** Component labels drawn through `IGerberComponentSimple` have to read
correctly once the board is turned over, which means they must look mirrored from the top. Check
whether mirroring through the transformer does that for every class, or whether labels are drawn
unmirrored regardless. `GerberLayer.mirrored` is currently `false` for every layer, and the
exporter output must stay in top view.
- **SolderPad solder mask.** `SolderPad` always writes its mask opening to `SolderMaskBot`,
whatever its own `PCBLayer`. Confirm whether this is intended for top-layer SMD pads before
`GerberLayer.flip()` is introduced next to it.
- **Plain property or action only.** A plain editable property lets a user change the side without
mirroring. Either keep both (the property for already-mirrored parts) or show the side as
read-only and change it only through the action.
- **Components that belong to no side.** In mirror stage 3 a jumper, eyelet or wire dropped in the
mirror has no side to set. Confirm that placing it at the reflected point unchanged is what users
expect, or whether such parts should be refused in the mirror.
@@ -153,10 +156,8 @@ order.
## 6. Next steps
1. Add the field to the library components, putting it on shared bases where they exist. Settle
the silkscreen-text question while doing so.
2. Wrap bottom-side painting in `startDrawingSide(getBoardSide())`.
3. Add the Flip to Other Side action.
4. Split the silkscreen print-out by side.
5. Mirror stage 3: placing in the mirror, after Flip to Other Side exists.
6. Run the regression suite.
1. Wrap bottom-side painting in `startDrawingSide(getBoardSide())`.
2. Add the Flip to Other Side action.
3. Split the silkscreen print-out by side.
4. Mirror stage 3: placing in the mirror, after Flip to Other Side exists.
5. Run the regression suite.