From 6002d409143a6726899a4de15c3a6b279a6b1d71 Mon Sep 17 00:00:00 2001 From: jaseg Date: Fri, 27 Sep 2019 10:07:38 +0200 Subject: Directory reorg: Put renderer into its own subdir --- renderer/support/generate_kicad.py | 322 +++++++++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100755 renderer/support/generate_kicad.py (limited to 'renderer/support/generate_kicad.py') diff --git a/renderer/support/generate_kicad.py b/renderer/support/generate_kicad.py new file mode 100755 index 0000000..3e1b614 --- /dev/null +++ b/renderer/support/generate_kicad.py @@ -0,0 +1,322 @@ +#!/usr/bin/env python3 + +import os +import sys +import time +from os import path +from textwrap import dedent +import pkgutil +import subprocess +import xml.etree.ElementTree as xe + +import ezdxf + + +__version__ = '0.1' + +PIN_TS_BASE = 0x23420000 +TEDIT_BASE = 0x23430000 +PATH_BASE = 0x23440000 + + +def sch_template(name, num_pins, yspace=200): + templ = f''' + EESchema Schematic File Version 5 + EELAYER 30 0 + EELAYER END + $Descr A3 16535 11693 + encoding utf-8 + Sheet 1 1 + Title "{name}" + Date "{time.strftime("%d %b %Y")}" + Rev "" + Comp "" + Comment1 "" + Comment2 "" + Comment3 "" + Comment4 "" + Comment5 "" + Comment6 "" + Comment7 "" + Comment8 "" + Comment9 "" + $EndDescr + {{components}} + $EndSCHEMATC + ''' + + components = [] + for i in range(num_pins): + identifier = f'TP{i}' + value = 'pogopin' + x, y = 1000, 1000 + i*yspace + components.append(dedent(f''' + $Comp + L Connector:Conn_01x01_Female {identifier} + U 1 1 {PIN_TS_BASE + i:08X} + P {x} {y} + F 0 "{identifier}" H {x-50} {y+50} 50 0000 R CNN + F 1 "{value}" H {x+50} {y} 50 0000 L CNN + F 2 "Pogopin:AutogeneratedPogopinFootprint" H {x} {y} 50 0001 C CNN + F 3 "~" H {x} {y} 50 0001 C CNN + 1 {x} {y} + -1 0 0 1 + $EndComp + ''').strip()) + + return dedent(templ).lstrip().format(components='\n'.join(components)) + +def pcb_template(outline, pins, annular=0.5): + pcb_templ = f''' + (kicad_pcb (version 20190605) (host pogojig "({__version__})") + + (general + (thickness 1.6) + (drawings {len(pins)}) + (tracks 0) + (modules {len(pins)}) + (nets {len(pins)+1}) + ) + + (page "A4") + (layers + (0 "F.Cu" signal) + (31 "B.Cu" signal) + (32 "B.Adhes" user) + (33 "F.Adhes" user) + (34 "B.Paste" user) + (35 "F.Paste" user) + (36 "B.SilkS" user) + (37 "F.SilkS" user) + (38 "B.Mask" user) + (39 "F.Mask" user) + (40 "Dwgs.User" user) + (41 "Cmts.User" user) + (42 "Eco1.User" user) + (43 "Eco2.User" user) + (44 "Edge.Cuts" user) + (45 "Margin" user) + (46 "B.CrtYd" user) + (47 "F.CrtYd" user) + (48 "B.Fab" user) + (49 "F.Fab" user) + ) + + (setup + (last_trace_width 0.25) + (trace_clearance 0.2) + (zone_clearance 0.508) + (zone_45_only no) + (trace_min 0.2) + (via_size 0.8) + (via_drill 0.4) + (via_min_size 0.4) + (via_min_drill 0.3) + (uvia_size 0.3) + (uvia_drill 0.1) + (uvias_allowed no) + (uvia_min_size 0.2) + (uvia_min_drill 0.1) + (max_error 0.005) + (defaults + (edge_clearance 0.01) + (edge_cuts_line_width 0.05) + (courtyard_line_width 0.05) + (copper_line_width 0.2) + (copper_text_dims (size 1.5 1.5) (thickness 0.3) keep_upright) + (silk_line_width 0.12) + (silk_text_dims (size 1 1) (thickness 0.15) keep_upright) + (other_layers_line_width 0.1) + (other_layers_text_dims (size 1 1) (thickness 0.15) keep_upright) + ) + (pad_size 3.14159 3.14159) + (pad_drill 1.41421) + (pad_to_mask_clearance 0.051) + (solder_mask_min_width 0.25) + (aux_axis_origin 0 0) + (visible_elements FFFFFF7F) + (pcbplotparams + (layerselection 0x010fc_ffffffff) + (usegerberextensions false) + (usegerberattributes false) + (usegerberadvancedattributes false) + (creategerberjobfile false) + (excludeedgelayer true) + (linewidth 0.100000) + (plotframeref false) + (viasonmask false) + (mode 1) + (useauxorigin false) + (hpglpennumber 1) + (hpglpenspeed 20) + (hpglpendiameter 15.000000) + (psnegative false) + (psa4output false) + (plotreference true) + (plotvalue true) + (plotinvisibletext false) + (padsonsilk false) + (subtractmaskfromsilk false) + (outputformat 1) + (mirror false) + (drillshape 1) + (scaleselection 1) + (outputdirectory "")) + ) + + (net 0 "") + {{net_defs}} + + (net_class "Default" "This is the default net class." + (clearance 0.2) + (trace_width 0.25) + (via_dia 0.8) + (via_drill 0.4) + (uvia_dia 0.3) + (uvia_drill 0.1) + {{net_class_defs}} + ) + + {{module_defs}} + + {{edge_cuts}} + )''' + + module_defs = [] + for i, pin in enumerate(pins): + (x, y), hole_dia = pin # all dimensions in mm here + pad_dia = hole_dia + 2*annular + mod = f''' + (module "Pogopin:AutogeneratedPogopinFootprint" (layer "F.Cu") (tedit {TEDIT_BASE + i:08X}) (tstamp {PIN_TS_BASE + i:08X}) + (at {x} {y}) + (descr "Pogo pin {i}") + (tags "test point plated hole") + (path "/{PATH_BASE + i:08X}") + (attr virtual) + (fp_text reference "TP{i}" (at 0 -{pad_dia/2 + 1}) (layer "F.SilkS") + (effects (font (size 1 1) (thickness 0.15))) + ) + (fp_text value "pogo pin {i}" (at 0 {pad_dia/2 + 1}) (layer "F.Fab") + (effects (font (size 1 1) (thickness 0.15))) + ) + (fp_text user "%R" (at 0 -{pad_dia/2 + 1}) (layer "F.Fab") + (effects (font (size 1 1) (thickness 0.15))) + ) + (fp_circle (center 0 0) (end {pad_dia} 0) (layer "F.CrtYd") (width 0.05)) + (fp_circle (center 0 0) (end 0 -{pad_dia}) (layer "F.SilkS") (width 0.12)) + (pad "1" thru_hole circle (at 0 0) (size {pad_dia} {pad_dia}) (drill {hole_dia}) (layers *.Cu *.Mask) + (net {i+1} "pogo{i}")) + )''' + module_defs.append(mod) + + edge_cuts = [ f'(gr_line (start {x1} {y1}) (end {x2} {y2}) (layer "Edge.Cuts") (width 0.05))' + for (x1, y1), (x2, y2) in outline ] + + net_defs = [ f'(net {i+1} "pogo{i}")' for i, _pin in enumerate(pins) ] + net_class_defs = [ f'(add_net "pogo{i}")' for i, _pin in enumerate(pins) ] + return pcb_templ.format( + net_defs='\n'.join(net_defs), + net_class_defs='\n'.join(net_class_defs), + module_defs='\n'.join(module_defs), + edge_cuts='\n'.join(edge_cuts)) + +def inkscape_query_all(filename): + proc = subprocess.run([ os.environ.get('INKSCAPE', 'inkscape'), filename, '--query-all'], capture_output=True) + proc.check_returncode() + data = [ line.split(',') for line in proc.stdout.decode().splitlines() ] + return { id: (float(x), float(y), float(w), float(h)) for id, x, y, w, h in data } + +SVG_NS = { + 'svg': 'http://www.w3.org/2000/svg', + 'inkscape': 'http://www.inkscape.org/namespaces/inkscape', + 'sodipodi': 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' +} + +def svg_find_elements(doc, tag, layer=None): + for i, g in enumerate(doc.findall('svg:g', SVG_NS)): + if g.attrib.get(f'{{{SVG_NS["inkscape"]}}}groupmode') != 'layer': + continue + + label = g.attrib.get(f'{{{SVG_NS["inkscape"]}}}label', '') + if not layer or label == layer: + yield from g.iter(tag) + +# def svg_get_scale(doc): +# w = doc.attrib['width'] +# h = doc.attrib['height'] +# +# if not w.endswith('mm') and h.endswith('mm'): +# raise ValueError('Document dimensions in SVG must be set to millimeters') +# +# w, h = float(w[:-2]), float(h[:-2]) +# _x, _y, vb_w, vb_h = map(float, doc.attrib['viewBox'].split()) +# scale_x, scale_y = vb_w / w, vb_h / h +# assert abs(1 - scale_x/scale_y) < 0.001 +# return scale_x + +def svg_get_viewbox_mm(doc): + w = doc.attrib['width'] + h = doc.attrib['height'] + + if not w.endswith('mm') and h.endswith('mm'): + raise ValueError('Document dimensions in SVG must be set to millimeters') + + w, h = float(w[:-2]), float(h[:-2]) + x, y, vb_w, vb_h = map(float, doc.attrib['viewBox'].split()) + scale_x, scale_y = vb_w / w, vb_h / h + return x/scale_x, y/scale_y, w, h + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('svg', metavar='pogo_map.svg', help='Input inkscape SVG pogo pin map (use provided template!)') + parser.add_argument('outline', metavar='outline.dxf', help='Board outline DXF generated by OpenSCAD') + parser.add_argument('output', default='kicad', help='Output directory/project name and path') + parser.add_argument('-y', '--yspace', type=int, default=200, help='Schematic pin Y spacing in mil (default: 200)') + parser.add_argument('-a', '--annular', type=float, default=0.5, help='Pogo pin annular ring width in mm (default: 0.5)') + parser.add_argument('-l', '--svg-layer', type=str, default='Test Points', help='Name of SVG layer containing pogo pins') + parser.add_argument('-n', '--name', default='jig', help='Output KiCAD project name') + args = parser.parse_args() + + if not path.exists(args.output): + os.mkdir(args.output) + + if not path.isdir(args.output): + raise SystemError(f'Output path "{args.output}" is not a directory') + + with open(args.svg, 'r') as f: + doc = xe.fromstring(f.read()) + pogo_circle_ids = [ circle.attrib['id'] for circle in svg_find_elements(doc, f'{{{SVG_NS["svg"]}}}circle', args.svg_layer) ] + # scale = svg_get_scale(doc) + page_x, page_y, page_w, page_h = svg_get_viewbox_mm(doc) + MM_PER_IN = 25.4 + SVG_DEF_DPI = 96 + px_to_mm = lambda px: px/SVG_DEF_DPI * MM_PER_IN + query = inkscape_query_all(args.svg) + dims = [ query[id] for id in pogo_circle_ids ] + assert all( abs(1 - w/h) < 0.001 for _x, _y, w, h in dims ) + print('origin:', page_x, page_y) + print('dims:', page_w, page_h) + pins = [ ( + (page_x + px_to_mm(x) + px_to_mm(w)/2, + page_y - page_h + px_to_mm(y) + px_to_mm(w)/2), + px_to_mm(w)) for x, y, w, h in dims ] + + doc = ezdxf.readfile(args.outline) + outline = [] + for line in doc.modelspace().query('LINE'): + (x1, y1, _z1), (x2, y2, _z2) = line.dxf.start, line.dxf.end + outline.append(((x1, -y1), (x2, -y2))) + + with open(path.join(args.output, f'{args.name}.sch'), 'w', encoding='utf8') as sch: + sch.write(sch_template(f'{args.name} generated schematic (PogoJig v{__version__})', len(pins), yspace=args.yspace)) + + with open(path.join(args.output, f'{args.name}.kicad_pcb'), 'w', encoding='utf8') as pcb: + pcb.write(pcb_template(outline, pins, annular=args.annular)) + + with open(path.join(args.output, f'{args.name}.pro'), 'w', encoding='utf8') as f: + f.write(pkgutil.get_data('pogojig.kicad', 'kicad.pro').decode('utf8')) + + with open(path.join(args.output, f'{args.name}-cache.lib'), 'w', encoding='utf8') as f: + f.write(pkgutil.get_data('pogojig.kicad', 'kicad-cache.lib').decode('utf8')) + -- cgit