summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gerbonara/cad/kicad/footprints.py7
-rw-r--r--gerbonara/cad/kicad/pcb.py75
-rw-r--r--twisted_coil_gen_twolayer.py43
3 files changed, 102 insertions, 23 deletions
diff --git a/gerbonara/cad/kicad/footprints.py b/gerbonara/cad/kicad/footprints.py
index 5c27855..90050ef 100644
--- a/gerbonara/cad/kicad/footprints.py
+++ b/gerbonara/cad/kicad/footprints.py
@@ -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
diff --git a/gerbonara/cad/kicad/pcb.py b/gerbonara/cad/kicad/pcb.py
index ccda43f..a97986b 100644
--- a/gerbonara/cad/kicad/pcb.py
+++ b/gerbonara/cad/kicad/pcb.py
@@ -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(
diff --git a/twisted_coil_gen_twolayer.py b/twisted_coil_gen_twolayer.py
index d84b4db..4284e8a 100644
--- a/twisted_coil_gen_twolayer.py
+++ b/twisted_coil_gen_twolayer.py
@@ -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()