-
-
Save aster94/bd52972ab6dbf13a44fc046b4222f7e7 to your computer and use it in GitHub Desktop.
| import pcbnew | |
| import os | |
| import shutil | |
| import subprocess | |
| # SETTINGS: | |
| # Gerber | |
| # Drill | |
| 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 | |
| def generate_gerbers(pcb, path): | |
| plot_controller = pcbnew.PLOT_CONTROLLER(pcb) | |
| plot_options = plot_controller.GetPlotOptions() | |
| # Set General Options: | |
| plot_options.SetOutputDirectory(path) | |
| plot_options.SetPlotFrameRef(False) | |
| plot_options.SetPlotValue(True) | |
| plot_options.SetPlotReference(True) | |
| plot_options.SetPlotInvisibleText(True) | |
| plot_options.SetPlotViaOnMaskLayer(True) | |
| plot_options.SetExcludeEdgeLayer(False) | |
| #plot_options.SetPlotPadsOnSilkLayer(PLOT_PADS_ON_SILK_LAYER) | |
| #plot_options.SetUseAuxOrigin(PLOT_USE_AUX_ORIGIN) | |
| plot_options.SetMirror(False) | |
| #plot_options.SetNegative(PLOT_NEGATIVE) | |
| #plot_options.SetDrillMarksType(PLOT_DRILL_MARKS_TYPE) | |
| #plot_options.SetScale(PLOT_SCALE) | |
| plot_options.SetAutoScale(True) | |
| #plot_options.SetPlotMode(PLOT_MODE) | |
| #plot_options.SetLineWidth(pcbnew.FromMM(PLOT_LINE_WIDTH)) | |
| # Set Gerber Options | |
| #plot_options.SetUseGerberAttributes(GERBER_USE_GERBER_ATTRIBUTES) | |
| #plot_options.SetUseGerberProtelExtensions(GERBER_USE_GERBER_PROTEL_EXTENSIONS) | |
| #plot_options.SetCreateGerberJobFile(GERBER_CREATE_GERBER_JOB_FILE) | |
| #plot_options.SetSubtractMaskFromSilk(GERBER_SUBTRACT_MASK_FROM_SILK) | |
| #plot_options.SetIncludeGerberNetlistInfo(GERBER_INCLUDE_GERBER_NETLIST_INFO) | |
| plot_plan = [ | |
| ( 'F.Cu', pcbnew.F_Cu, 'Front Copper' ), | |
| ( 'B.Cu', pcbnew.B_Cu, 'Back Copper' ), | |
| ( 'F.Paste', pcbnew.F_Paste, 'Front Paste' ), | |
| ( 'B.Paste', pcbnew.B_Paste, 'Back Paste' ), | |
| ( 'F.SilkS', pcbnew.F_SilkS, 'Front SilkScreen' ), | |
| ( 'B.SilkS', pcbnew.B_SilkS, 'Back SilkScreen' ), | |
| ( 'F.Mask', pcbnew.F_Mask, 'Front Mask' ), | |
| ( 'B.Mask', pcbnew.B_Mask, 'Back Mask' ), | |
| ( 'Edge.Cuts', pcbnew.Edge_Cuts, 'Edges' ), | |
| ( 'Eco1.User', pcbnew.Eco1_User, 'Eco1 User' ), | |
| ( 'Eco2.User', pcbnew.Eco2_User, 'Eco1 User' ), | |
| ] | |
| for layer_info in plot_plan: | |
| plot_controller.SetLayer(layer_info[1]) | |
| plot_controller.OpenPlotfile(layer_info[0], pcbnew.PLOT_FORMAT_GERBER, layer_info[2]) | |
| plot_controller.PlotLayer() | |
| plot_controller.ClosePlot() | |
| def detect_blind_buried_or_micro_vias(pcb): | |
| through_vias = 0 | |
| micro_vias = 0 | |
| blind_or_buried_vias = 0 | |
| for track in pcb.GetTracks(): | |
| if track.Type() != pcbnew.PCB_VIA_T: | |
| continue | |
| if track.GetShape() == pcbnew.VIA_THROUGH: | |
| through_vias += 1 | |
| elif track.GetShape() == pcbnew.VIA_MICROVIA: | |
| micro_vias += 1 | |
| elif track.GetShape() == pcbnew.VIA_BLIND_BURIED: | |
| blind_or_buried_vias += 1 | |
| if micro_vias or blind_or_buried_vias: | |
| return True | |
| else: | |
| return False | |
| def generate_drill_file(pcb, path): | |
| #if detect_blind_buried_or_micro_vias(pcb): | |
| # return | |
| 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(path, DRILL_FILE, MAP_FILE, REPORTER) | |
| class SimplePlugin(pcbnew.ActionPlugin): | |
| def defaults(self): | |
| self.name = 'Gerber Plot' | |
| self.category = 'Gerber' | |
| self.description = 'Generate Gerber files, drill holes, see the result and send to a compressed folder' | |
| self.show_toolbar_button = True | |
| self.icon_file_name = os.path.join(os.path.dirname(__file__), 'gerber_plot_icon.png') | |
| def Run(self): | |
| # The entry function of the plugin that is executed on user action | |
| try: | |
| cwd_path = os.getcwd() | |
| pcb = pcbnew.GetBoard() | |
| project_path, project_name = os.path.split(pcb.GetFileName()) | |
| project_name = os.path.splitext(project_name)[0] | |
| output_path = os.path.join(project_path, project_name + '-Gerber').replace('\\','/') | |
| tmp_path = os.path.join(project_path, 'tmp').replace('\\','/') | |
| log_file = os.path.join(project_path, 'log.txt').replace('\\','/') | |
| if os.path.exists(log_file): | |
| os.remove(log_file) | |
| except Exception as err: | |
| with open(log_file, 'a') as file: | |
| file.write('Startup error\nError:{}\n'.format(err)) | |
| # Create a temp folder | |
| try: | |
| os.mkdir(tmp_path) | |
| except Exception as err: | |
| with open(log_file, 'a') as file: | |
| file.write('tmp folder not created\nError:{}\n'.format(err)) | |
| # Generate Gerber and drill files | |
| try: | |
| generate_gerbers(pcb, tmp_path) | |
| except Exception as err: | |
| with open(log_file, 'a') as file: | |
| file.write('Gerbers not plotted\nError:{}\n'.format(err)) | |
| try: | |
| generate_drill_file(pcb, tmp_path) | |
| except Exception as err: | |
| with open(log_file, 'a') as file: | |
| file.write('Drill file not plotted\nError:{}\n'.format(err)) | |
| # Render an image: we need to call an external script that uses python 3 | |
| try: | |
| subprocess.check_call(['powershell','render_pcb', tmp_path, os.path.join(project_path, project_name + '.png').replace('\\','/')], shell=True) | |
| # if you don't wish to have it as a exe file you could use: | |
| #subprocess.check_call(['powershell', 'path_to_python3', 'path_to_render_pcb', tmp_path, os.path.join(project_path, project_name + '.png').replace('\\','/')], shell=True) | |
| except Exception as err: | |
| with open(log_file, 'a') as file: | |
| file.write('PCB not rendered\nError:{}\n'.format(err)) | |
| # Create compressed file from tmp | |
| try: | |
| os.chdir(tmp_path) | |
| shutil.make_archive(output_path, 'zip', tmp_path) | |
| os.chdir(cwd_path) | |
| except Exception as err: | |
| with open(log_file, 'a') as file: | |
| file.write('ZIP file not created\nError:{}\n'.format(err)) | |
| # Remove temp folder | |
| try: | |
| shutil.rmtree(tmp_path, ignore_errors=True) | |
| except Exception as err: | |
| with open(log_file, 'a') as file: | |
| file.write('temp folder not deleted\nError:{}\n'.format(err)) | |
| SimplePlugin().register() # Instantiate and register to Pcbnew |
| import os, shutil | |
| from gerber import common | |
| from gerber.layers import PCBLayer, DrillLayer | |
| from gerber.render import RenderSettings | |
| from gerber.render.cairo_backend import GerberCairoContext | |
| from PIL import Image | |
| import click | |
| # Render | |
| SCALE = 25 | |
| OFFSET = 20 | |
| @click.command() | |
| @click.argument('input_path') | |
| def render_pcb(input_path): | |
| """Render Gerber Files into a PNG Image | |
| INPUT_PATH - Could be a folder or a zip file containing the Gerber Files | |
| """ | |
| del_tmp_folder = False | |
| extract_dir = '' | |
| if os.path.isfile(input_path): | |
| if not input_path.endswith('.zip'): | |
| click.BadParameter('Wrong INPUT_PATH') # exit | |
| extract_dir = os.path.join(os.path.dirname(input_path), 'tmp') | |
| shutil.unpack_archive(input_path, extract_dir, 'zip') | |
| input_path = extract_dir | |
| del_tmp_folder = True | |
| output_path = os.path.join(os.path.dirname(input_path), 'pcb.png') | |
| img_front_path = os.path.join(input_path, 'front.png') | |
| img_bottom_path = os.path.join(input_path, 'bottom.png') | |
| for file in os.listdir(input_path): | |
| real_path = os.path.join(input_path, file) | |
| if not os.path.isfile(real_path): | |
| continue | |
| # Drill | |
| if file.endswith('.drl'): | |
| drill = DrillLayer(real_path, common.read(real_path)) | |
| # Front | |
| elif file.endswith('-F_Cu.gbr'): | |
| copper_front = PCBLayer(real_path, 'top', common.read(real_path)) | |
| elif file.endswith('-F_Mask.gbr'): | |
| mask_front = PCBLayer(real_path, 'topmask', common.read(real_path)) | |
| elif file.endswith('-F_SilkS.gbr'): | |
| silk_front = PCBLayer(real_path, 'topsilk', common.read(real_path)) | |
| # Bottom | |
| elif file.endswith('-B_Cu.gbr'): | |
| copper_bottom = PCBLayer(real_path, 'bottom', common.read(real_path)) | |
| elif file.endswith('-B_Mask.gbr'): | |
| mask_bottom = PCBLayer(real_path, 'bottommask', common.read(real_path)) | |
| elif file.endswith('-B_SilkS.gbr'): | |
| silk_bottom = PCBLayer(real_path, 'bottomsilk', common.read(real_path)) | |
| else: | |
| continue | |
| # Create a new drawing context | |
| ctx = GerberCairoContext(scale=SCALE) | |
| ctx.render_layer(copper_front) | |
| ctx.render_layer(mask_front) | |
| ctx.render_layer(silk_front) | |
| ctx.render_layer(drill) | |
| # Write png file | |
| ctx.dump(img_front_path) | |
| # Clear the drawing | |
| ctx.clear() | |
| # Render bottom layers | |
| ctx.render_layer(copper_bottom) | |
| ctx.render_layer(mask_bottom) | |
| ctx.render_layer(silk_bottom) | |
| ctx.render_layer(drill, settings=RenderSettings(mirror=True)) | |
| # Write png file | |
| ctx.dump(img_bottom_path) | |
| ctx.clear() | |
| # Concatenate | |
| front = Image.open(img_front_path) | |
| bottom = Image.open(img_bottom_path) | |
| render = Image.new('RGB', (front.width, front.height * 2 + OFFSET)) | |
| render.paste(front, (0, 0)) | |
| render.paste(bottom, (0, front.height + OFFSET)) | |
| render.save(output_path) | |
| render.show() | |
| if del_tmp_folder: | |
| shutil.rmtree(extract_dir, ignore_errors=True) | |
| if __name__ == "__main__": | |
| render_pcb() |
IMHO you should merge the two files and remove the kicad part, it will make life for future contributor easier
you just need to bring the function render_pcb in the main file and instead of this (https://gist.github.com/aster94/bd52972ab6dbf13a44fc046b4222f7e7#file-gerber_plot-py-L153) call the new function
I personally would not merge the files, because they do different, potentially independent usable things. So to keep it modular, having them separate makes sense.
For example, I just got to know about KiBot, which seems to be a much more sophisticated and further developed way to auto-generate Gerbers from KiCad files. If I see it right though, it does not do 2D rendering of Gerbers, so we could use KiBot for gerbers and your render_pcb.py for 2D rendering.
I see it can generate also BOM and pick and place files, very nice!
yeah!
It can also render in 2D, I saw
I did not yet get it to work for me. it is made up of quite a few sub-tools, and one needs to find out how to install each one first. I will try to help make the documentation more easy to get into. But the end result is really great! especially the interactive BOM.
ouhh thank you for that explanation!
I will add this as docu into the file.
That only seems to affect the gerber_plot.py when used as a plugin in KiCad though (which I did not try). When using from the command line, I ran both the files with my default python (3.8.5), and it works fine.
I left the plugin part in there anyway, even though we do not need it; I will add this comment of yours to that part of the code.