-
-
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.