from copy import deepcopy 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_str = lines[3].split(":")[-1].strip() z_offset = eval(z_str) 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) > 1: 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])) disable_model = False if len(foot_sp) > 2: disable_model = eval(foot_sp[2]) new_foots[ref] = [foot, offset, disable_model] # 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()) elif "F.Courtyard" 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()) > 0: 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]) # Disable model if applicable if vals[2]: load_foot.Models().clear() # 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]) print("Added: ", load_foot) pcb.Add(load_foot) # # Sort by Y # sorted_foots.sort(key=lambda foot: foot[0].GetY()) # Export the step file # Export step file of board with no additional components 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.stp " + 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.stp" 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_stp = sys.path[0] + "/../../libs/melon3d/" + foot_path + ".3dshapes/" + foot_name + ".stp" 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_stp + " 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 courtyard # for [foot, pad_map] in sorted_foots: # 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) keepouts = [] pad_cnt = 0 for [foot, pad_map] in sorted_foots: local_box = foot.GetBoundingBox(False,False) local_cent = ToMM(local_box.GetCenter() - brd_cent) local_size = ToMM(local_box.GetSize()) print("Bounding: ", local_cent, local_size) keepouts.append([local_cent, local_size]) 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 # size = foot.GetBoundingBox(False,False).GetSize() # cent = foot.GetCenter() - brd_cent # x_flag = abs(cent.x) - brd_width/2 > -size.x/2 # y_flag = abs(cent.y) - brd_height/2 > -size.y/2 # if x_flag or y_flag: # shape_type = foot.GetEffectiveShape() # cent_mm = ToMM(cent) # size_mm = ToMM(size) # start_x = brd_width/2 # start_y = brd_height/2 # if cent.x < 0: # start_x = -1*start_x # if cent.y < 0: # start_y = -1*start_y # if not x_flag: # start_x = cent_mm[0] # if not y_flag: # start_y = cent_mm[1] # print("Silk: ", cent_mm, size_mm) # if shape_type == pcbnew.SHAPE_T_RECT: # start = [start_x, start_y + size_mm[1]/2] # end = [start[0] + size_mm[0]/2, start[1]] # kicad_mod.append(Line(start=start, end=end, layer='F.Silkscreen', width=0.05)) # start = [end[0], end[1]] # end = [start[0], start[1] - size[1]] # kicad_mod.append(Line(start=start, end=end, layer='F.Silkscreen', width=0.05)) # start = [end[0], end[1]] # end = [start[0] - size[0]/2, start[1]] # kicad_mod.append(Line(start=start, end=end, layer='F.Silkscreen', width=0.05)) # start = [cent_mm[0] - size_mm[0], cent_mm[1] - size_mm[1]] # end = [cent_mm[0] + size_mm[0], cent_mm[1] + size_mm[1]] # kicad_mod.append(RectLine(start=start, end=end, layer='F.Silkscreen', width=0.05, offset=0.2)) # elif shape_type == pcbnew.SHAPE_T_CIRCLE: # start = [cent_mm[0], cent_mm[1] + size_mm[1]/2] # end = [start[0], start[1]] # radius = size.x/2 # kicad_mod.append(Arc(center=cent, start=radius, layer='F.Silkscreen', width=0.05)) # radius = size_mm[0]/2 + 0.2 # kicad_mod.append(Circle(center=cent_mm, radius=radius, layer='F.Silkscreen', width=0.05)) for d in foot.GraphicalItems(): print("Type: ", type(d), "Layer: ", d.GetLayerName()) if type(d) is pcbnew.PCB_SHAPE and "Courtyard" in d.GetLayerName(): shape_type = d.GetShape() cent = d.GetCenter() - brd_cent print("Courtyard: ", cent, shape_type) if shape_type == pcbnew.SHAPE_T_SEGMENT: start = d.GetStart() - brd_cent end = d.GetEnd() - brd_cent print("Seg: ", ToMM(start), ToMM(end)) kicad_mod.append(Line(start=ToMM(start), end=ToMM(end), layer='F.CrtYd', width=0.05)) elif shape_type == pcbnew.SHAPE_T_RECT: start = d.GetStart() - brd_cent end = d.GetEnd() - brd_cent print("Rect: ", ToMM(start), ToMM(end)) kicad_mod.append(RectLine(start=ToMM(start), end=ToMM(end), layer='F.CrtYd', width=0.05)) elif shape_type == pcbnew.SHAPE_T_CIRCLE: radius = ToMM(d.GetRadius()) cent = ToMM(cent) print("Circ: ", cent, radius) kicad_mod.append(Circle(center=cent, radius=radius, layer='F.CrtYd', width=0.05)) 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() offset = pad.GetOffset() 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(): if type(d) is pcbnew.PCB_TEXT and "User.Drawings" in d.GetLayerName(): txt_sp = d.GetText().split(':',1) if pad_number != txt_sp[0]: continue print("Item:", txt_sp[1].strip()) exec("tmp = " + txt_sp[1].strip()) primitives.append(tmp) # primitives.append(Arc(center=[0, 0], start=[-2.95, 0], width=1.5, angle=360, layer='F.Cu')) # 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]) # primitives = [Arc(center=[0, 0], start=[-2.95, 0], width=1.5, angle=360, layer='F.Cu')] print("offset: ", [ToMM(offset.x), ToMM(offset.y)]) final_pad = Pad(number=pad_number, type=pad_type, shape=pad_shape, at=list(cent), size=list(pad_size), drill=list(drill_size), offset=list(ToMM(offset)), layers=pad_layers, primitives=primitives) # for prim in primitives: # final_pad.addPrimitive(prim) kicad_mod.append(final_pad) real_pads += len(pad.GetNumber()) > 0 pad_cnt += real_pads # create silkscreen print("keepouts: ", keepouts) corners = [[-brd_width/2, -brd_height/2], [-brd_width/2, brd_height/2], [brd_width/2, brd_height/2], [brd_width/2, -brd_height/2]] for i in range(len(corners)): if i == len(corners)-1: j = 0 else: j = i+1 start = deepcopy(corners[i]) end = deepcopy(start) y_dir = False if corners[i][0] == corners[j][0]: y_dir = True sign = -1 if corners[j][y_dir] > corners[i][y_dir]: sign = 1 # Find intersections with keepouts intersects = [] for k in keepouts: if abs(start[not y_dir] - k[0][not y_dir]) < k[1][not y_dir]/2: intersects.append(k) intersects.sort(key=lambda k: abs(start[y_dir] - k[0][y_dir])) print("Start: ", start) print("end: ", corners[j]) print("Flag: ", y_dir) print("intersects: ", intersects) if len(intersects): intersect = intersects[0] skip_flag = abs(start[y_dir]) < abs(intersect[0][y_dir] - sign * intersect[1][y_dir]/2) print("Skip params: ", abs(start[y_dir]), abs(intersect[0][y_dir] - sign * intersect[1][y_dir]/2)) for intersect in intersects: end[y_dir] = intersect[0][y_dir] - sign * intersect[1][y_dir]/2 # if y_dir: # end[0] = intersect[0][0] - sign * intersect[1][0]/2 # else: # end[1] = intersect[0][1] - sign * intersect[1][1]/2 if not skip_flag: print("Silk: ", start, end) kicad_mod.append(Line(start=start, end=end, layer='F.SilkS', width=0.1)) skip_flag = False start[y_dir] = intersect[0][y_dir] + sign * intersect[1][y_dir]/2 # if y_dir: # start[0] = intersect[0][0] + sign * intersect[1][0]/2 # else: # start[1] = intersect[0][1] + sign * intersect[1][1]/2 print("End params: ", abs(start[y_dir]), abs(corners[j][y_dir])) if abs(start[y_dir]) <= abs(corners[j][y_dir]): print("End Fake: ", start, end) print("End: ", start, corners[j]) kicad_mod.append(Line(start=start, end=corners[j], layer='F.SilkS', width=0.1)) # kicad_mod.append(RectLine(start=[-brd_width/2,-brd_height/2], end=[brd_width/2,brd_height/2], layer='F.SilkS', width=0.15)) final_model_path = "${KIPRJMOD}/../libs/melon3d/" + foot_path + ".3dshapes/" + foot_name + ".stp" 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")