Create fill-clearance.md

This commit is contained in:
Bane Stojkovic
2026-10-14 21:31:08 +02:00
parent a8d8a9b409
commit 4916d02af6
+161
View File
@@ -0,0 +1,161 @@
# Ground Fill Clearance
Let a copper ground fill keep an isolation gap around traces and pads on its own layer, so a
board can be poured with ground without every trace shorting to it. The fill sets the gap; a
trace or pad can override it, including overriding it to zero so that it connects to the fill.
## 1. Behaviour
**Fill clearance.** `GroundFill` gains a **Clearance** property (`Size`). The fill draws its
polygon minus, for every clearance source on the same layer, that source's copper grown by the
effective clearance. A clearance of zero (or `null`, which is what old files deserialize to)
cuts nothing, so existing projects render and net exactly as before.
**Override.** Every clearance source gains a **Fill Clearance** property (`Size`):
| Value | Effect |
| --- | --- |
| blank (`null`, the default) | Use the clearance of whichever fill it overlaps |
| `0` | No gap: the source overlaps the fill and is electrically connected to it |
| any positive size | Use this gap instead of the fill's |
A ground trace is therefore "a trace with Fill Clearance 0". The override is a property of the
source, not of a source/fill pair; a trace overlapping two fills with different clearances
takes each fill's own value unless overridden.
**Layers.** A fill is cut only by sources whose copper is on the fill's layer
(`ILayeredComponent.getLayerId()` / `PCBLayer.getId()`). A bottom-layer trace passing under a
top-layer fill does not cut it. Sources that put copper on several layers report their shapes
per layer (see §2).
**Z-order does not matter.** Every source on the layer cuts the fill regardless of whether it
is drawn before or after it. "Send to back" never changes the copper.
**What does not cut a fill.** Other fills (two overlapping fills on one layer still merge, as
today), boards (`VeroBoard`, `PerfBoard`, `TriPadBoard`), `TraceCut`, and anything not on a
copper layer.
**Islands.** Clearance can split a fill into disjoint pieces. Each piece is a separate copper
area for continuity and netlist purposes. Islands are kept, not removed; an orphaned island is
visible and the user can fix it by moving the fill's points.
## 2. Architecture
```
diylc-core IFillClearanceSource (interface: copper shapes per layer + override)
^ implemented by
diylc-library CopperTrace, CurvedTrace, SolderPad, PCBText (phase 1)
footprint components with copper pads (phase 2)
^ queried by
GroundFill.draw() -> FillClearanceCalculator -> cut Area, split into islands
|
+- netlist: one positive continuity area per island
+- canvas: fill() per island
+- Gerber: same fill() calls through GerberG2DWrapper, dark polarity
```
The fill does all the cutting. Continuity analysis (`DrawingManager.getContinuityAreas`),
the drawing pipeline and `GerberExporter` stay unchanged, because the fill already *draws* its
final copper. Negative continuity areas are deliberately not used: a negative area subtracts
from every earlier positive area on the layer, so a trace's halo would sever traces and pads
it legitimately connects to.
### diylc-core
**`org.diylc.core.IFillClearanceSource`**, next to `ILayeredComponent`, Javadoc with
`@author`, in the shape of `IGerberComponentSimple`:
```java
public interface IFillClearanceSource {
/** Copper this component places on the given PCB layer, in canvas coordinates; empty if none. */
List<Shape> getCopperShapes(int layerId);
/** Gap to keep from a ground fill; null inherits the fill's clearance, zero connects. */
Size getFillClearance();
}
```
Per-layer shapes rather than a single shape plus `getLayerId()`, so that phase-2 footprint
components with pads on both sides fit the same interface.
### diylc-library
**`GroundFill`**
- New field `private Size clearance;` with getter `@EditableProperty(name = "Clearance")`,
null-tolerant (treat `null` as zero). New fills default to `CLEARANCE`, a `public static`
non-final constant (see §4, open question 1).
- `draw()` uses `project` to collect the `IFillClearanceSource` components, gets the cut area
from `FillClearanceCalculator`, splits it with `AreaUtils.tryAreaBreakout` and `fill()`s
each island inside the existing `startTrackingContinuityArea(true)` block. Each `fill()` with
no continuity marker becomes its own continuity area in `G2DWrapper`.
- **Memoization.** The cut is expensive (`Area` operations over every source) and `draw()` runs
on every repaint. Keep a `transient` cached `Area` plus a signature of its inputs: the fill's
points, layer, clearance, and for each source on the layer its shapes' bounds and effective
clearance. Recompute only when the signature changes. `enableCache` stays `false`, because the
fill now depends on other components and `DrawingCache` keys only on the component itself.
**`FillClearanceCalculator`** (new, `org.diylc.components.connectivity`): pure geometry,
no Swing, unit-testable.
- Input: fill polygon, fill layer id, fill clearance, the sources.
- For each source: skip it if its effective clearance is zero or it has no shapes on the layer.
Otherwise grow each shape by the clearance and union the result into a cut area.
- Return the fill area minus the cut area.
- Growing a shape: `shape ∪ roundStroke(2 × clearance).createStrokedShape(shape)`.
`CopperTrace` and `CurvedTrace` can skip the union. Instead they report a centerline, and the
calculator strokes it at `thickness + 2 × clearance`, which is exact and cheaper. Decide
during implementation whether that special case is worth a second interface method.
Otherwise use the generic path.
**Phase-1 sources.** Each one gets a nullable `Size fillClearance` field, a getter annotated
`@EditableProperty(name = "Fill Clearance")` that may return `null`, and `getCopperShapes`.
`draw()` and `getCopperShapes` must build their copper from the **same** private helper, so
the gap always matches the drawn copper.
| Component | Copper shape |
| --- | --- |
| `CopperTrace` | Stroked line, current thickness and `LineEndingStyle` cap |
| `CurvedTrace` | Stroked curve, same stroke as `draw()` |
| `SolderPad` | Pad outline per `Type` (round, oval H/V, square); the drill hole is ignored |
| `PCBText` | Glyph outlines (confirm it is copper and not silkscreen before including) |
`PCBTerminalBlock` tracks no continuity and is not layered today, so it waits for phase 2.
## 3. File format
- New fields only, nothing renamed. `GroundFill.clearance == null` behaves as zero, and
`fillClearance == null` means "inherit". Both are safe for every existing file.
- With zero clearance an old file must produce identical canvas, netlist and Gerber output.
Run the regression suite after implementation to confirm this. Expect zero diffs.
## 4. Open questions
1. **Default clearance on new fills.** Zero means a new fill behaves like today and clearance
is opt-in. A non-zero default (e.g. 0.5 mm) is more useful, but a fill dropped onto an
existing layout silently isolates every trace not set to `0`. Leaning towards zero.
2. **Phase-2 scope.** About 40 library components output copper pads to Gerber (`DIL_IC`,
`SIL_IC`, `Resistor`, `AbstractRadialComponent`, `AbstractTransistorPackage`,
`AbstractPotentiometer`, jacks, switches, …). Until they implement the interface, their
pads short to a fill they overlap. Implementing it in the abstract bases covers most of
them.
3. **Thermal relief** (spoked joins between a zero-clearance pad and the fill) is out of
scope. If added later, it belongs in `FillClearanceCalculator` as a pattern of spokes
added back after the cut.
## 5. Next steps
1. Add `IFillClearanceSource` to `diylc-core`.
2. Add `FillClearanceCalculator` with unit tests in `diylc-library/src/test`. Cover: layer
filtering, inheritance vs. override vs. zero, a trace that crosses the fill edge, a cut that
splits the fill into islands, and an empty source list returning the original polygon.
3. `GroundFill`: the `Clearance` property, the cut and islands in `draw()`, and memoization.
4. Implement the interface on `CopperTrace`, `CurvedTrace` and `SolderPad`, refactoring their
`draw()` to share the shape helper. Then `PCBText` if question in §2 resolves to copper.
5. Check by hand: highlighting continuity on a fill with gaps, a netlist with a zero-clearance
ground trace, dragging a trace across a large fill (check that repaints stay responsive),
and a two-layer board with fills on both sides.
6. Check that Gerber export of a fill with holes produces correct regions with clear polarity
inside them (`GerberPathRenderer.assignNegativeFlags`). Open the output in a Gerber viewer.
7. Build and test: `mvn -q -pl diylc-library -am test`, `mvn -q -pl diylc-swing -am compile`.
Then run the regression suite.
8. Phase 2: footprint pads through the abstract component bases.