1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
|
import enum
import math
import re
from .sexp import *
from .base_types import *
def unfuck_layers(layers):
if layers and layers[0] == 'F&B.Cu':
return ['F.Cu', 'B.Cu', *layers[1:]]
else:
return layers
def fuck_layers(layers):
if layers and 'F.Cu' in layers and 'B.Cu' in layers and not any(re.match(r'^In[0-9]+\.Cu$', l) for l in layers):
return ['F&B.Cu', *(l for l in layers if l not in ('F.Cu', 'B.Cu'))]
else:
return layers
def layer_mask(layers):
if isinstance(layers, int):
return layers
if isinstance(layers, str):
layers = [l.strip() for l in layers.split(',')]
mask = 0
for layer in layers:
match layer:
case '*.Cu':
return 0xffffffff
case 'F.Cu':
mask |= 1<<0
case 'B.Cu':
mask |= 1<<31
case _:
if (m := re.match(f'In([0-9]+)\.Cu', layer)):
mask |= 1<<int(m.group(1))
return mask
def center_arc_to_kicad_mid(center, start, end):
# Convert normal p1/p2/center notation to the insanity that is kicad's midpoint notation
cx, cy = center.x, center.y
x1, y1 = start.x - cx, start.y - cy
x2, y2 = end.x - cx, end.y - cy
# Get a vector pointing from the center to the "mid" point.
dx, dy = x1 - x2, y1 - y2 # Get a vector pointing from "end" to "start"
dx, dy = -dy, dx # rotate by 90 degrees counter-clockwise
# normalize vector, and multiply by radius to get final point
r = math.hypot(x1, y1)
l = math.hypot(dx, dy)
mx = cx + dx / l * r
my = cy + dy / l * r
return XYCoord(mx, my)
@sexp_type('hatch')
class Hatch:
style: AtomChoice(Atom.none, Atom.edge, Atom.full) = Atom.edge
pitch: float = 0.5
@sexp_type('connect_pads')
class PadConnection:
type: AtomChoice(Atom.thru_hole_only, Atom.full, Atom.no) = None
clearance: Named(float) = 0
@sexp_type('keepout')
class ZoneKeepout:
tracks_allowed: Named(YesNoAtom(yes=Atom.allowed, no=Atom.not_allowed), name='tracks') = True
vias_allowed: Named(YesNoAtom(yes=Atom.allowed, no=Atom.not_allowed), name='vias') = True
pads_allowed: Named(YesNoAtom(yes=Atom.allowed, no=Atom.not_allowed), name='pads') = True
copperpour_allowed: Named(YesNoAtom(yes=Atom.allowed, no=Atom.not_allowed), name='copperpour') = True
footprints_allowed: Named(YesNoAtom(yes=Atom.allowed, no=Atom.not_allowed), name='footprints') = True
@sexp_type('smoothing')
class ZoneSmoothing:
style: AtomChoice(Atom.chamfer, Atom.fillet) = Atom.chamfer
radius: Named(float) = None
@sexp_type('fill')
class ZoneFill:
yes: Flag() = False
mode: Named(Flag(atom=Atom.hatch)) = False
thermal_gap: Named(float) = 0.508
thermal_bridge_width: Named(float) = 0.508
smoothing: ZoneSmoothing = None
island_removal_mode: Named(int) = None
island_area_min: Named(float) = None
hatch_thickness: Named(float) = None
hatch_gap: Named(float) = None
hatch_orientation: Named(int) = None
hatch_smoothing_level: Named(int) = None
hatch_smoothing_value: Named(float) = None
hatch_border_algorithm: Named(AtomChoice(Atom.hatch_thickness, Atom.min_thickness)) = None
hatch_min_hole_area: Named(float) = None
@sexp_type('filled_polygon')
class FillPolygon:
layer: Named(str) = ""
island: Wrap(Flag()) = False
pts: PointList = field(default_factory=PointList)
@sexp_type('fill_segments')
class FillSegment:
layer: Named(str) = ""
pts: PointList = field(default_factory=PointList)
@sexp_type('polygon')
class ZonePolygon:
pts: PointList = field(default_factory=PointList)
@sexp_type('zone')
class Zone:
net: Named(int) = 0
net_name: Named(str) = ""
layer: Named(str) = None
layers: Named(Array(str)) = None
tstamp: Timestamp = None
name: Named(str) = None
hatch: Hatch = None
priority: OmitDefault(Named(int)) = 0
connect_pads: PadConnection = field(default_factory=PadConnection)
min_thickness: Named(float) = 0.254
filled_areas_thickness: Named(YesNoAtom()) = True
keepout: ZoneKeepout = None
fill: ZoneFill = field(default_factory=ZoneFill)
polygon: ZonePolygon = field(default_factory=ZonePolygon)
fill_polygons: List(FillPolygon) = field(default_factory=list)
fill_segments: List(FillSegment) = field(default_factory=list)
def __after_parse__(self, parent=None):
self.layers = unfuck_layers(self.layers)
def __before_sexp__(self):
self.layers = fuck_layers(self.layers)
def unfill(self):
self.fill.yes = False
self.fill_polygons = []
self.fill_segments = []
@sexp_type('polygon')
class RenderCachePolygon:
pts: PointList = field(default_factory=PointList)
@sexp_type('render_cache')
class RenderCache:
text: str = None
rotation: int = 0
polygons: List(RenderCachePolygon) = field(default_factory=list)
|