1
0
mirror of https://github.com/whowechina/popn_pico.git synced 2025-03-03 16:43:49 +01:00
2022-08-22 21:51:48 +08:00

260 lines
8.0 KiB
Python

"""
draw_mod.py
Copyright 2015 Adam Greig
Licensed under the MIT licence, see LICENSE file for details.
Render a .kicad_mod file to a PNG.
"""
from __future__ import print_function, division
import sys
import math
import cairo
from sexp import parse as sexp_parse
# Settings ====================================================================
# Gap between courtyard and image edge, in fraction of axis length
border_ratio = 0.02
# Image size in pixels
image_size = 512
# Background colour
bg_colour = (1, 1, 1, 1)
# Drill/hole colour
drill_colour = (0, 1, 1, .7)
# Colour mapping
colours = {
"F.CrtYd": (0, 0, 0, 1),
"F.SilkS": (1, 0, 1, 0.8),
"F.Fab": (0, 0, 1, 0.8),
"F.Cu": (1, 0, 0, 1),
"F.Mask": (0.5, 0, 1, 0.6),
"F.Paste": (0.6, 0.6, 0.6, 1),
}
# Layer stack (bottom first)
layer_stack = [
"F.Cu", "F.Mask", "F.Paste", "F.SilkS", "F.Fab", "F.CrtYd", "Drill"]
# End Settings ================================================================
def find_size(mod):
"""
Use the courtyard and a little padding to determine the footprint extent.
"""
left = right = top = bottom = 0
for line in (n for n in mod if n[0] == "fp_line"):
layer = [n for n in line if n[0] == "layer"][0]
if layer[1] in ("F.CrtYd", "B.CrtYd"):
start = [n for n in line if n[0] == "start"][0]
end = [n for n in line if n[0] == "end"][0]
for x, y in (start[1:], end[1:]):
x = float(x)
y = float(y)
left = min(x, left)
right = max(x, right)
top = min(y, top)
bottom = max(y, bottom)
width = right - left
height = bottom - top
left -= width * border_ratio
right += width * border_ratio
top -= height * border_ratio
bottom += height * border_ratio
return left, right, top, bottom
def draw_line(ctxs, draw):
layer = [n for n in draw if n[0] == "layer"][0][1]
if layer in ctxs:
ctx = ctxs[layer]
rgba = colours[layer]
width = [n for n in draw if n[0] == "width"][0][1]
ctx.set_source_rgba(*rgba)
ctx.set_line_width(float(width))
ctx.set_line_cap(cairo.LINE_CAP_ROUND)
if draw[0] == "fp_line":
start = [n for n in draw if n[0] == "start"][0]
end = [n for n in draw if n[0] == "end"][0]
ctx.move_to(float(start[1]), float(start[2]))
ctx.line_to(float(end[1]), float(end[2]))
elif draw[0] == "fp_circle":
center = [n for n in draw if n[0] == "center"][0]
end = [n for n in draw if n[0] == "end"][0]
dx = float(end[1]) - float(center[1])
dy = float(end[2]) - float(center[2])
r = math.sqrt(dx**2 + dy**2)
ctx.new_sub_path()
ctx.arc(float(center[1]), float(center[2]), r, 0, 2*math.pi)
elif draw[0] == "fp_arc":
start = [n for n in draw if n[0] == "start"][0]
end = [n for n in draw if n[0] == "end"][0]
angle = [n for n in draw if n[0] == "angle"][0]
dx = float(end[1]) - float(start[1])
dy = float(end[2]) - float(start[2])
r = math.sqrt(dx**2 + dy**2)
a_start = math.atan2(dy, dx)
a_end = a_start + float(angle[1]) * (math.pi / 180.0)
ctx.new_sub_path()
ctx.arc(float(start[1]), float(start[2]), r, a_start, a_end)
ctx.stroke()
def hatch(positive, rgba):
hs = 64
hatch = cairo.ImageSurface(cairo.FORMAT_ARGB32, hs, hs)
hctx = cairo.Context(hatch)
if positive:
hctx.move_to(0, 0)
hctx.line_to(hs, hs)
else:
hctx.move_to(0, hs)
hctx.line_to(hs, 0)
hctx.set_line_width(16)
hctx.set_source_rgba(*rgba)
hctx.stroke()
hpat = cairo.SurfacePattern(hatch)
hpat.set_extend(cairo.EXTEND_REPEAT)
hpat.set_matrix(cairo.Matrix(xx=image_size, yy=image_size))
return hpat
hatch_mask = hatch(True, colours["F.Mask"])
hatch_paste = hatch(False, colours["F.Paste"])
def pad_all_layers_front(layers):
for idx, layer in enumerate(layers):
if layer[0] == "*":
layers[idx] = "F" + layer[1:]
def pad_drill(drill, centre, ctx):
try:
drill_size = float(drill[0][1])
except ValueError:
pass
else:
ctx.arc(centre[0], centre[1], drill_size/2.0, 0, 2*math.pi)
ctx.set_source_rgba(*drill_colour)
ctx.fill()
offset = [n for n in drill[0] if n[0] == "offset"]
if offset:
centre[0] += float(offset[0][1])
centre[1] += float(offset[0][2])
def pad_margins(pad):
mask_margin = [n for n in pad if n[0] == "solder_mask_margin"]
paste_margin = [n for n in pad if n[0] == "solder_paste_margin"]
paste_ratio = [n for n in pad if n[0] == "solder_paste_ratio"]
mask_margin = float(mask_margin[0][1]) if mask_margin else 0
paste_margin = float(paste_margin[0][1]) if paste_margin else 0
paste_ratio = float(paste_ratio[0][1]) if paste_ratio else 0
return mask_margin, paste_margin, paste_ratio
def draw_pad(ctxs, pad):
shape = pad[3]
layers = [n for n in pad if n[0] == "layers"][0][1:]
pad_all_layers_front(layers)
centre = [float(v) for v in [n for n in pad if n[0] == "at"][0][1:]]
size = [float(v) for v in [n for n in pad if n[0] == "size"][0][1:]]
drill = [n for n in pad if n[0] == "drill"]
if drill:
pad_drill(drill, centre, ctxs['Drill'])
mask_margin, paste_margin, paste_ratio = pad_margins(pad)
for layer in ["F.Cu", "F.Mask", "F.Paste"]:
if layer in layers and layer in ctxs:
ctx = ctxs[layer]
rgba = colours[layer]
if layer.endswith("Mask"):
size[0] += 2*mask_margin
size[1] += 2*mask_margin
ctx.set_source(hatch_mask)
elif layer.endswith("Paste"):
size[0] += 2*paste_margin
size[1] += 2*paste_margin
size[0] += 2*paste_ratio*size[0]
size[1] += 2*paste_ratio*size[1]
ctx.set_source(hatch_paste)
elif layer.endswith("Cu"):
ctx.set_source_rgba(*rgba)
if shape == "rect":
x = centre[0] - size[0]/2.0
y = centre[1] - size[1]/2.0
ctx.rectangle(x, y, size[0], size[1])
elif shape == "circle":
ctx.arc(centre[0], centre[1], size[0]/2.0, 0, 2*math.pi)
else:
return
ctx.fill()
def draw(mod):
surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, image_size, image_size)
ctx = cairo.Context(surf)
ctx.set_source_rgba(*bg_colour)
ctx.paint()
# Set up the context to map footprint coordinates to image coordinates
# Footprint is in mm, with the origin at the centre, x increasing right,
# and y increasing down.
# Image is in pixels, with the origin at the top left, x increasing right,
# and y increasing down.
left, right, top, bottom = find_size(mod)
length = float(max(right - left, bottom - top))
ctxs = {}
for layer in layer_stack:
lsurf = cairo.ImageSurface(cairo.FORMAT_ARGB32, image_size, image_size)
lctx = cairo.Context(lsurf)
lctx.scale(image_size/length, image_size/length)
lctx.translate(right, bottom)
ctxs[layer] = lctx
for pad in (n for n in mod if n[0] == "pad"):
draw_pad(ctxs, pad)
draw_types = ("fp_line", "fp_circle", "fp_arc")
for draw in (n for n in mod if n[0] in draw_types):
draw_line(ctxs, draw)
for layer in layer_stack:
lctx = ctxs[layer]
ctx.set_source_surface(lctx.get_target())
ctx.paint()
return surf
def main(modpath, outpath):
with open(modpath) as f:
sexp = sexp_parse(f.read())
img = draw(sexp)
img.write_to_png(outpath)
if __name__ == "__main__":
if len(sys.argv) == 3:
modpath = sys.argv[1]
outpath = sys.argv[2]
main(modpath, outpath)
else:
print("Usage: {} <.kicad_mod file> <output file>".format(sys.argv[0]))
sys.exit(0)