Initial commit
positioning is still a bit broken
This commit is contained in:
commit
3195cfbb1e
8 changed files with 29130 additions and 0 deletions
394
Untitled_17-11-19_18-00-15.ipynb
Normal file
394
Untitled_17-11-19_18-00-15.ipynb
Normal file
File diff suppressed because one or more lines are too long
22
chibi_2024-Edge.Cuts.gbr
Normal file
22
chibi_2024-Edge.Cuts.gbr
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
G04 #@! TF.FileFunction,Profile,NP*
|
||||
%FSLAX46Y46*%
|
||||
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
|
||||
G04 Created by KiCad (PCBNEW 4.0.6) date Mon Sep 18 11:29:53 2017*
|
||||
%MOMM*%
|
||||
%LPD*%
|
||||
G01*
|
||||
G04 APERTURE LIST*
|
||||
%ADD10C,0.100000*%
|
||||
%ADD11C,0.150000*%
|
||||
G04 APERTURE END LIST*
|
||||
D10*
|
||||
D11*
|
||||
X149000000Y-43750000D02*
|
||||
X49000000Y-43750000D01*
|
||||
X149000000Y-121750000D02*
|
||||
X149000000Y-43750000D01*
|
||||
X49000000Y-121750000D02*
|
||||
X149000000Y-121750000D01*
|
||||
X49000000Y-43750000D02*
|
||||
X49000000Y-121750000D01*
|
||||
M02*
|
||||
22840
chibi_2024-F.SilkS.gbr
Normal file
22840
chibi_2024-F.SilkS.gbr
Normal file
File diff suppressed because it is too large
Load diff
185
gerbimg.py
Executable file
185
gerbimg.py
Executable file
|
|
@ -0,0 +1,185 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import zipfile
|
||||
import tempfile
|
||||
import os.path as path
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import math
|
||||
|
||||
import gerber
|
||||
from gerber.render import GerberCairoContext
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
def paste_image(target_gerber:str, outline_gerber:str, source_img:np.ndarray, extend_overlay_r_mil:float=12, extend_picture_r_mil:float=2):
|
||||
outline = gerber.loads(outline_gerber)
|
||||
(minx, maxx), (miny, maxy) = outline.bounds
|
||||
grbw, grbh = maxx - minx, maxy - miny
|
||||
|
||||
imgh, imgw = source_img.shape
|
||||
scale = math.ceil(max(imgw/grbw, imgh/grbh)) # scale is in dpi
|
||||
|
||||
target = gerber.loads(target_gerber)
|
||||
(tminx, tmaxx), (tminy, tmaxy) = target.bounds
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
img_file = path.join(tmpdir, 'target.png')
|
||||
|
||||
fg, bg = gerber.render.RenderSettings((1, 1, 1)), gerber.render.RenderSettings((0, 0, 0))
|
||||
ctx = GerberCairoContext(scale=scale)
|
||||
ctx.render_layer(target, settings=fg, bgsettings=bg)
|
||||
ctx.dump(img_file)
|
||||
|
||||
original_img = cv2.imread(img_file, cv2.IMREAD_GRAYSCALE)
|
||||
r = 1+2*max(1, int(extend_overlay_r_mil/1000 * scale))
|
||||
target_img = cv2.blur(original_img, (r, r))
|
||||
_, target_img = cv2.threshold(target_img, 255//(1+r), 255, cv2.THRESH_BINARY)
|
||||
|
||||
qr = 1+2*max(1, int(extend_picture_r_mil/1000 * scale))
|
||||
# source_img = cv2.blur(source_img, (r, r))
|
||||
# cv2.imwrite('/tmp/03blurred.png', source_img)
|
||||
source_img = source_img[::-1]
|
||||
_, source_img = cv2.threshold(source_img, 127, 255, cv2.THRESH_BINARY)
|
||||
cv2.imwrite('/tmp/06thresh.png', source_img)
|
||||
tgth, tgtw = target_img.shape
|
||||
padded_img = np.zeros(shape=(max(imgh, tgth), max(imgw, tgtw)), dtype=source_img.dtype)
|
||||
padded_img[(tgth-imgh)//2:tgth-((tgth-imgh+1)//2), (tgtw-imgw)//2:tgtw-((tgtw-imgw+1)//2)] = source_img
|
||||
|
||||
cv2.imwrite('/tmp/10padded.png', padded_img)
|
||||
cv2.imwrite('/tmp/20target.png', target_img)
|
||||
out_img = (np.multiply((padded_img/255.0), (target_img/255.0) * -1 + 1) * 255).astype(np.uint8)
|
||||
|
||||
cv2.imwrite('/tmp/30multiplied.png', out_img)
|
||||
cv2.imwrite('/tmp/40vis.png', out_img + original_img)
|
||||
|
||||
plot_contours(out_img, target, offx=(tminx, tminy), scale=scale)
|
||||
|
||||
from gerber.render import rs274x_backend
|
||||
ctx = rs274x_backend.Rs274xContext(target.settings)
|
||||
target.render(ctx)
|
||||
return ctx.dump().getvalue()
|
||||
|
||||
|
||||
def plot_contours(img:np.ndarray, layer:gerber.rs274x.GerberFile, offx:tuple, scale:float, debug=lambda *args:None):
|
||||
imgh, imgw = img.shape
|
||||
|
||||
# Extract contours
|
||||
img_cont_out, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_TC89_KCOS)
|
||||
|
||||
aperture = list(layer.apertures)[0]
|
||||
|
||||
# XXX
|
||||
layer.primitives.clear()
|
||||
|
||||
from gerber.primitives import Line, Region
|
||||
debug('offx', offx, 'scale', scale)
|
||||
|
||||
xbias, ybias = offx
|
||||
def map(coord):
|
||||
x, y = coord
|
||||
# FIXME sometimes only ybias is needed
|
||||
return (x/scale, y/scale + ybias)
|
||||
def contour_lines(c):
|
||||
return [ Line(map(start), map(end), aperture, level_polarity='dark', units=layer.settings.units)
|
||||
for start, end in zip(c, np.vstack((c[1:], c[:1]))) ]
|
||||
|
||||
done = []
|
||||
process_stack = [-1]
|
||||
next_process_stack = []
|
||||
is_dark = True
|
||||
while len(done) != len(contours):
|
||||
for i, (_1, _2, _3, parent) in enumerate(hierarchy[0]):
|
||||
if parent in process_stack:
|
||||
contour = contours[i]
|
||||
polarity = 'dark' if is_dark else 'clear'
|
||||
debug('rendering {} with parent {} as {} with {} vertices'.format(i, parent, polarity, len(contour)))
|
||||
debug('process_stack is', process_stack)
|
||||
debug()
|
||||
layer.primitives.append(Region(contour_lines(contour[:,0]), level_polarity=polarity, units=layer.settings.units))
|
||||
next_process_stack.append(i)
|
||||
done.append(i)
|
||||
debug('skipping to next level')
|
||||
process_stack, next_process_stack = next_process_stack, []
|
||||
is_dark = not is_dark
|
||||
debug('done', done)
|
||||
|
||||
# Utility foo
|
||||
# ===========
|
||||
|
||||
def find_gerber_in_dir(dir_path, file_or_ext):
|
||||
lname = path.join(dir_path, file_or_ext)
|
||||
if path.isfile(lname):
|
||||
with open(lname, 'r') as f:
|
||||
return lname, f.read()
|
||||
|
||||
contents = os.listdir(dir_path)
|
||||
for entry in contents:
|
||||
if entry.lower().endswith(file_or_ext.lower()):
|
||||
lname = path.join(dir_path, entry)
|
||||
if not path.isfile(lname):
|
||||
continue
|
||||
with open(lname, 'r') as f:
|
||||
return lname, f.read()
|
||||
|
||||
raise ValueError('Cannot find file or suffix "{}" in dir {}'.format(file_or_ext, dir_path))
|
||||
|
||||
def find_gerber_in_zip(zip_path, file_or_ext):
|
||||
with zipfile.ZeipFile(zip_path, 'r') as lezip:
|
||||
nlist = [ item.filename for item in zipin.infolist() ]
|
||||
if file_or_ext in nlist:
|
||||
return file_or_ext, lezip.read(file_or_ext)
|
||||
|
||||
for n in nlist:
|
||||
if n.lower().endswith(file_or_ext.lower()):
|
||||
return n, lezip.read(n)
|
||||
|
||||
raise ValueError('Cannot find file or suffix "{}" in zip {}'.format(file_or_ext, dir_path))
|
||||
|
||||
def replace_file_in_zip(zip_path, filename, contents):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tempname = path.join(tmpdir, 'out.zip')
|
||||
with zipfile.ZipFile(zip_path, 'r') as zipin, zipfile.ZipFile(tempname, 'w') as zipout:
|
||||
for item in zipin.infolist():
|
||||
if item.filename != filename:
|
||||
zipout.writestr(item, zipin.read(item.filename))
|
||||
zipout.writestr(filename, contents)
|
||||
shutil.move(tempname, zip_path)
|
||||
|
||||
def paste_image_file(zip_or_dir, target, outline, source_img):
|
||||
if path.isdir(zip_or_dir):
|
||||
tname, target = find_gerber_in_dir(zip_or_dir, target)
|
||||
_, outline = find_gerber_in_dir(zip_or_dir, outline)
|
||||
|
||||
out = paste_image(target, outline, source_img)
|
||||
|
||||
# XXX
|
||||
with open('/tmp/out.GTO', 'w') as f:
|
||||
# with open(tname, 'w') as f:
|
||||
f.write(out)
|
||||
elif zipfile.is_zipfile(zip_or_dir):
|
||||
tname, target = find_gerber_in_zip(zip_or_dir, target)
|
||||
_, outline = find_gerber_in_zip(zip_or_dir, outline)
|
||||
|
||||
out = paste_image(target, outline, source_img)
|
||||
replace_file_in_zip(zip_or_dir, tname, out)
|
||||
else:
|
||||
raise ValueError('{} does not look like either a folder or a zip file')
|
||||
|
||||
# Command line interface
|
||||
# ======================
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-t', '--target', default='.GTO', help='Target layer. Filename or extension in target folder/zip')
|
||||
parser.add_argument('-o', '--outline', default='.GKO', help='Target outline layer. Filename or extension in target folder/zip')
|
||||
parser.add_argument('zip_or_dir', default='.', nargs='?', help='Optional folder or zip with target files')
|
||||
parser.add_argument('source', help='Source image')
|
||||
args = parser.parse_args()
|
||||
|
||||
source_img = cv2.imread(args.source, cv2.IMREAD_GRAYSCALE)
|
||||
paste_image_file(args.zip_or_dir, args.target, args.outline, source_img)
|
||||
|
||||
33
led_drv.GKO
Normal file
33
led_drv.GKO
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
G04 Layer_Color=16711935*
|
||||
%FSLAX25Y25*%
|
||||
%MOIN*%
|
||||
G70*
|
||||
G01*
|
||||
G75*
|
||||
%ADD26C,0.01000*%
|
||||
D26*
|
||||
X354331Y177165D02*
|
||||
G03*
|
||||
X334646Y196850I-19685J0D01*
|
||||
G01*
|
||||
Y0D02*
|
||||
G03*
|
||||
X354331Y19685I0J19685D01*
|
||||
G01*
|
||||
X0D02*
|
||||
G03*
|
||||
X19685Y0I19685J0D01*
|
||||
G01*
|
||||
Y196850D02*
|
||||
G03*
|
||||
X0Y177165I0J-19685D01*
|
||||
G01*
|
||||
X354331Y19685D02*
|
||||
Y177165D01*
|
||||
X19685Y196850D02*
|
||||
X334646D01*
|
||||
X19685Y0D02*
|
||||
X334646D01*
|
||||
X0Y19685D02*
|
||||
Y177165D01*
|
||||
M02*
|
||||
5494
led_drv.GTO
Normal file
5494
led_drv.GTO
Normal file
File diff suppressed because it is too large
Load diff
162
test.svg
Normal file
162
test.svg
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="70mm"
|
||||
height="50mm"
|
||||
viewBox="0 0 70 50"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
|
||||
sodipodi:docname="test.svg"
|
||||
inkscape:export-filename="/home/user/toys/gerbimg/test.svg.png"
|
||||
inkscape:export-xdpi="290.29001"
|
||||
inkscape:export-ydpi="290.29001">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="2.8284271"
|
||||
inkscape:cx="139.16551"
|
||||
inkscape:cy="85.907173"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1918"
|
||||
inkscape:window-height="1026"
|
||||
inkscape:window-x="1617"
|
||||
inkscape:window-y="154"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-247)">
|
||||
<rect
|
||||
style="opacity:0.98000004;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.79375005"
|
||||
id="rect4504"
|
||||
width="10"
|
||||
height="10"
|
||||
x="0"
|
||||
y="247" />
|
||||
<rect
|
||||
style="opacity:0.98000004;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.79375005"
|
||||
id="rect4504-3"
|
||||
width="10"
|
||||
height="10"
|
||||
x="60"
|
||||
y="247" />
|
||||
<rect
|
||||
style="opacity:0.98000004;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.79375005"
|
||||
id="rect4504-6"
|
||||
width="10"
|
||||
height="10"
|
||||
x="60"
|
||||
y="287" />
|
||||
<rect
|
||||
style="opacity:0.98000004;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.79375005"
|
||||
id="rect4504-7"
|
||||
width="10"
|
||||
height="10"
|
||||
x="0"
|
||||
y="287" />
|
||||
<rect
|
||||
style="opacity:0.98000004;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.79375005"
|
||||
id="rect4504-5"
|
||||
width="10"
|
||||
height="10"
|
||||
x="60"
|
||||
y="267" />
|
||||
<rect
|
||||
style="opacity:0.98000004;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.79375005"
|
||||
id="rect4504-35"
|
||||
width="10"
|
||||
height="10"
|
||||
x="0"
|
||||
y="267" />
|
||||
<rect
|
||||
style="opacity:0.98000004;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.79375005"
|
||||
id="rect4504-62"
|
||||
width="10"
|
||||
height="10"
|
||||
x="30"
|
||||
y="247" />
|
||||
<rect
|
||||
style="opacity:0.98000004;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.79375005"
|
||||
id="rect4504-9"
|
||||
width="10"
|
||||
height="10"
|
||||
x="30"
|
||||
y="287" />
|
||||
<circle
|
||||
style="opacity:0.98000004;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.79374999"
|
||||
id="path4563"
|
||||
cx="35"
|
||||
cy="272"
|
||||
r="7.5000005" />
|
||||
<circle
|
||||
style="opacity:0.98000004;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.79374999"
|
||||
id="path4563-1"
|
||||
cx="35"
|
||||
cy="272"
|
||||
r="5" />
|
||||
<circle
|
||||
style="opacity:0.98000004;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.79374999"
|
||||
id="path4563-2"
|
||||
cx="35"
|
||||
cy="272"
|
||||
r="2.5" />
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot4586"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:15px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.69816089px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
transform="matrix(0.37897186,0,0,0.37897186,-10.192843,241.84261)"><flowRegion
|
||||
id="flowRegion4588"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';stroke-width:0.69816089px"><rect
|
||||
id="rect4590"
|
||||
width="101.46983"
|
||||
height="72.832001"
|
||||
x="55.507881"
|
||||
y="18.917198"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';stroke-width:0.69816089px" /></flowRegion><flowPara
|
||||
id="flowPara4592"
|
||||
style="stroke-width:0.69816089px">↑UP↑</flowPara></flowRoot> <flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot4586-7"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:15px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.69816089px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
transform="matrix(0.37897186,0,0,0.37897186,19.807155,241.84261)"><flowRegion
|
||||
id="flowRegion4588-0"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';stroke-width:0.69816089px"><rect
|
||||
id="rect4590-9"
|
||||
width="101.46983"
|
||||
height="72.832001"
|
||||
x="55.507881"
|
||||
y="18.917198"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';stroke-width:0.69816089px" /></flowRegion><flowPara
|
||||
id="flowPara4592-3"
|
||||
style="stroke-width:0.69816089px">↑UP↑</flowPara></flowRoot> </g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
BIN
test.svg.png
Normal file
BIN
test.svg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Loading…
Add table
Add a link
Reference in a new issue