coil gen: add kicad pcb export

This commit is contained in:
jaseg 2023-09-20 14:24:15 +02:00
parent 5ff40e0ad1
commit 5f1350d4f4
3 changed files with 102 additions and 23 deletions

View file

@ -95,6 +95,10 @@ class Line:
locked: Flag() = False
tstamp: Timestamp = None
def to_graphical_primitive(self, flip=False):
# FIXME flip
return gr.Line(self.start, self.end, self.layer, self.width, self.stroke, self.tstamp)
def render(self, variables=None, cache=None):
dasher = Dasher(self)
dasher.move(self.start.x, self.start.y)
@ -183,6 +187,9 @@ class Arc:
locked: Flag() = False
tstamp: Timestamp = None
def to_graphical_primitive(self, flip=False):
# FIXME flip
return gr.Arc(self.start, self.mid, self.end, self.layer, self.width, self.stroke, self.tstamp)
def render(self, variables=None, cache=None):
mx, my = self.mid.x, self.mid.y

View file

@ -95,8 +95,8 @@ TFBool = YesNoAtom(yes=Atom.true, no=Atom.false)
@sexp_type('pcbplotparams')
class ExportSettings:
layerselection: Named(Atom) = 0
plot_on_all_layers_selection: Named(Atom) = 0
layerselection: Named(Atom) = None
plot_on_all_layers_selection: Named(Atom) = None
disableapertmacros: Named(TFBool) = False
usegerberextensions: Named(TFBool) = True
usegerberattributes: Named(TFBool) = True
@ -246,6 +246,24 @@ class Via:
net: Named(int) = 0
tstamp: Timestamp = field(default_factory=Timestamp)
@classmethod
def from_pad(kls, pad):
if pad.type != Atom.thru_hole or pad.shape != Atom.circle:
raise ValueError('Can only convert circular through-hole pads to vias.')
if pad.drill and (pad.drill.oval or pad.drill.offset):
raise ValueError('Can only convert pads with centered, circular drills to vias.')
x, y, rot, _flip = pad.abs_pos
return kls(locked=pad.locked,
at=XYCoord(x, y),
size=max(pad.size.x, pad.size.y),
drill=pad.drill.diameter if pad.drill else 0,
layers=[l for l in pad.layers if l.endswith('.Cu')],
free=True,
net=pad.net.number if pad.net else 0,
tstamp=pad.tstamp)
@property
def abs_pos(self):
return self.at.x, self.at.y, 0, False
@ -282,10 +300,10 @@ class Via:
SUPPORTED_FILE_FORMAT_VERSIONS = [20210108, 20211014, 20221018, 20230517]
@sexp_type('kicad_pcb')
class Board:
_version: Named(int, name='version') = 20210108
_version: Named(int, name='version') = 20230517
generator: Named(Atom) = Atom.gerbonara
general: GeneralSection = field(default_factory=GeneralSection)
page: PageSettings = field(default_factory=PageSettings)
general: GeneralSection = None
page: PageSettings = None
layers: Named(Array(Untagged(LayerSettings))) = field(default_factory=list)
setup: BoardSetup = field(default_factory=BoardSetup)
properties: List(Property) = field(default_factory=list)
@ -317,6 +335,49 @@ class Board:
_trace_index_map: dict = None
@classmethod
def empty_board(kls, inner_layers=0, **kwargs):
if 'setup' not in kwargs:
kwargs['setup'] = None
b = Board(**kwargs)
b.init_default_layers(inner_layers)
b.__after_parse__(None)
return b
def init_default_layers(self, inner_layers=0):
inner = [(i, f'In{i}.Cu', 'signal', None) for i in range(1, inner_layers+1)]
self.layers = [LayerSettings(idx, name, Atom(ltype)) for idx, name, ltype, cname in [
(0, 'F.Cu', 'signal', None),
*inner,
(31, 'B.Cu', 'signal', None),
(32, 'B.Adhes', 'user', 'B.Adhesive'),
(33, 'F.Adhes', 'user', 'F.Adhesive'),
(34, 'B.Paste', 'user', None),
(35, 'F.Paste', 'user', None),
(36, 'B.SilkS', 'user', 'B.Silkscreen'),
(37, 'F.SilkS', 'user', 'F.Silkscreen'),
(38, 'B.Mask', 'user', None),
(39, 'F.Mask', 'user', None),
(40, 'Dwgs.User', 'user', 'User.Drawings'),
(41, 'Cmts.User', 'user', 'User.Comments'),
(42, 'Eco1.User', 'user', 'User.Eco1'),
(43, 'Eco2.User', 'user', 'User.Eco2'),
(44, 'Edge.Cuts', 'user', None),
(45, 'Margin', 'user', None),
(46, 'B.CrtYd', 'user', 'B.Courtyard'),
(47, 'F.CrtYd', 'user', 'F.Courtyard'),
(48, 'B.Fab', 'user', None),
(49, 'F.Fab', 'user', None),
(50, 'User.1', 'user', None),
(51, 'User.2', 'user', None),
(52, 'User.3', 'user', None),
(53, 'User.4', 'user', None),
(54, 'User.5', 'user', None),
(55, 'User.6', 'user', None),
(56, 'User.7', 'user', None),
(57, 'User.8', 'user', None),
(58, 'User.9', 'user', None)]]
def rebuild_trace_index(self):
idx = self._trace_index = rtree.index.Index()
id_map = self._trace_index_map = {}
@ -473,7 +534,7 @@ class Board:
net=self.net_id(net_name))
case cad_pr.Via(pad_stack=cad_pr.ThroughViaStack(hole, dia, unit=st_unit)):
x, y, _a, _f = obj.abs_pos()
x, y, _a, _f = obj.abs_pos
x, y = MM(x, st_unit), MM(y, obj.unit)
yield Via(
locked=locked,
@ -484,7 +545,7 @@ class Board:
net=self.net_id(net_name))
case cad_pr.Text(_x, _y, text, font_size, stroke_width, h_align, v_align, layer, dark):
x, y, a, flip = obj.abs_pos()
x, y, a, flip = obj.abs_pos
x, y = MM(x, st_unit), MM(y, st_unit)
size = MM(size, unit)
yield gr.Text(

View file

@ -130,6 +130,7 @@ def print_valid_twists(ctx, param, value):
@click.option('--footprint-name', help="Name for the generated footprint. Default: Output file name sans extension.")
@click.option('--layer-pair', default='F.Cu,B.Cu', help="Target KiCad layer pair for the generated footprint, comma-separated. Default: F.Cu/B.Cu.")
@click.option('--turns', type=int, default=5, help='Number of turns')
@click.option('--pcb/--footprint', default=False, help='Generate a KiCad PCB instead of a footprint')
@click.option('--outer-diameter', type=float, default=50, help='Outer diameter [mm]')
@click.option('--inner-diameter', type=float, default=25, help='Inner diameter [mm]')
@click.option('--trace-width', type=float, default=None)
@ -147,7 +148,7 @@ def print_valid_twists(ctx, param, value):
@click.version_option()
def generate(outfile, turns, outer_diameter, inner_diameter, via_diameter, via_drill, via_offset, trace_width, clearance,
footprint_name, layer_pair, twists, clipboard, counter_clockwise, keepout_zone, keepout_margin,
arc_tolerance):
arc_tolerance, pcb):
if 'WAYLAND_DISPLAY' in os.environ:
copy, paste, cliputil = ['wl-copy'], ['wl-paste'], 'xclip'
else:
@ -450,30 +451,40 @@ def generate(outfile, turns, outer_diameter, inner_diameter, via_diameter, via_d
else:
zones = []
fp = kicad_fp.Footprint(
name=name,
generator=kicad_fp.Atom('GerbonaraTwistedCoilGenV1'),
layer='F.Cu',
descr=f"{turns} turn {outer_diameter:.2f} mm diameter twisted coil footprint, inductance approximately {L:.6f} µH. Generated by gerbonara'c Twisted Coil generator, version {__version__}.",
clearance=clearance,
zone_connect=0,
lines=lines,
arcs=arcs,
pads=pads,
zones=zones,
)
if pcb:
obj = kicad_pcb.Board.empty_board(
zones=zones,
lines=[line.to_graphical_primitive() for line in lines],
arcs=[arc.to_graphical_primitive() for arc in arcs],
vias=[kicad_pcb.Via.from_pad(pad) for pad in pads if pad.type == kicad_pcb.Atom.thru_hole])
else:
obj = kicad_fp.Footprint(
name=name,
generator=kicad_fp.Atom('GerbonaraTwistedCoilGenV1'),
layer='F.Cu',
descr=f"{turns} turn {outer_diameter:.2f} mm diameter twisted coil footprint, inductance approximately {L:.6f} µH. Generated by gerbonara'c Twisted Coil generator, version {__version__}.",
clearance=clearance,
zone_connect=0,
lines=lines,
arcs=arcs,
pads=pads,
zones=zones,
)
if clipboard:
try:
data = obj.serialize()
print(f'Running {copy[0]}.', file=sys.stderr)
proc = subprocess.Popen(copy, stdin=subprocess.PIPE, text=True)
proc.communicate(fp.serialize())
proc.communicate(data)
print('passed to wl-clip:', data)
except FileNotFoundError:
print(f'Error: --clipboard requires the {copy[0]} and {paste[0]} utilities from {cliputil} to be installed.', file=sys.stderr)
elif not outfile:
print(fp.serialize())
print(obj.serialize())
else:
fp.write(outfile)
obj.write(outfile)
if __name__ == '__main__':
generate()