coil gen: add kicad pcb export
This commit is contained in:
parent
5ff40e0ad1
commit
5f1350d4f4
3 changed files with 102 additions and 23 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue