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_outline = [] 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]: 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_outline.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_outline: 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) # 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_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 # print(dir(pad.GetPrimitives())) # for prim in pad.GetPrimitives(): # shape = prim.GetEffectiveShape() # print("Shape: ", shape) pad_number = "" if len(pad.GetNumber()) > 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")