Files
kicad-ci/.scripts/foot_gen.py

290 lines
9.7 KiB
Python

import sys
import os
import pcbnew
from pcbnew import FOOTPRINT, FP_3DMODEL, VECTOR3D, FootprintLoad, FromMM, ToMM, LoadBoard, SaveBoard, PCB_TEXT
filename = sys.argv[1]
os.mkdir("test")
pcb = LoadBoard(filename)
box = pcb.GetBoardEdgesBoundingBox()
brd_cent = box.GetCenter()
brd_width = ToMM(box.GetWidth())
brd_height = ToMM(box.GetHeight())
print("Box: ", box)
print("W: ", brd_width, "H: ", brd_height)
# layers = pcb.GetEnabledLayers()
top_layer_id = pcb.GetLayerID("F.Cu")
new_foots = {}
brd_drawings = []
foot_name = ""
foot_path = ""
z_offset = 0
for item in pcb.GetDrawings():
if type(item) is PCB_TEXT and "User.Drawings" in item.GetLayerName():
lines = str(item.GetText()).splitlines()
# Must have the correct header
if "Foot Pinout" not in lines[0]:
brd_drawings.append(item)
continue
if not lines[1].startswith("Name:"):
continue
foot_name = lines[1].split(":")[-1].strip()
if not lines[2].startswith("Path:"):
continue
foot_path = lines[2].split(":")[-1].strip()
if not lines[3].startswith("Z:"):
continue
z_offset = float(lines[3].split(":")[-1].strip())
for line in lines[4:]:
sp = line.split(':', 1)
if len(sp) != 2:
continue
# Path to substitue footprint
ref = sp[0]
foot_sp = sp[1].split(';')
foot = foot_sp[0].strip()
offset = (0,0)
# Optional offset tuple
if len(foot_sp) == 2:
print("Foot sp: ", foot_sp)
offset = foot_sp[1].strip(" ()").split(',')
print("Offset: ", offset)
offset[0] = FromMM(float(offset[0]))
offset[1] = FromMM(float(offset[1]))
new_foots[ref] = [foot, offset]
# pins = sp[1]
# pin_refs = []
# for pin in pins.split(','):
# pin_refs.push(int(pin))
break
elif "Edge.Cuts" in item.GetLayerName():
brd_drawings.append(item)
# print("Text: ", item.GetText())
# print("Layer: ", item.GetLayerName())
print("")
print("New Footprint List: ", new_foots)
if len(new_foots) == 0:
exit(0)
# Clear tracks
pcb.Tracks().clear()
# Clear zones
pcb.Zones().clear()
# Clear Drawings
pcb.Drawings().clear()
# Add back in the outline
for d in brd_drawings:
pcb.Drawings().append(d)
# Keep only required foots
saved = []
while len(pcb.Footprints()) > 1:
foot = pcb.Footprints().pop()
if foot.GetReference() in new_foots.keys():
saved.append(foot)
saved.sort(key=lambda foot: list(new_foots.keys()).index(foot.GetReference()))
# just in case
pcb.DeleteAllFootprints()
# Add them back
sorted_foots = []
for foot in saved:
vals = new_foots[foot.GetReference()]
# Change the footprint
path_split = vals[0].split(':')
folder = sys.path[0] + "/../../libs/melonlib/"+ path_split[0] + ".pretty"
load_foot = FootprintLoad(folder, path_split[1])
# foot.SetFPIDAsString(vals[0])
# Save the original postion of footprint + pads
orig_cent = foot.GetBoundingBox(False,False).Centre()
orig_orient = foot.GetOrientation()
pads = []
for pad in foot.Pads():
pads.append([pad.GetNumber(), pad.GetCenter(), pad.GetNet()])
# # Flip to other side
# foot.SetLayerAndFlip(top_layer_id)
# Put back in original position with an offset
load_foot.SetOrientation(orig_orient)
new_cent = load_foot.GetBoundingBox(False,False).Centre()
cent_diff = orig_cent - new_cent
load_foot.SetX(load_foot.GetX() + cent_diff.x + vals[1][0])
load_foot.SetY(load_foot.GetY() + cent_diff.y + vals[1][1])
# Flip the net assignments of the pads
pad_map = {}
for pad in load_foot.Pads():
if len(pad.GetNumber()) == 0:
continue
# Check position diff from old
# to new
new_cent = pad.GetCenter()
diffs = []
for [_, old_cent,_] in pads:
diffs.append(new_cent - old_cent)
min_diff = min(diffs)
# If pad is within 0.05mm it's a match
if (min_diff.x**2 + min_diff.y**2)**0.5 < 50000:
# Set the net to what the old pad was
i = diffs.index(min_diff)
match_num = pads[i][0]
match_pad = pads[i][2:]
pad.SetNet(match_pad[0])
pad_map[pad.GetNumber()] = match_num
elif "mounting" in str(foot.GetKeywords()).lower():
match_num = pads[0][0]
match_pad = pads[0][2:]
pad.SetNet(match_pad[0])
pad_map[pad.GetNumber()] = match_num
sorted_foots.append([load_foot, pad_map])
pcb.Add(load_foot)
# # Sort by Y
# sorted_foots.sort(key=lambda foot: foot[0].GetY())
# Export the step file
os.system("kicad-cli pcb export step -f --subst-models --user-origin " + str(ToMM(brd_cent.x)) + "x" + str(ToMM(brd_cent.y)) + "mm -o /tmp/dummy.step " + sys.argv[1])
# Import the 3d model of the actual PCB
dummy = FOOTPRINT(pcb)
dummy.SetPosition(brd_cent)
dummy_model = FP_3DMODEL()
dummy_model.m_Filename = "/tmp/dummy.step"
dummy_model.m_Offset = VECTOR3D(0.0, 0.0, z_offset)
dummy.Add3DModel(dummy_model)
pcb.Add(dummy)
# # Set the pcb thickness to 0
# des_sett = pcb.GetDesignSettings()
# stackup = des_sett.GetStackupDescriptor()
# Save and export step of board + mating connectors
SaveBoard("test/test.kicad_pcb", pcb)
final_model_path = sys.path[0] + "/../../libs/melon3d/" + foot_path + ".3dshapes/" + foot_name + ".step"
os.system("kicad-cli pcb export step -f --subst-models --user-origin " + str(ToMM(brd_cent.x)) + "x" + str(ToMM(brd_cent.y)) + "mm -o " + final_model_path + " test/test.kicad_pcb")
# Generate footprint
sys.path.append(os.path.join(sys.path[0],"../kicad-footprint-generator"))
from KicadModTree import *
from KicadModTree.nodes.specialized.PadArray import PadArray
# init kicad footprint
kicad_mod = Footprint(foot_name, FootprintType.SMD)
kicad_mod.setDescription("A example footprint")
kicad_mod.setTags("example")
# set general values
kicad_mod.append(Text(type='reference', text='REF**', at=[0,-3], layer='F.SilkS'))
kicad_mod.append(Text(type='value', text=foot_name, at=[1.5,3], layer='F.Fab'))
# create silscreen
kicad_mod.append(RectLine(start=[-brd_width/2,-brd_height/2], end=[brd_width/2,brd_height/2], layer='F.SilkS', width=0.15))
# create courtyard
kicad_mod.append(RectLine(start=[-brd_width/2,-brd_height/2], end=[brd_width/2,brd_height/2], layer='F.CrtYd', width=0.05, offset=0.5))
# create pads
print(sorted_foots)
pad_cnt = 0
for [foot, pad_map] in sorted_foots:
mounting_flag = "mounting" in str(foot.GetKeywords()).lower()
custom_paste_flag = False
if mounting_flag:
for pad in foot.Pads():
if pad.IsAperturePad():
custom_paste_flag = True
real_pads = 0
for pad in foot.Pads():
cent = ToMM(pad.GetCenter() - brd_cent)
pad_size = ToMM(pad.GetSize())
drill_size = ToMM(pad.GetDrillSize())
curr_pad_num = pad.GetNumber()
attr_type = pad.GetAttribute()
shape_type = pad.GetShape()
pad_type = Pad.TYPE_THT
pad_layers = Pad.LAYERS_THT
if attr_type == pcbnew.PAD_ATTRIB_SMD:
pad_type = Pad.TYPE_SMT
if custom_paste_flag:
pad_layers = ["F.Cu", "F.Mask"]
else:
pad_layers = Pad.LAYERS_SMT
elif attr_type == pcbnew.PAD_ATTRIB_NPTH:
pad_type = Pad.TYPE_NPTH
pad_layers = Pad.LAYERS_NPTH
elif attr_type == pcbnew.PAD_ATTRIB_CONN:
pad_type = Pad.TYPE_CONNECT
pad_layers = Pad.LAYERS_NPTH
if pad.IsAperturePad():
pad_layers = ["F.Paste"]
primitives = []
pad_number = pad.GetNumber()
pad_shape = Pad.SHAPE_RECT
if shape_type == pcbnew.PAD_SHAPE_CIRCLE:
pad_shape = Pad.SHAPE_CIRCLE
elif shape_type == pcbnew.PAD_SHAPE_OVAL:
pad_shape = Pad.SHAPE_OVAL
elif shape_type == pcbnew.PAD_SHAPE_CHAMFERED_RECT:
pad_shape = Pad.SHAPE_TRAPEZE
elif shape_type == pcbnew.PAD_SHAPE_ROUNDRECT:
pad_shape = Pad.SHAPE_ROUNDRECT
elif shape_type == pcbnew.PAD_SHAPE_CUSTOM:
pad_shape = Pad.SHAPE_CUSTOM
for d in foot.GraphicalItems():
print("Item: ", d)
if type(d) is PCB_TEXT and "User.Drawings" in d.GetLayerName():
txt_sp = d.GetText().split(':',1)
if pad_number != txt_sp[0]:
continue
primitives.append(exec(txt_sp[1]))
# print(dir(pad.GetPrimitives()))
# for prim in pad.GetPrimitives():
# shape = prim.GetEffectiveShape()
# print("Shape: ", shape)
if len(pad_number) > 0:
pad_number = pad_cnt + int(pad_map[curr_pad_num])
final_pad = Pad(number=pad_number, type=pad_type, shape=pad_shape, at=list(cent), size=list(pad_size), drill=list(drill_size), layers=pad_layers)
for prim in primitives:
final_pad.addPrimitive(prim)
kicad_mod.append(final_pad)
real_pads += len(pad.GetNumber()) > 0
pad_cnt += real_pads
final_model_path = "${KIPRJMOD}/../libs/melon3d/" + foot_path + ".3dshapes/" + foot_name + ".step"
kicad_mod.append(Model(filename=final_model_path
,at=[0,0,-1.6]
,scale=[1,1,1]
,rotate=[0,0,0]))
# write file
file_handler = KicadFileHandler(kicad_mod)
final_foot_path = sys.path[0] + "/../../libs/melonlib/" + foot_path + ".pretty/" + foot_name + ".kicad_mod"
file_handler.writeFile(final_foot_path)
import shutil
shutil.rmtree("test")