summaryrefslogtreecommitdiff
path: root/support/generate_kicad.py
diff options
context:
space:
mode:
Diffstat (limited to 'support/generate_kicad.py')
-rwxr-xr-xsupport/generate_kicad.py322
1 files changed, 0 insertions, 322 deletions
diff --git a/support/generate_kicad.py b/support/generate_kicad.py
deleted file mode 100755
index 3e1b614..0000000
--- a/support/generate_kicad.py
+++ /dev/null
@@ -1,322 +0,0 @@
-#!/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'))
-