mirror of
https://github.com/whowechina/popn_pico.git
synced 2025-03-03 16:43:49 +01:00
264 lines
7.5 KiB
Python
264 lines
7.5 KiB
Python
"""
|
|
build_mod_chip.py
|
|
Copyright 2015 Adam Greig
|
|
Licensed under the MIT licence, see LICENSE file for details.
|
|
|
|
Create two-terminal chip packages.
|
|
"""
|
|
from __future__ import print_function, division
|
|
|
|
# Other constants =============================================================
|
|
|
|
# Courtyard clearance
|
|
# Use 0.25 for IPC nominal and 0.10 for IPC least.
|
|
ctyd_gap = 0.25
|
|
|
|
# Courtyard grid
|
|
ctyd_grid = 0.05
|
|
|
|
# Courtyard line width
|
|
ctyd_width = 0.01
|
|
|
|
# Internal silk clearance from pads
|
|
silk_pad_igap = 0.2
|
|
|
|
# External silk clearance from pads
|
|
silk_pad_egap = 0.2
|
|
|
|
# Silk line width
|
|
silk_width = 0.15
|
|
|
|
# Fab layer line width
|
|
fab_width = 0.01
|
|
|
|
# Ref/Val font size (width x height)
|
|
font_size = (1.0, 1.0)
|
|
|
|
# Ref/Val font thickness
|
|
font_thickness = 0.15
|
|
|
|
# Ref/Val font spacing from centre to top/bottom edge
|
|
font_halfheight = 0.7
|
|
|
|
# End constants ===============================================================
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
import math
|
|
import argparse
|
|
import yaml
|
|
import fnmatch
|
|
|
|
from sexp import parse as sexp_parse, generate as sexp_generate
|
|
from kicad_mod import fp_line, fp_text, pad, draw_square, model
|
|
|
|
|
|
def refs(conf):
|
|
"""Generate val and ref labels."""
|
|
out = []
|
|
x = conf['pitch']/2 + conf['pad_shape'][0]/2 + ctyd_gap + font_halfheight
|
|
out.append(fp_text("reference", "REF**", (-x, 0, 90), "F.Fab",
|
|
font_size, font_thickness))
|
|
out.append(fp_text("value", conf['name'], (x, 0, 90), "F.Fab",
|
|
font_size, font_thickness))
|
|
return out
|
|
|
|
|
|
def fab(conf):
|
|
"""Generate a drawing of the chip on the Fab layer."""
|
|
out = []
|
|
cw, ch = conf['chip_shape']
|
|
pw, ph = conf['pin_shape']
|
|
w = fab_width
|
|
|
|
# Chip body
|
|
_, _, _, _, sq = draw_square(cw, ch, (0, 0), "F.Fab", w)
|
|
out += sq
|
|
|
|
# Pin 1 indicator
|
|
if conf.get('silk') in ("triangle", "internal_pin1", "external_pin1"):
|
|
out.append(fp_line(
|
|
(-cw/4, -ch/2), (-cw/4, ch/2), "F.Fab", w))
|
|
|
|
# Left pin
|
|
out.append(fp_line((-cw/2 - pw, -ph/2), (-cw/2 - pw, ph/2), "F.Fab", w))
|
|
if ph != ch:
|
|
out.append(fp_line((-cw/2 - pw, -ph/2), (-cw/2, -ph/2), "F.Fab", w))
|
|
out.append(fp_line((-cw/2 - pw, ph/2), (-cw/2, ph/2), "F.Fab", w))
|
|
|
|
# Right pin
|
|
out.append(fp_line((cw/2 + pw, -ph/2), (cw/2 + pw, ph/2), "F.Fab", w))
|
|
if ph != ch:
|
|
out.append(fp_line((cw/2 + pw, -ph/2), (cw/2, -ph/2), "F.Fab", w))
|
|
out.append(fp_line((cw/2 + pw, ph/2), (cw/2, ph/2), "F.Fab", w))
|
|
|
|
return out
|
|
|
|
|
|
def internal_silk(conf):
|
|
"""Draw internal silkscreen."""
|
|
w = conf['pitch'] - conf['pad_shape'][0] - 2*silk_pad_igap
|
|
h = conf['chip_shape'][1] - silk_width
|
|
_, _, _, _, sq = draw_square(w, h, (0, 0), "F.SilkS", silk_width)
|
|
return sq
|
|
|
|
|
|
def external_silk(conf):
|
|
"""Draw external silkscreen."""
|
|
out = []
|
|
return out
|
|
|
|
|
|
def triangle_silk(conf):
|
|
"""Draw a triangle silkscreen pointing to pin 1."""
|
|
out = []
|
|
w = conf['pitch'] - conf['pad_shape'][0] - 2*silk_pad_igap
|
|
h = conf['chip_shape'][1] - silk_width
|
|
out.append(fp_line((-w/2, 0), (w/2, -h/2), "F.SilkS", silk_width))
|
|
out.append(fp_line((-w/2, 0), (w/2, +h/2), "F.SilkS", silk_width))
|
|
out.append(fp_line((w/2, -h/2), (w/2, +h/2), "F.SilkS", silk_width))
|
|
return out
|
|
|
|
|
|
def pin1_silk(conf):
|
|
"""Draw a small pin1 indicator on the silkscreen."""
|
|
w = conf['pitch'] - conf['pad_shape'][0] - 2*silk_pad_igap
|
|
h = conf['chip_shape'][1] - silk_width
|
|
return [fp_line((-w/4, -h/2), (-w/4, h/2), "F.SilkS", silk_width)]
|
|
|
|
|
|
def silk(conf):
|
|
s = conf.get('silk', 'internal')
|
|
if s == "internal":
|
|
return internal_silk(conf)
|
|
elif s == "external":
|
|
return external_silk(conf)
|
|
elif s == "triangle":
|
|
return triangle_silk(conf)
|
|
elif s == "internal_pin1":
|
|
return internal_silk(conf) + pin1_silk(conf)
|
|
elif s == "external_pin1":
|
|
return external_silk(conf) + pin1_silk(conf)
|
|
else:
|
|
return []
|
|
|
|
|
|
def ctyd(conf):
|
|
"""Draw a courtyard around the part."""
|
|
gap = conf.get('courtyard_gap', ctyd_gap)
|
|
|
|
# Compute width and height of courtyard
|
|
width = max(conf['pad_shape'][0] + conf['pitch'], conf['chip_shape'][0])
|
|
height = max(conf['pad_shape'][1], conf['chip_shape'][1])
|
|
|
|
width += 2 * gap
|
|
height += 2 * gap
|
|
|
|
# Ensure courtyard lies on a specified grid
|
|
# (double the grid since we halve the width/height)
|
|
grid = 2*ctyd_grid
|
|
width = grid * int(math.ceil(width / (2*ctyd_grid)))
|
|
height = grid * int(math.ceil(height / (2*ctyd_grid)))
|
|
|
|
# Render courtyard
|
|
_, _, _, _, sq = draw_square(width, height, (0, 0), "F.CrtYd", ctyd_width)
|
|
return sq
|
|
|
|
|
|
def pads(conf):
|
|
"""Place the part pads."""
|
|
out = []
|
|
x = conf['pitch'] / 2.0
|
|
layers = ["F.Cu", "F.Mask", "F.Paste"]
|
|
out.append(pad(1, "smd", "rect", (-x, 0), conf['pad_shape'], layers))
|
|
out.append(pad(2, "smd", "rect", (+x, 0), conf['pad_shape'], layers))
|
|
return out
|
|
|
|
|
|
def _3d(conf):
|
|
"""Add 3d model."""
|
|
if "model" in conf:
|
|
return [model(**conf['model'])]
|
|
else:
|
|
return []
|
|
|
|
|
|
def footprint(conf):
|
|
tedit = format(int(time.time()), 'X')
|
|
sexp = ["module", conf['name'], ("layer", "F.Cu"), ("tedit", tedit)]
|
|
sexp += refs(conf)
|
|
sexp += fab(conf)
|
|
sexp += silk(conf)
|
|
sexp += ctyd(conf)
|
|
sexp += pads(conf)
|
|
sexp += _3d(conf)
|
|
return sexp_generate(sexp)
|
|
|
|
|
|
def load_items(modpath):
|
|
config = {}
|
|
for dirpath, dirnames, files in os.walk(modpath):
|
|
dirnames.sort()
|
|
files.sort()
|
|
for fn in fnmatch.filter(files, "*.yaml"):
|
|
path = os.path.join(dirpath, fn)
|
|
with open(path) as f:
|
|
item = yaml.safe_load(f)
|
|
item["path"] = dirpath
|
|
config[item["name"]] = item
|
|
return config
|
|
|
|
|
|
def main(prettypath, modpath, verify=False, verbose=False):
|
|
config = load_items(modpath)
|
|
for name, conf in config.items():
|
|
# Generate footprint
|
|
conf['name'] = name
|
|
fp = footprint(conf)
|
|
path = os.path.join(prettypath, name+".kicad_mod")
|
|
|
|
if verify and verbose:
|
|
print("Verifying", path)
|
|
|
|
# Check if an identical part already exists
|
|
if os.path.isfile(path):
|
|
with open(path) as f:
|
|
old = f.read()
|
|
old = [n for n in sexp_parse(old) if n[0] != "tedit"]
|
|
new = [n for n in sexp_parse(fp) if n[0] != "tedit"]
|
|
if new == old:
|
|
continue
|
|
|
|
# If not, either verification failed or we should output the new fp
|
|
if verify:
|
|
return False
|
|
else:
|
|
with open(path, "w") as f:
|
|
f.write(fp)
|
|
|
|
# If we finished and didn't return yet, verification has succeeded.
|
|
if verify:
|
|
return True
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("prettypath", type=str, help=
|
|
"Path to footprints to process")
|
|
parser.add_argument("modpath", type=str, help=
|
|
"Path to .yaml files defining footprints")
|
|
parser.add_argument("--verify", action="store_true", help=
|
|
"Verify libraries are up to date")
|
|
parser.add_argument("--verbose", action="store_true", help=
|
|
"Print out every library verified")
|
|
args = vars(parser.parse_args())
|
|
result = main(**args)
|
|
if args['verify']:
|
|
if result:
|
|
print("OK: all footprints up-to-date.")
|
|
sys.exit(0)
|
|
else:
|
|
print("Error: footprints not up-to-date.", file=sys.stderr)
|
|
sys.exit(1)
|