import pcbnew import os from datetime import datetime from pathlib import Path import zipfile __all__ = ["FabOutputs"] class FabOutputs(pcbnew.ActionPlugin): def defaults(self): self.name = "Generate Outputs" self.category = "Output Generation" self.description = "Generate Gerbers, BOM, Drawings, etc and add to pdf/zip" self.show_toolbar_button = True self.icon_file_name = os.path.join(os.path.dirname(__file__), "icon.png") def Run(self): # ================ # General Data # ================ now = datetime.now() pcb = pcbnew.GetBoard() path_cwd = Path.cwd() path_pcb = Path(pcb.GetFileName()) dir_pcb = path_pcb.parent dir_fab = dir_pcb / "FabricationOutputs" dir_asy = dir_pcb / "AssemblyOutputs" project_name = path_pcb.stem part_number = pcb.GetTitleBlock().GetTitle() rev = str.upper(pcb.GetTitleBlock().GetRevision()) suffix = "" if rev != "": suffix += f"_REV{rev}" suffix += f"_{now.strftime('%Y%m%d_%H%M%S')}" layer_count = pcb.GetDesignSettings().GetCopperLayerCount() layer_info = [ ("Front Paste", "gtp", pcbnew.F_Paste), ("Front Silkscreen", "gto", pcbnew.F_SilkS), ("Front Mask", "gts", pcbnew.F_Mask), ("Front Copper", "gtl", pcbnew.F_Cu), *[(f'Inner Layer {layer} Copper', f'g{layer}', layer) for layer in range(1, layer_count-1)], ('Back Copper', 'gbl', pcbnew.B_Cu), ('Back Mask', 'gbs', pcbnew.B_Mask), ('Back SilkScreen', 'gbo', pcbnew.B_SilkS), ('Back Paste', 'gbp', pcbnew.B_Paste), ('Edges Cuts', 'gm1', pcbnew.Edge_Cuts), ('Drill', 'drl', None), ] stackup = [ # [pcbnew layer, file extension, thickness, comment] [pcbnew.F_Paste, 'gtp', None, "SN63/PB37"], [pcbnew.F_SilkS, 'gto', None, "White"], [pcbnew.F_Mask, 'gts', 1, "Explicit mask material"], [None, None, None, "ENIG"], [pcbnew.F_Cu, 'gtl', 2.1, "copper roughness"], [None, None, 10, "Dielectric stuff"], [1, 'g1', 0.7, "Copper roughness"], [None, None, 24, "Dielectric stuff"], [None, None, 12, "Dielectric stuff"], [2, 'g2', 0.7, "Copper roughness"], [None, None, 10, "Dielectric stuff"], [pcbnew.B_Cu, 'gbl', 2.1, "copper roughness"], [None, None, None, "ENIG"], [pcbnew.B_Mask, 'gbs', 1, "Explicit mask material"], [pcbnew.B_SilkS, 'gbo', None, "White"], [pcbnew.B_Paste, 'gbp', None, "SN63/PB37"], ] board_features = { "core cap": True, "castellated": False, "plated board edge": False, "copper finish": "ENIG", "hard gold": False, "bevelled edge": False, "soldermask defined": None, # TODO: how do I want to determine this? } dir_fab.mkdir(parents=True, exist_ok=True) dir_asy.mkdir(parents=True, exist_ok=True) files_fab = [] files_asy = [] # ================ # Gerbers # ================ #### SETTINGS tent_vias = True trim_silkscreen = False plot_controller = pcbnew.PLOT_CONTROLLER(pcb) plot_options = plot_controller.GetPlotOptions() # Set General Options: # plot_options.Format() plot_options.SetOutputDirectory(dir_fab) plot_options.SetPlotFrameRef(False) plot_options.SetPlotValue(False) plot_options.SetPlotReference(True) plot_options.SetPlotInvisibleText(False) plot_options.SetPlotViaOnMaskLayer(not tent_vias) plot_options.SetExcludeEdgeLayer(True) plot_options.SetUseAuxOrigin(False) plot_options.SetMirror(False) plot_options.SetNegative(False) plot_options.SetScale(1) # plot_options.SetAutoScale(True) #plot_options.SetPlotMode(PLOT_MODE) #plot_options.SetLineWidth(pcbnew.FromMM(PLOT_LINE_WIDTH)) plot_options.SetUseGerberAttributes(True) plot_options.SetUseGerberProtelExtensions(False) plot_options.SetCreateGerberJobFile(False) plot_options.SetIncludeGerberNetlistInfo(False) plot_options.SetUseGerberX2format(True) # plot_options.SetDrillMarksType() plot_options.SetSubtractMaskFromSilk(trim_silkscreen) plot_plan = [ # ( layer ID, file extension, description) ( pcbnew.F_Paste, 'gtp', 'Front Paste' ), ( pcbnew.F_SilkS, 'gto', 'Front SilkScreen' ), ( pcbnew.F_Mask, 'gts', 'Front Mask' ), ( pcbnew.F_Cu, 'gtl', 'Front Copper' ), *[(layer, f'g{layer}', f'Inner Layer {layer} Copper') for layer in range(1, layer_count-1)], ( pcbnew.B_Cu, 'gbl', 'Back Copper' ), ( pcbnew.B_Mask, 'gbs', 'Back Mask' ), ( pcbnew.B_SilkS, 'gbo', 'Back SilkScreen' ), ( pcbnew.B_Paste, 'gbp', 'Back Paste' ), ( pcbnew.Edge_Cuts, 'gm1', 'Edges Cuts' ), ] for layer_info in plot_plan: plot_controller.SetLayer(layer_info[0]) plot_controller.OpenPlotfile('', pcbnew.PLOT_FORMAT_GERBER, layer_info[2]) plot_controller.PlotLayer() fname = f"{project_name}{suffix}.{layer_info[1]}" os.rename(dir_fab / f"{project_name}.gbr", dir_fab / fname) files_fab.append(fname) plot_controller.ClosePlot() # ================ # Drill Files # ================ METRIC = True ZERO_FORMAT = pcbnew.GENDRILL_WRITER_BASE.DECIMAL_FORMAT INTEGER_DIGITS = 3 MANTISSA_DIGITS = 3 MIRROR_Y_AXIS = False HEADER = True OFFSET = pcbnew.wxPoint(0,0) MERGE_PTH_NPTH = True DRILL_FILE = True MAP_FILE = False REPORTER = None drill_writer = pcbnew.EXCELLON_WRITER(pcb) drill_writer.SetFormat(METRIC, ZERO_FORMAT, INTEGER_DIGITS, MANTISSA_DIGITS) drill_writer.SetOptions(MIRROR_Y_AXIS, HEADER, OFFSET, MERGE_PTH_NPTH) drill_writer.CreateDrillandMapFilesSet(str(dir_fab), DRILL_FILE, MAP_FILE, REPORTER) fname = f"{project_name}{suffix}.drl" os.rename(dir_fab / f"{project_name}.drl", dir_fab / fname) files_fab.append(fname) # ================ # Drawing # ================ # TODO # ================ # BOM # ================ # TODO # ================ # Pick and Place # ================ # TODO # ================ # Fab Notes # ================ # fname = f"README_FABRICATION{suffix}.TXT" # with open(dir_fab / fname, "w") as f: # f.write(f"{project_name}-REV{rev}\n") # f.write(f"Layer Order\n") # # for layer in plot_plan: # files_fab.append(fname) # ================ # Assembly Notes # ================ # fname = f"README_ASSEMBLY{suffix}.TXT" # with open(dir_asy / fname, "w") as f: # f.write(f"{project_name}-REV{rev}\n") # files_asy.append(fname) # ================ # Zip # ================ with zipfile.ZipFile(dir_fab / f"{project_name}{suffix}_fabrication.zip", "w") as z: for fname in files_fab: z.write(dir_fab / fname, arcname=fname) with zipfile.ZipFile(dir_asy / f"{project_name}{suffix}_assembly.zip", "w") as z: for fname in files_fab: z.write(dir_fab / fname, arcname=Path("fabrication") / fname) for fname in files_asy: z.write(dir_asy / fname, arcname=fname) # dir_archive = dir_pcb / "Archive" # with zipfile.ZipFile(dir_archive / f"{project_name}{suffix}_archive.zip", "w") as z: # for fname in files_fab: # z.write(dir_fab / fname, arcname=Path("fabrication") / fname) # for fname in files_asy: # z.write(dir_asy / fname, arcname=Path("assembly") / fname) # # TODO: archive project here