Bring kicad PCB file format up to 9.0.5
This commit is contained in:
parent
fd6880640d
commit
42dfd1be7f
7 changed files with 196 additions and 39 deletions
|
|
@ -600,6 +600,14 @@ class DrawnProperty(TextMixin):
|
|||
self.value = value
|
||||
|
||||
|
||||
@sexp_type('chamfer')
|
||||
class Chamfer:
|
||||
top_left: Flag() = False
|
||||
top_right: Flag() = False
|
||||
bottom_left: Flag() = False
|
||||
bottom_right: Flag() = False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
class Foo:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ class Arc:
|
|||
mid: Rename(XYCoord) = None
|
||||
end: Rename(XYCoord) = None
|
||||
width: Named(float) = None
|
||||
angle: Named(float) = None
|
||||
stroke: Stroke = None
|
||||
layer: Named(str) = None
|
||||
uuid: UUID = field(default_factory=UUID)
|
||||
|
|
@ -321,27 +322,6 @@ class CustomPadPrimitives:
|
|||
yield from self.curves
|
||||
|
||||
|
||||
@sexp_type('chamfer')
|
||||
class Chamfer:
|
||||
top_left: Flag() = False
|
||||
top_right: Flag() = False
|
||||
bottom_left: Flag() = False
|
||||
bottom_right: Flag() = False
|
||||
|
||||
|
||||
@sexp_type('teardrops')
|
||||
class TeardropSpec:
|
||||
best_length_ratio: Named(float) = 1.0
|
||||
max_length: Named(float) = 2.0
|
||||
best_width_ratio: Named(float) = 1.0
|
||||
max_width: Named(float) = 2.0
|
||||
curve_points: Named(int) = 0
|
||||
filter_ratio: Named(float) = 0.9
|
||||
enabled: Named(YesNoAtom()) = True
|
||||
allow_two_segments: Named(YesNoAtom()) = True
|
||||
prefer_zone_connections: Named(YesNoAtom()) = True
|
||||
|
||||
|
||||
@sexp_type('pad')
|
||||
class Pad(NetMixin):
|
||||
number: str = None
|
||||
|
|
@ -368,7 +348,7 @@ class Pad(NetMixin):
|
|||
pin_function: Named(str) = None
|
||||
pintype: Named(str) = None
|
||||
pinfunction: Named(str) = None
|
||||
teardrops: TeardropSpec = None
|
||||
teardrops: gr.TeardropSpec = None
|
||||
die_length: Named(float) = None
|
||||
solder_mask_margin: Named(float) = None
|
||||
solder_paste_margin: Named(float) = None
|
||||
|
|
@ -378,6 +358,7 @@ class Pad(NetMixin):
|
|||
thermal_width: Named(float) = None
|
||||
thermal_gap: Named(float) = None
|
||||
options: OmitDefault(CustomPadOptions) = None
|
||||
padstack: gr.PadStack = None
|
||||
primitives: OmitDefault(CustomPadPrimitives) = None
|
||||
_: SEXP_END = None
|
||||
footprint: object = field(repr=False, default=None)
|
||||
|
|
@ -579,10 +560,16 @@ class Model:
|
|||
hide: Flag() = False
|
||||
at: Named(XYZCoord) = field(default_factory=XYZCoord)
|
||||
offset: Named(XYZCoord) = field(default_factory=XYZCoord)
|
||||
opacity: Named(float) = None
|
||||
scale: Named(XYZCoord) = field(default_factory=XYZCoord)
|
||||
rotate: Named(XYZCoord) = field(default_factory=XYZCoord)
|
||||
|
||||
|
||||
@sexp_type('component_classes')
|
||||
class FootprintComponentClasses:
|
||||
classes: List(Named(str, name='class')) = field(default_factory=list)
|
||||
|
||||
|
||||
SUPPORTED_FILE_FORMAT_VERSIONS = [20210108, 20211014, 20221018, 20230517]
|
||||
@sexp_type('footprint')
|
||||
class Footprint:
|
||||
|
|
@ -600,6 +587,7 @@ class Footprint:
|
|||
descr: Named(str) = None
|
||||
tags: Named(str) = None
|
||||
properties: List(DrawnProperty) = field(default_factory=list)
|
||||
component_classes: FootprintComponentClasses = None
|
||||
path: Named(str) = None
|
||||
sheetname: Named(str) = None
|
||||
sheetfile: Named(str) = None
|
||||
|
|
|
|||
|
|
@ -40,12 +40,14 @@ class TextBox(BBoxMixin):
|
|||
text: str = ''
|
||||
start: Named(XYCoord) = None
|
||||
end: Named(XYCoord) = None
|
||||
margins: Margins = None
|
||||
pts: PointList = field(default_factory=list)
|
||||
angle: OmitDefault(Named(float)) = 0.0
|
||||
layer: Named(str) = ""
|
||||
uuid: UUID = field(default_factory=UUID)
|
||||
tstamp: Timestamp = None
|
||||
effects: TextEffect = field(default_factory=TextEffect)
|
||||
border: Named(YesNoAtom()) = False
|
||||
stroke: Stroke = field(default_factory=Stroke)
|
||||
render_cache: RenderCache = None
|
||||
|
||||
|
|
@ -112,6 +114,20 @@ class Line(WidthMixin):
|
|||
return (x_min-w, y_max-w), (x_max+w, y_max+w)
|
||||
|
||||
|
||||
@sexp_type('target')
|
||||
class Target(WidthMixin):
|
||||
shape: AtomChoice(Atom.x, Atom.plus) = 'plus'
|
||||
at: AtPos = field(default_factory=AtPos)
|
||||
size: Rename(XYCoord) = field(default_factory=XYCoord)
|
||||
width: Named(float) = None
|
||||
layer: Named(str) = None
|
||||
uuid: UUID = field(default_factory=UUID)
|
||||
tstamp: Timestamp = None
|
||||
|
||||
def render(self, variables=None):
|
||||
raise NotImplementedError('Target objects are not implemented yet')
|
||||
|
||||
|
||||
@sexp_type('fill')
|
||||
class FillMode:
|
||||
# Needed for compatibility with weird files
|
||||
|
|
@ -199,6 +215,7 @@ class Arc(WidthMixin, BBoxMixin):
|
|||
start: Rename(XYCoord) = None
|
||||
mid: Rename(XYCoord) = None
|
||||
end: Rename(XYCoord) = None
|
||||
angle: Named(float) = None
|
||||
layer: Named(str) = None
|
||||
width: Named(float) = None
|
||||
stroke: Stroke = field(default_factory=Stroke)
|
||||
|
|
@ -322,6 +339,7 @@ class DimensionFormat:
|
|||
precision: Named(int) = 7
|
||||
override_value: Named(str) = None
|
||||
suppress_zeros: Flag() = False
|
||||
suppress_zeroes: Flag() = False
|
||||
|
||||
|
||||
@sexp_type('style')
|
||||
|
|
@ -356,6 +374,7 @@ class Image:
|
|||
at: AtPos = field(default_factory=AtPos)
|
||||
scale: Named(float) = None
|
||||
layer: Named(str) = None
|
||||
locked: Flag() = False
|
||||
uuid: UUID = field(default_factory=UUID)
|
||||
data: Base64Blob = ''
|
||||
|
||||
|
|
@ -365,6 +384,7 @@ class Image:
|
|||
|
||||
@sexp_type('dimension')
|
||||
class Dimension:
|
||||
value: float = None
|
||||
locked: Flag() = False
|
||||
dimension_type: Named(AtomChoice(Atom.aligned, Atom.leader, Atom.center, Atom.orthogonal, Atom.radial), name='type') = Atom.aligned
|
||||
layer: Named(str) = 'Dwgs.User'
|
||||
|
|
@ -372,6 +392,7 @@ class Dimension:
|
|||
tstamp: Timestamp = field(default_factory=Timestamp)
|
||||
pts: PointList = field(default_factory=list)
|
||||
height: Named(float) = None
|
||||
width: Named(float) = None
|
||||
orientation: Named(int) = None
|
||||
leader_length: Named(float) = None
|
||||
gr_text: Text = None
|
||||
|
|
@ -384,3 +405,58 @@ class Dimension:
|
|||
def offset(self, x=0, y=0):
|
||||
self.pts = [pt.with_offset(x, y) for pt in self.pts]
|
||||
|
||||
|
||||
@sexp_type('options')
|
||||
class PadStackLayerOptions:
|
||||
anchor: AtomChoice(Atom.rect, Atom.circle) = Atom.circle
|
||||
|
||||
|
||||
@sexp_type('primitives')
|
||||
class PadStackPrimitives:
|
||||
vectors: Rename(Line, name='gr_vector') = field(default_factory=list)
|
||||
lines: List(Line) = field(default_factory=list)
|
||||
bboxes: List(AnnotationBBox) = field(default_factory=list)
|
||||
arcs: List(Arc) = field(default_factory=list)
|
||||
circles: List(Circle) = field(default_factory=list)
|
||||
curves: List(Curve) = field(default_factory=list)
|
||||
polygons:List(Polygon) = field(default_factory=list)
|
||||
|
||||
|
||||
@sexp_type('layer')
|
||||
class PadStackLayer:
|
||||
layer: str = ''
|
||||
shape: Named(AtomChoice(Atom.circle, Atom.rect, Atom.oval, Atom.trapezoid, Atom.roundrect, Atom.custom)) = Atom.circle
|
||||
size: Rename(XYCoord) = field(default_factory=XYCoord)
|
||||
rect_delta: Rename(XYCoord) = None
|
||||
offset: Rename(XYCoord) = None
|
||||
roundrect_rratio: Named(float) = None
|
||||
chamfer_ratio: Named(float) = None
|
||||
chamfer: Chamfer = None
|
||||
primitives: PadStackPrimitives = None
|
||||
options: PadStackLayerOptions = None
|
||||
thermal_bridge_angle: Named(float) = None
|
||||
thermal_gap: Named(float) = None
|
||||
thermal_bridge_width: Named(float) = None
|
||||
clearance: Named(float) = None
|
||||
zone_connect: Named(int) = None
|
||||
|
||||
|
||||
@sexp_type('padstack')
|
||||
class PadStack:
|
||||
mode: Named(AtomChoice('front_inner_back', 'custom')) = Atom.front_inner_back
|
||||
layers: List(PadStackLayer) = field(default_factory=list)
|
||||
|
||||
|
||||
@sexp_type('teardrops')
|
||||
class TeardropSpec:
|
||||
best_length_ratio: Named(float) = 1.0
|
||||
max_length: Named(float) = 2.0
|
||||
best_width_ratio: Named(float) = 1.0
|
||||
max_width: Named(float) = 2.0
|
||||
curve_points: Named(int) = 0
|
||||
filter_ratio: Named(float) = 0.9
|
||||
enabled: Named(YesNoAtom()) = True
|
||||
allow_two_segments: Named(YesNoAtom()) = True
|
||||
prefer_zone_connections: Named(YesNoAtom()) = True
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -60,10 +60,14 @@ def gn_layer_to_kicad(layer, flip=False):
|
|||
class GeneralSection:
|
||||
thickness: Named(float) = 1.60
|
||||
legacy_teardrops: Named(YesNoAtom()) = False
|
||||
drawings: Named(int) = 4
|
||||
tracks: Named(int) = 14
|
||||
modules: Named(int) = 2
|
||||
nets: Named(int) = 4
|
||||
drawings: Named(int) = None
|
||||
tracks: Named(int) = None
|
||||
zones: Named(int) = None
|
||||
modules: Named(int) = None
|
||||
nets: Named(int) = None
|
||||
links: Named(int) = None
|
||||
no_connects: Named(int) = None
|
||||
area: Named(Array(float)) = None
|
||||
|
||||
|
||||
@sexp_type('layers')
|
||||
|
|
@ -112,8 +116,10 @@ class TrackSegment(BBoxMixin):
|
|||
start: Rename(XYCoord) = field(default_factory=XYCoord)
|
||||
end: Rename(XYCoord) = field(default_factory=XYCoord)
|
||||
width: Named(float) = 0.5
|
||||
layer: Named(str) = 'F.Cu'
|
||||
locked: Flag() = False
|
||||
layer: Named(str) = 'F.Cu'
|
||||
extra_layers: Named(Array(str), name='layers') = field(default_factory=list)
|
||||
solder_mask_margin: Named(float) = None
|
||||
net: Named(int) = 0
|
||||
uuid: UUID = field(default_factory=UUID)
|
||||
tstamp: Timestamp = None
|
||||
|
|
@ -127,6 +133,15 @@ class TrackSegment(BBoxMixin):
|
|||
self.start = XYCoord(self.start)
|
||||
self.end = XYCoord(self.end)
|
||||
|
||||
def __after_parse__(self, parent):
|
||||
if self.extra_layers:
|
||||
self.layer, *self.extra_layers = self.extra_layers
|
||||
|
||||
def __before_sexp__(self):
|
||||
if self.extra_layers:
|
||||
self.extra_layers.insert(0, self.layer)
|
||||
self.layer = None
|
||||
|
||||
@property
|
||||
def layer_mask(self):
|
||||
return layer_mask([self.layer])
|
||||
|
|
@ -195,6 +210,13 @@ class TrackArc(BBoxMixin):
|
|||
self.end = self.end.with_offset(x, y)
|
||||
|
||||
|
||||
@sexp_type('tenting')
|
||||
class Tenting:
|
||||
front: Flag() = False
|
||||
back: Flag() = False
|
||||
none: Flag() = False
|
||||
|
||||
|
||||
@sexp_type('via')
|
||||
class Via(BBoxMixin):
|
||||
via_type: AtomChoice(Atom.blind, Atom.micro) = None
|
||||
|
|
@ -203,6 +225,9 @@ class Via(BBoxMixin):
|
|||
size: Named(float) = 0.8
|
||||
drill: Named(float) = 0.4
|
||||
layers: Named(Array(str)) = field(default_factory=lambda: ['F.Cu', 'B.Cu'])
|
||||
teardrops: gr.TeardropSpec = None
|
||||
tenting: Tenting = None
|
||||
padstack: gr.PadStack = None
|
||||
remove_unused_layers: Flag() = False
|
||||
keep_end_layers: Flag() = False
|
||||
free: Named(YesNoAtom()) = False
|
||||
|
|
@ -262,7 +287,48 @@ class Via(BBoxMixin):
|
|||
self.at = self.at.with_offset(x, y)
|
||||
|
||||
|
||||
SUPPORTED_FILE_FORMAT_VERSIONS = [20210108, 20211014, 20221018, 20230517]
|
||||
@sexp_type('net_class')
|
||||
class LegacyNetclass:
|
||||
name: str = ''
|
||||
description: str = ''
|
||||
clearance: Named(float) = None
|
||||
trace_width: Named(float) = None
|
||||
via_dia: Named(float) = None
|
||||
via_drill: Named(float) = None
|
||||
uvia_dia: Named(float) = None
|
||||
uvia_drill: Named(float) = None
|
||||
diff_pair_width: Named(float) = None
|
||||
diff_pair_gap: Named(float) = None
|
||||
nets: Rename(List(Named(str)), name='add_net') = field(default_factory=list)
|
||||
|
||||
|
||||
@sexp_type('generated')
|
||||
class GeneratedPatterns:
|
||||
type: Named(Atom) = ''
|
||||
name: Named(str) = ''
|
||||
layer: Named(str) = ''
|
||||
locked: Flag() = False
|
||||
members: Named(Array(Atom), name='members') = field(default_factory=list)
|
||||
_ : SEXP_END = None
|
||||
params: dict = field(default_factory=dict)
|
||||
|
||||
def __catchall__(self, sexp_value, path=''):
|
||||
key, value = sexp_value
|
||||
self.params[key] = value
|
||||
|
||||
@classmethod
|
||||
def __sexp__(kls, value):
|
||||
return [kls.name_atom,
|
||||
['type', value.type],
|
||||
['name', value.name],
|
||||
['layer', value.layer],
|
||||
['locked', ('true' if value.locked else 'false')],
|
||||
*[[k, v] for k, v in value.params.items()],
|
||||
['members', *value.members]]
|
||||
|
||||
|
||||
|
||||
SUPPORTED_FILE_FORMAT_VERSIONS = [20200119, 20200512, 20210108, 20211014, 20220621, 20221018, 20230517, 20240706, 20240922, 20241229]
|
||||
@sexp_type('kicad_pcb')
|
||||
class Board:
|
||||
_version: Named(int, name='version') = 20230517
|
||||
|
|
@ -270,18 +336,21 @@ class Board:
|
|||
generator_version: Named(str) = Atom.gerbonara
|
||||
legacy_generator: Named(Array(str), name='host') = None
|
||||
general: GeneralSection = None
|
||||
page: PageSettings = None
|
||||
legacy_paper: Named(str, name='paper') = None
|
||||
paper: PageSettings = None
|
||||
legacy_page: Rename(PageSettings, 'page') = None
|
||||
title_block: TitleBlock = None
|
||||
layers: Named(Array(Untagged(LayerSettings))) = field(default_factory=list)
|
||||
setup: BoardSetup = field(default_factory=BoardSetup)
|
||||
properties: List(Property) = field(default_factory=list)
|
||||
nets: List(Net) = field(default_factory=list)
|
||||
legacy_netclasses: List(LegacyNetclass) = field(default_factory=list)
|
||||
footprints: List(Footprint) = field(default_factory=list)
|
||||
legacy_footprints: Rename(List(Footprint), 'module') = field(default_factory=list)
|
||||
# Graphical elements
|
||||
texts: List(gr.Text) = field(default_factory=list)
|
||||
text_boxes: List(gr.TextBox) = field(default_factory=list)
|
||||
lines: List(gr.Line) = field(default_factory=list)
|
||||
targets: List(gr.Target) = field(default_factory=list)
|
||||
rectangles: List(gr.Rectangle) = field(default_factory=list)
|
||||
circles: List(gr.Circle) = field(default_factory=list)
|
||||
arcs: List(gr.Arc) = field(default_factory=list)
|
||||
|
|
@ -296,6 +365,7 @@ class Board:
|
|||
# Other stuff
|
||||
zones: List(Zone) = field(default_factory=list)
|
||||
groups: List(Group) = field(default_factory=list)
|
||||
generated_patterns: List(GeneratedPatterns) = field(default_factory=list)
|
||||
embedded_fonts: Named(YesNoAtom()) = False
|
||||
|
||||
_ : SEXP_END = None
|
||||
|
|
@ -462,6 +532,8 @@ class Board:
|
|||
fp.board = self
|
||||
|
||||
self.nets = {net.index: net.name for net in self.nets}
|
||||
if self.legacy_page:
|
||||
self.paper, self.legacy_page = self.legacy_page, None
|
||||
|
||||
|
||||
def __before_sexp__(self):
|
||||
|
|
|
|||
|
|
@ -157,17 +157,18 @@ class ZonePlacement:
|
|||
|
||||
|
||||
@sexp_type('teardrop')
|
||||
class TeardropSpec:
|
||||
type: Named(AtomChoice(Atom.padvia, Atom.pad_end)) = Atom.padvia
|
||||
class ZoneTeardropSpec:
|
||||
type: Named(AtomChoice(Atom.padvia, Atom.track_end)) = Atom.padvia
|
||||
|
||||
|
||||
@sexp_type('attr')
|
||||
class ZoneAttr:
|
||||
teardrop: TeardropSpec = None
|
||||
teardrop: ZoneTeardropSpec = None
|
||||
|
||||
|
||||
@sexp_type('zone')
|
||||
class Zone:
|
||||
locked: Flag() = False
|
||||
net: Named(int) = 0
|
||||
net_name: Named(str) = ""
|
||||
layer: Named(str) = None
|
||||
|
|
|
|||
|
|
@ -120,7 +120,8 @@ class WrapperType:
|
|||
|
||||
def __bind_field__(self, field):
|
||||
self.field = field
|
||||
getattr(self.next_type, '__bind_field__', lambda x: None)(field)
|
||||
if self.next_type is not Atom:
|
||||
getattr(self.next_type, '__bind_field__', lambda x: None)(field)
|
||||
|
||||
def __atoms__(self):
|
||||
if hasattr(self, 'name_atom'):
|
||||
|
|
@ -314,10 +315,20 @@ class _SexpTemplate:
|
|||
setattr(inst, name, mapped)
|
||||
|
||||
elif isinstance(v, list):
|
||||
name, etype = kls.keys[v[0]]
|
||||
mapped = map_sexp(etype, v, parent=inst, path=f'{path}/{kls.name_atom}')
|
||||
if mapped is not None:
|
||||
setattr(inst, name, mapped)
|
||||
key = v[0]
|
||||
if key in kls.keys:
|
||||
name, etype = kls.keys[key]
|
||||
mapped = map_sexp(etype, v, parent=inst, path=f'{path}/{kls.name_atom}')
|
||||
if mapped is not None:
|
||||
setattr(inst, name, mapped)
|
||||
|
||||
elif hasattr(inst, '__catchall__'):
|
||||
inst.__catchall__(v, path=f'{path}/{kls.name_atom}')
|
||||
|
||||
else:
|
||||
#print('class has keys:')
|
||||
#print('\n'.join(map(str, kls.keys)))
|
||||
raise TypeError(f'Unhandled keyed argument {v!r} while parsing {kls}')
|
||||
|
||||
else:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ from gerbonara.cad.kicad.pcb import Board
|
|||
|
||||
def test_load_kicad_pcb(kicad_pcb_file):
|
||||
if kicad_pcb_file.name in [
|
||||
# contains legacy syntax
|
||||
'fakeboard.kicad_pcb', # malformed test file
|
||||
'ZoneFill-4.0.7.kicad_pcb', # Super old version
|
||||
]:
|
||||
pytest.skip()
|
||||
pcb = Board.open(kicad_pcb_file)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue