1
0
mirror of https://github.com/whowechina/popn_pico.git synced 2025-03-03 16:43:49 +01:00
popn_pico/PCB/agg-kicad/scripts/build_lib_ic.py
2022-08-22 21:51:48 +08:00

314 lines
9.9 KiB
Python

"""
build_lib_ic.py
Copyright 2016 Adam Greig
Licensed under the MIT licence, see LICENSE file for details.
Generate symbols for generic black-box ICs etc.
Symbols configuration:
Each symbol is defined by a .yaml file in the same path that the .lib file
should be placed. Each file contains the following keys:
designator: optional, default "IC", the default reference designator
footprint: optional, an associated footprint to autofill
datasheet: optional, a URL or path to a datasheet
ordercodes: optional, list of (supplier, code) for supplier order codes
description: description of the part, placed in the .dcm file
pins: list of lists of left and right pin groups
(blocks of related pins with a space in-between).
Each group contains a list of tuples of:
(pin name, pin number, electrical type).
Number and name may be given as a string or an integer.
Electrical type must be a string out of:
in, out, bidi, tri, passive, unspec, pwrin, pwrout,
oc, od, oe, os, nc.
These correspond to input, output, bidirectional, tristate, passive,
unspecified, power_input, power_output, open_collector,
open_emitter, and not_connected. They should be given as strings.
"""
from __future__ import print_function, division
import os
import sys
import yaml
import fnmatch
import argparse
pin_types = {
"in": "I",
"out": "O",
"bidi": "B",
"tri": "T",
"passive": "P",
"unspec": "U",
"pwrin": "W",
"pwrout": "w",
"oc": "C",
"od": "C",
"oe": "E",
"os": "E",
"nc": "N",
}
def longest_num(units):
return max(max(
max([0] + [max(len(str(p[1])) for p in grp) for grp in left_pins]),
max([0] + [max(len(str(p[1])) for p in grp) for grp in right_pins]))
for (left_pins, right_pins) in units)
def geometry(unit, longest_num):
left_pins, right_pins = unit
length = max(100, longest_num * 50)
# Find longest name of all pins
longest_name = max(
max([0] + [max(len(p[0]) for p in grp) for grp in left_pins]),
max([0] + [max(len(p[0]) for p in grp) for grp in right_pins]))
# Width is either that required for longest name or twice that for
# dual-sided parts, rounded up to nearest 100. If length is not a
# multiple of 100, add extra width to ensure pins are on 0.1" grid.
width = (longest_name + 1) * 50
width += width % 100
if left_pins and right_pins:
width *= 2
if ((width//2)+length) % 100 != 0:
width += 2 * (((width//2)+length) % 100)
# Height is maximum required between each side
n_left_pins = sum(len(grp) for grp in left_pins)
n_right_pins = sum(len(grp) for grp in right_pins)
n_left_groups = len(left_pins)
n_right_groups = len(right_pins)
height = 100 * max(
n_left_pins + n_left_groups - 1, n_right_pins + n_right_groups - 1)
# Ensure height is an odd multiple of 0.1" to keep everything aligned
# to the 0.1" grid. This is responsible for the unseemly gaps at the
# bottom of parts with an even number of pins, but preserves symmetry.
if (height // 100) % 2 == 0:
height += 100
return width, height, length
def normalise_pins(pins):
"""
Convert YAML representation of pins into a normal structure, which is
a list of (left, right) tuples, where each tuple is a symbol unit,
and left and right are either empty lists, or lists of groups,
where each group is a list of [name, number, type] pins.
"""
output = []
# Get what might be either the first pin or the first group of pins,
# depending on whether the list is 3 deep (one unit) or 4 (multiple units)
first_pin_or_grp = pins[0][0][0]
if first_pin_or_grp is None:
# For right-hand-only parts, we might need to check the second entry
first_pin_or_grp = pins[1][0][0]
if isinstance(first_pin_or_grp[0], str):
# Str means a name, so this is a pin, so there's only
# one unit, so wrap in a new list.
pins = [pins]
for unit in pins:
if len(unit) == 1:
# Only one side: left groups only
output.append((unit[0], []))
elif len(unit) == 2:
if unit[0][0][0] is None:
# Empty left side: right groups only
output.append(([], unit[1]))
else:
# Both sides
output.append((unit[0], unit[1]))
else:
raise ValueError("Invalid pins")
return output
def fields(conf, units):
n = longest_num(units)
geoms = [geometry(unit, n) for unit in units]
width = max(g[0] for g in geoms)
height = max(g[1] for g in geoms)
field_x = -width//2
field_y = height//2 + 50
out = []
# Designator at top
out.append("F0 \"{}\" {} {} 50 H V L CNN".format(
conf.get('designator', 'IC'), field_x, field_y))
# Value/name at bottom
out.append("F1 \"{}\" {} {} 50 H V L CNN".format(
conf['name'], field_x, -field_y))
# Either specify a footprint or just set its size, position, invisibility
if "footprint" in conf:
out.append("F2 \"{}\" {} {} 50 H I L CNN".format(
conf['footprint'], field_x, -field_y-100))
else:
out.append("F2 \"\" {} {} 50 H I L CNN".format(field_x, -field_y-100))
# Specify a datasheet if given
if "datasheet" in conf:
out.append("F3 \"{}\" {} {} 50 H I L CNN".format(
conf['datasheet'], field_x, -field_y-200))
else:
out.append("F3 \"\" {} {} 50 H I L CNN".format(field_x, -field_y-200))
# Order codes
for idx, (supplier, code) in enumerate(conf.get("ordercodes", [])):
out.append("F{} \"{}\" {} {} 50 H I L CNN \"{}\"".format(
idx+4, code, field_x, -field_y-(300+idx*100), supplier))
return out
def draw_pins(groups, x0, y0, direction, length, unit_idx):
out = []
pin_x = x0
pin_y = y0
for group in groups:
for (name, num, t) in group:
out.append("X {} {} {} {} {} {} 50 50 {} 0 {}".format(
name, num, pin_x, pin_y, length, direction, unit_idx,
pin_types[t]))
pin_y -= 100
pin_y -= 100
return out
def draw(units):
out = []
out.append("DRAW")
n = longest_num(units)
for unit_idx, unit in enumerate(units):
if len(units) > 1:
# For multi-unit parts, unit indices start at 1,
# while for single-unit parts, everythign is unit 0.
unit_idx += 1
width, height, length = geometry(unit, n)
# Containing box
out.append("S {} {} {} {} {} 1 0 f".format(
-width//2, height//2, width//2, -height//2, unit_idx))
# Pins
x0 = -width//2 - length
y0 = height//2 - 50
left_pins, right_pins = unit
if left_pins:
out += draw_pins(left_pins, x0, y0, "R", length, unit_idx)
if right_pins:
out += draw_pins(right_pins, -x0, y0, "L", length, unit_idx)
out.append("ENDDRAW")
return out
def library(conf):
out = []
units = normalise_pins(conf['pins'])
out.append("EESchema-LIBRARY Version 2.3")
out.append("#encoding utf-8")
out.append("#\n# {}\n#".format(conf['name']))
locked = "F" if len(units) == 1 else "L"
out.append("DEF {} {} 0 40 Y Y {} {} N".format(
conf['name'], conf.get('designator', 'IC'), len(units), locked))
out += fields(conf, units)
out += draw(units)
out.append("ENDDEF\n#\n#End Library\n")
return "\n".join(out)
def documentation(conf):
out = []
out.append("EESchema-DOCLIB Version 2.0")
out.append("$CMP {}".format(conf['name']))
out.append("D {}".format(conf['description']))
if "datasheet" in conf:
out.append("F {}".format(conf['datasheet']))
out.append("$ENDCMP\n")
return "\n".join(out)
def load_items(libpath):
config = {}
for dirpath, dirnames, files in os.walk(libpath):
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(libpath, verify=False, verbose=False):
config = load_items(libpath)
for name, conf in config.items():
conf['name'] = name
path = os.path.join(conf.get("path", ""), name.lower()+".lib")
dcmpath = os.path.splitext(path)[0] + ".dcm"
lib = library(conf)
dcm = documentation(conf)
if verify and verbose:
print("Verifying", path)
# Check if anything has changed
if os.path.isfile(path):
with open(path) as f:
oldlib = f.read()
if os.path.isfile(dcmpath):
with open(dcmpath) as f:
olddcm = f.read()
if lib == oldlib and dcm == olddcm:
continue
# If so, either verification failed or write the new files
if verify:
return False
else:
with open(path, "w") as f:
f.write(lib)
with open(dcmpath, "w") as f:
f.write(dcm)
# If we finished and didn't return yet, verification has succeeded
if verify:
return True
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("libpath", type=str, help=
"Path to libraries to process")
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 libs up-to-date.")
sys.exit(0)
else:
print("Error: libs not up-to-date.", file=sys.stderr)
sys.exit(1)