Skip to content

Commit

Permalink
Rework copperfills and add hex copper fill
Browse files Browse the repository at this point in the history
  • Loading branch information
yaqwsx committed Oct 29, 2023
1 parent c521b10 commit c3a8dd0
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 23 deletions.
11 changes: 10 additions & 1 deletion docs/panelization/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,12 +525,13 @@ via `width` and `height`.

Fill non-board areas of the panel with copper.

**Types**: none, solid, hatched
**Types**: none, solid, hatched, hex

**Common options**:

- `clearance` - optional extra clearance from the board perimeters. Suitable
for, e.g., not filling the tabs with copper.
- `edgeclearance` - specifies clearance between the fill and panel perimeter.
- `layers` - comma-separated list of layer to fill. Default top and bottom. You
can specify a shortcut `all` to fill all layers.

Expand All @@ -546,6 +547,14 @@ Use hatch pattern for the fill.
- `spacing` - the space between the strokes
- `orientation` - the orientation of the strokes

#### Hex

Use hexagon pattern for the fill.

- `diameter` – diameter of the hexagons
- `spacing` – space between the hexagons
- `threshold` – a percentage value that will discard fragments smaller than
given threshold

### Post

Expand Down
1 change: 1 addition & 0 deletions kikit/panel_features/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .baseFeature import PanelFeature
8 changes: 8 additions & 0 deletions kikit/panel_features/baseFeature.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from ..panelize import Panel

class PanelFeature:
"""
Basic interface for various
"""
def apply(self, panel: Panel) -> None:
raise NotImplementedError("Implementation error: PanelFeature doesn't support applying")
163 changes: 163 additions & 0 deletions kikit/panel_features/copperFill.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
from dataclasses import dataclass, field
from ..substrate import linestringToKicad
from ..defs import Layer
from ..common import KiAngle, KiLength, fromDegrees, fromMm
from ..pcbnew_utils import increaseZonePriorities
from pcbnewTransition import pcbnew
from ..panelize import Panel
from .baseFeature import PanelFeature
from typing import Any, List, Tuple
import numpy as np
from shapely.geometry import (
Polygon,
MultiPolygon)

class KiCADCopperFillMixin(PanelFeature):
"""
Build solid infill of non-board areas
"""
def _adjustZoneParameters(self, zone: pcbnew.ZONE):
"""
Allow an inherited class to override KiCAD zone parameters
"""
pass # solid infill does nothing

def apply(self, panel: Any) -> None:
if not panel.boardSubstrate.isSinglePiece():
raise RuntimeError(
"The substrate has to be a single piece to fill unused areas"
)
if not len(self.layers) > 0:
raise RuntimeError("No layers to add copper to")
increaseZonePriorities(panel.board)

zoneArea = panel.boardSubstrate.substrates.buffer(-self.edgeclearance)
zoneArea = zoneArea.difference(MultiPolygon(
[substrate.exterior().buffer(self.clearance) for substrate in panel.substrates]
))

geoms = [zoneArea] if isinstance(zoneArea, Polygon) else zoneArea.geoms

for g in geoms:
zoneContainer = pcbnew.ZONE(panel.board)
self._adjustZoneParameters(zoneContainer)
zoneContainer.Outline().AddOutline(linestringToKicad(g.exterior))
for hole in g.interiors:
zoneContainer.Outline().AddHole(linestringToKicad(hole))
zoneContainer.SetAssignedPriority(0)

for l in self.layers:
if not panel.board.GetEnabledLayers().Contains(l):
continue
zoneContainer = zoneContainer.Duplicate()
zoneContainer.SetLayer(l)
panel.board.Add(zoneContainer)
panel.zonesToRefill.append(zoneContainer)


@dataclass
class SolidCopperFill(KiCADCopperFillMixin):
"""
Build solid infill of non-board areas
"""
clearance: KiLength = field(default_factory=lambda: fromMm(1))
edgeclearance: KiLength = field(default_factory=lambda: fromMm(1))
layers: List[Layer] = field(default_factory=lambda: [Layer.F_Cu, Layer.B_Cu])

def _adjustZoneParameters(self, zone: pcbnew.ZONE) -> None:
pass # There are no adjustments for solid infill


@dataclass
class HatchedCopperFill(KiCADCopperFillMixin):
"""
Build hatched infill of non-board areas
"""
clearance: KiLength = field(default_factory=lambda: fromMm(1))
edgeclearance: KiLength = field(default_factory=lambda: fromMm(1))
layers: List[Layer] = field(default_factory=lambda: [Layer.F_Cu, Layer.B_Cu])
strokeWidth: KiLength = field(default_factory=lambda: fromMm(1))
strokeSpacing: KiLength = field(default_factory=lambda: fromMm(1))
orientation: KiAngle = field(default_factory=lambda: fromDegrees(45))

def _adjustZoneParameters(self, zoneContainer: pcbnew.ZONE) -> None:
zoneContainer.SetFillMode(pcbnew.ZONE_FILL_MODE_HATCH_PATTERN)
zoneContainer.SetHatchOrientation(self.orientation)
zoneContainer.SetHatchGap(self.strokeSpacing)
zoneContainer.SetHatchThickness(self.strokeWidth)

@dataclass
class HexCopperFill(PanelFeature):
"""
Build hex infill of non-board areas
"""
clearance: KiLength = field(default_factory=lambda: fromMm(1))
edgeclearance: KiLength = field(default_factory=lambda: fromMm(1))
layers: List[Layer] = field(default_factory=lambda: [Layer.F_Cu, Layer.B_Cu])
diameter: KiLength = field(default_factory=lambda: fromMm(5))
space: KiLength = field(default_factory=lambda: fromMm(0.5))
threshold: float = field(default_factory=lambda: 0.15)

def _buildHexagonsPolygon(self, area: Tuple[float, float, float, float]) -> MultiPolygon:
horizontalSpacing = self.space + np.sqrt(3) / 2 * self.diameter
verticalSpacing = 3 / 4 * self.diameter + np.sqrt(3) / 2 * self.space

minx, miny, maxx, maxy = area

maxx += horizontalSpacing
maxy += horizontalSpacing

hexagons = []
y = miny
shifted = False
while y <= maxy:
x = minx - (horizontalSpacing / 2 if shifted else 0)
while x <= maxx:
hexagons.append(Polygon([
(x + self.diameter / 2 * np.cos(np.pi / 6 + i / 3 * np.pi),
y + self.diameter / 2 * np.sin(np.pi / 6 + i / 3 * np.pi)) for i in range(6)
]))
x += horizontalSpacing
y += verticalSpacing
shifted = not shifted

return MultiPolygon(hexagons)

def apply(self, panel: Panel) -> None:
if not panel.boardSubstrate.isSinglePiece():
raise RuntimeError(
"The substrate has to be a single piece to fill unused areas"
)
if not len(self.layers) > 0:
raise RuntimeError("No layers to add copper to")

increaseZonePriorities(panel.board)

zoneArea = panel.boardSubstrate.substrates.buffer(-self.edgeclearance)
zoneArea = zoneArea.intersection(panel.boardSubstrate.substrates)
zoneArea = zoneArea.difference(MultiPolygon(
[substrate.exterior().buffer(self.clearance) for substrate in panel.substrates]
))

hexagons = self._buildHexagonsPolygon(zoneArea.bounds)
hexagons = hexagons.intersection(zoneArea)

baseHexArea = 3 * np.sqrt(3) * (self.diameter / 2) ** 2 / 2

geoms = [hexagons] if isinstance(hexagons, Polygon) else hexagons.geoms
for g in geoms:
if g.area < self.threshold * baseHexArea:
continue
zoneContainer = pcbnew.ZONE(panel.board)
zoneContainer.Outline().AddOutline(linestringToKicad(g.exterior))
for hole in g.interiors:
zoneContainer.Outline().AddHole(linestringToKicad(hole))
zoneContainer.SetAssignedPriority(0)

for l in self.layers:
if not panel.board.GetEnabledLayers().Contains(l):
continue
zoneContainer = zoneContainer.Duplicate()
zoneContainer.SetLayer(l)
panel.board.Add(zoneContainer)
panel.zonesToRefill.append(zoneContainer)
13 changes: 9 additions & 4 deletions kikit/panelize.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from kikit.annotations import AnnotationReader, TabAnnotation
from kikit.drc import DrcExclusion, readBoardDrcExclusions, serializeExclusion
from kikit.units import mm, deg
from kikit.pcbnew_utils import increaseZonePriorities

class PanelError(RuntimeError):
pass
Expand Down Expand Up @@ -338,10 +339,6 @@ def isBoardEdge(edge):
"""
return isinstance(edge, pcbnew.PCB_SHAPE) and edge.GetLayerName() == "Edge.Cuts"

def increaseZonePriorities(board, amount=1):
for zone in board.Zones():
zone.SetAssignedPriority(zone.GetAssignedPriority() + amount)

def tabSpacing(width, count):
"""
Given a width of board edge and tab count, return an iterable with tab
Expand Down Expand Up @@ -1832,6 +1829,8 @@ def copperFillNonBoardAreas(self, clearance: KiLength=fromMm(1),
strokeWidth: KiLength=fromMm(1), strokeSpacing: KiLength=fromMm(1),
orientation: KiAngle=fromDegrees(45)) -> None:
"""
This function is deprecated, please, use panel features instead.
Fill given layers with copper on unused areas of the panel (frame, rails
and tabs). You can specify the clearance, if it should be hatched
(default is solid) or shape the strokes of hatched pattern.
Expand Down Expand Up @@ -2221,6 +2220,12 @@ def addPanelDimensions(self, layer: Layer, offset: KiLength) -> None:
vDim.SetExtensionOffset(-self.filletSize)
self.board.Add(vDim)

def apply(self, feature: Any) -> None:
"""
Apply given feature to the panel
"""
feature.apply(self)


def getFootprintByReference(board, reference):
"""
Expand Down
32 changes: 21 additions & 11 deletions kikit/panelize_ui_impl.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from kikit import panelize
from kikit.panel_features.copperFill import HatchedCopperFill, HexCopperFill, SolidCopperFill
from kikit.panelize_ui import Section, PresetError
from kikit.panelize import *
from kikit.defs import Layer
Expand Down Expand Up @@ -579,20 +580,29 @@ def buildCopperfill(preset, panel):
if type == "none":
return
if type == "solid":
panel.copperFillNonBoardAreas(
panel.apply(SolidCopperFill(
clearance=preset["clearance"],
edgeclearance=preset["edgeclearance"],
layers=preset["layers"],
hatched=False
)
))
if type == "hatched":
panel.copperFillNonBoardAreas(
clearance=preset["clearance"],
layers=preset["layers"],
hatched=True,
strokeWidth=preset["width"],
strokeSpacing=preset["spacing"],
orientation=preset["orientation"]
)
panel.apply(HatchedCopperFill(
clearance=preset["clearance"],
edgeclearance=preset["edgeclearance"],
layers=preset["layers"],
strokeWidth=preset["width"],
strokeSpacing=preset["spacing"],
orientation=preset["orientation"]
))
if type == "hex":
panel.apply(HexCopperFill(
clearance=preset["clearance"],
edgeclearance=preset["edgeclearance"],
layers=preset["layers"],
diameter=preset["diameter"],
space=preset["spacing"],
threshold=preset["threshold"]
))
except KeyError as e:
raise PresetError(f"Missing parameter '{e}' in section 'postprocessing'")

Expand Down
32 changes: 26 additions & 6 deletions kikit/panelize_ui_sections.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ def __init__(self, *args, **kwargs):
def validate(self, x):
return readLength(x)

class SPercent(SectionBase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def validate(self, x):
x = x.strip()
if not x.endswith("%"):
raise PresetError("Percentage error has to end with %")
return readPercents(x)

class SLengthOrPercent(SectionBase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -618,14 +628,17 @@ def ppText(section):

COPPERFILL_SECTION = {
"type": SChoice(
["none", "solid", "hatched"],
["none", "solid", "hatched", "hex"],
always(),
"Fill non board areas with copper"),
"clearance": SLength(
typeIn(["solid", "hatched"]),
typeIn(["solid", "hatched", "hex"]),
"Clearance between the fill and boards"),
"edgeclearance": SLength(
typeIn(["solid", "hatched", "hex"]),
"Clearance between the fill and boards"),
"layers": SLayerList(
typeIn(["solid", "hatched"]),
typeIn(["solid", "hatched", "hex"]),
"Specify which layer to fill with copper",
{
"all": Layer.allCu()
Expand All @@ -634,11 +647,18 @@ def ppText(section):
typeIn(["hatched"]),
"Width of hatch strokes"),
"spacing": SLength(
typeIn(["hatched"]),
"Spacing of hatch strokes"),
typeIn(["hatched", "hex"]),
"Spacing of hatch strokes or hexagons"),
"orientation": SAngle(
typeIn(["hatched"]),
"Orientation of the strokes"
"Orientation of the strokes"),
"diameter": SLength(
typeIn(["hex"]),
"Diameter of hexagons"
),
"threshold": SPercent(
typeIn(["hex"]),
"Remove fragments smaller than threshold"
)
}

Expand Down
9 changes: 9 additions & 0 deletions kikit/pcbnew_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from pcbnewTransition import pcbnew


def increaseZonePriorities(board: pcbnew.BOARD, amount: int = 1):
"""
Given a board, increase priority of all zones by given amount
"""
for zone in board.Zones():
zone.SetAssignedPriority(zone.GetAssignedPriority() + amount)
5 changes: 4 additions & 1 deletion kikit/resources/panelizePresets/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,13 @@
"copperfill": {
"type": "none",
"clearance": "0.5mm",
"edgeclearance": "0.5mm",
"layers": "F.Cu,B.Cu",
"width": "1mm",
"diameter": "5mm",
"spacing": "1mm",
"orientation": "45deg"
"orientation": "45deg",
"threshold": "15%"
},
"post": {
"type": "auto",
Expand Down

0 comments on commit c3a8dd0

Please sign in to comment.