Files
diy-layout-creator/diylc/docs/plans/fill-clearance.md
T
2026-10-14 21:31:08 +02:00

162 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.