mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
Create fill-clearance.md
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user