Add outline highlight example
This commit is contained in:
parent
57941b1b76
commit
1944521bb9
2 changed files with 134 additions and 43 deletions
|
|
@ -7,44 +7,47 @@ from gerbonara import LayerStack
|
|||
from gerbonara.graphic_objects import Line, Arc
|
||||
from gerbonara.apertures import CircleAperture
|
||||
from gerbonara.utils import MM
|
||||
from gerbonara.utils import rotate_point
|
||||
|
||||
def highlight_outline(input_dir, output_dir):
|
||||
#stack = LayerStack.from_directory(input_dir)
|
||||
stack = LayerStack.from_directory(input_dir)
|
||||
|
||||
#outline = []
|
||||
#for obj in stack.outline.objects:
|
||||
# if isinstance(obj, Line):
|
||||
# outline.append(obj)
|
||||
#
|
||||
# elif isinstance(obj, Arc):
|
||||
# outline += obj.approximate(0.1, 'mm')
|
||||
outline = []
|
||||
for obj in stack.outline.objects:
|
||||
if isinstance(obj, Line):
|
||||
outline.append(obj.converted('mm'))
|
||||
|
||||
elif isinstance(obj, Arc):
|
||||
outline += obj.converted('mm').approximate(0.1, 'mm')
|
||||
|
||||
# FIXME test code
|
||||
print('<?xml version="1.0" encoding="utf-8"?>')
|
||||
print('<svg width="300mm" height="300mm" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">')
|
||||
from gerbonara.utils import rotate_point
|
||||
outline = []
|
||||
for i in range(16):
|
||||
for j in range(16):
|
||||
cx, cy = i*3, j*3
|
||||
w = i/8
|
||||
angle = j*2*math.pi/16
|
||||
x1, y1 = cx-w/2, cy
|
||||
x2, y2 = cx+w/2, cy
|
||||
#print('<?xml version="1.0" encoding="utf-8"?>')
|
||||
#print('<svg width="300mm" height="300mm" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">')
|
||||
#outline = []
|
||||
#for i in range(16):
|
||||
# for j in range(16):
|
||||
# cx, cy = i*3, j*3
|
||||
# w = i/8
|
||||
# angle = j*2*math.pi/16
|
||||
# x1, y1 = cx-w/2, cy
|
||||
# x2, y2 = cx+w/2, cy
|
||||
#
|
||||
# x1, y1 = rotate_point(x1, y1, angle, cx, cy)
|
||||
# x2, y2 = rotate_point(x2, y2, angle, cx, cy)
|
||||
#
|
||||
# outline.append(Line(x1, y1, x2, y2, aperture=CircleAperture(1.0, unit=MM), unit=MM))
|
||||
# print(f'<path style="stroke: red; stroke-width: 0.01mm;" d="M {x1} {y1} L {x2} {y2}"/>')
|
||||
|
||||
x1, y1 = rotate_point(x1, y1, angle, cx, cy)
|
||||
x2, y2 = rotate_point(x2, y2, angle, cx, cy)
|
||||
|
||||
outline.append(Line(x1, y1, x2, y2, aperture=CircleAperture(1.0, unit=MM), unit=MM))
|
||||
print(f'<path style="stroke: red; stroke-width: 0.01mm;" d="M {x1} {y1} L {x2} {y2}"/>')
|
||||
|
||||
marker_angle = math.pi/4
|
||||
marker_spacing = 0.2
|
||||
marker_width = 0.01
|
||||
marker_angle = math.pi/3
|
||||
marker_spacing = 2
|
||||
marker_width = 0.1
|
||||
|
||||
marker_dx, marker_dy = math.sin(marker_angle)*marker_spacing, -math.cos(marker_angle)*marker_spacing
|
||||
marker_nx, marker_ny = math.sin(marker_angle), math.cos(marker_angle)
|
||||
|
||||
ap = CircleAperture(0.1, unit=MM)
|
||||
stack['top silk'].apertures.append(ap)
|
||||
|
||||
for line in outline:
|
||||
cx, cy = (line.x1 + line.x2)/2, (line.y1 + line.y2)/2
|
||||
dx, dy = line.x1 - cx, line.y1 - cy
|
||||
|
|
@ -55,7 +58,8 @@ def highlight_outline(input_dir, output_dir):
|
|||
continue
|
||||
|
||||
cr = math.hypot(cx, cy)
|
||||
w = line.aperture.equivalent_width('mm')
|
||||
#w = line.aperture.equivalent_width('mm')
|
||||
w = 10
|
||||
|
||||
tl_x, tl_y = line.x1 + math.sin(angle)*w/2, line.y1 - math.cos(angle)*w/2
|
||||
tr_x, tr_y = line.x2 + math.sin(angle)*w/2, line.y2 - math.cos(angle)*w/2
|
||||
|
|
@ -64,13 +68,11 @@ def highlight_outline(input_dir, output_dir):
|
|||
|
||||
tr = math.dist((tl_x, tl_y), (br_x, br_y))/2
|
||||
|
||||
print(f'<path style="stroke: red; stroke-width: 0.01mm; fill: none;" d="M {tl_x} {tl_y} L {tr_x} {tr_y} L {br_x} {br_y} L {bl_x} {bl_y} Z"/>')
|
||||
#print(f'<path style="stroke: red; stroke-width: 0.01mm; fill: none;" d="M {tl_x} {tl_y} L {tr_x} {tr_y} L {br_x} {br_y} L {bl_x} {bl_y} Z"/>')
|
||||
|
||||
rot_cx, rot_cy = rotate_point(cx, cy, -marker_angle)
|
||||
offx = (rot_cy % marker_spacing) / marker_spacing
|
||||
n = math.ceil(tr/marker_spacing)
|
||||
for i in range(-n, n+1):
|
||||
px, py = cx + (i+offx)*marker_dx, cy + (i+offx)*marker_dy
|
||||
px, py = cx + i*marker_dx, cy + i*marker_dy
|
||||
|
||||
lx1, ly1 = px + tr*marker_nx, py + tr*marker_ny
|
||||
lx2, ly2 = px - tr*marker_nx, py - tr*marker_ny
|
||||
|
|
@ -80,7 +82,7 @@ def highlight_outline(input_dir, output_dir):
|
|||
#print(f'<circle style="fill: blue; stroke: none;" r="{marker_spacing/2}" cx="{px}" cy="{py}"/>')
|
||||
|
||||
def clip_line_point(x1, y1, x2, y2, xabs, yabs):
|
||||
print(x1, y1, x2, y2, end=' -> ', file=sys.stderr)
|
||||
#print(x1, y1, x2, y2, end=' -> ', file=sys.stderr)
|
||||
if x2 != x1:
|
||||
a = (y2 - y1) / (x2 - x1)
|
||||
x2 = min(xabs, max(-xabs, x2))
|
||||
|
|
@ -97,7 +99,7 @@ def highlight_outline(input_dir, output_dir):
|
|||
elif abs(y1) > yabs:
|
||||
return None
|
||||
|
||||
print(x1, y1, x2, y2, file=sys.stderr)
|
||||
#print(x1, y1, x2, y2, file=sys.stderr)
|
||||
return x1, y1, x2, y2
|
||||
|
||||
if not (foo := clip_line_point(lx1-cx, ly1-cy, lx2-cx, ly2-cy, r, w/2)):
|
||||
|
|
@ -113,7 +115,9 @@ def highlight_outline(input_dir, output_dir):
|
|||
lx1, ly1 = rotate_point(lx1, ly1, -angle, cx, cy)
|
||||
lx2, ly2 = rotate_point(lx2, ly2, -angle, cx, cy)
|
||||
|
||||
print(f'<path style="stroke: blue; stroke-width: 0.01mm; opacity: 0.2;" d="M {lx1} {ly1} L {lx2} {ly2}"/>')
|
||||
stack['top silk'].objects.append(Line(lx1, ly1, lx2, ly2, unit=MM, aperture=ap, polarity_dark=True))
|
||||
|
||||
#print(f'<path style="stroke: blue; stroke-width: {marker_width}mm; opacity: 0.2;" d="M {lx1} {ly1} L {lx2} {ly2}"/>')
|
||||
|
||||
#delta_a = marker_angle - angle
|
||||
#ex, ey = px, py
|
||||
|
|
@ -121,7 +125,8 @@ def highlight_outline(input_dir, output_dir):
|
|||
#print(delta_a, file=sys.stderr)
|
||||
# delta_a + math.pi/2
|
||||
|
||||
print('</svg>')
|
||||
stack.save_to_directory(output_dir)
|
||||
#print('</svg>')
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
import os
|
||||
import re
|
||||
import warnings
|
||||
import copy
|
||||
from collections import namedtuple
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -42,6 +43,22 @@ STANDARD_LAYERS = [
|
|||
'bottom paste',
|
||||
]
|
||||
|
||||
class NamingScheme:
|
||||
kicad = {
|
||||
'top copper': '{board_name}-F.Cu.gbr',
|
||||
'top mask': '{board_name}-F.Mask.gbr',
|
||||
'top silk': '{board_name}-F.SilkS.gbr',
|
||||
'top paste': '{board_name}-F.Paste.gbr',
|
||||
'bottom copper': '{board_name}-B.Cu.gbr',
|
||||
'bottom mask': '{board_name}-B.Mask.gbr',
|
||||
'bottom silk': '{board_name}-B.SilkS.gbr',
|
||||
'bottom paste': '{board_name}-B.Paste.gbr',
|
||||
'inner copper': '{board_name}-In{layer_number}.Cu.gbr',
|
||||
'mechanical outline': '{board_name}-Edge.Cuts.gbr',
|
||||
'drill unknown': '{board_name}.drl',
|
||||
'other netlist': '{board_name}.d356',
|
||||
}
|
||||
|
||||
|
||||
def match_files(filenames):
|
||||
matches = {}
|
||||
|
|
@ -176,8 +193,15 @@ def layername_autoguesser(fn):
|
|||
|
||||
|
||||
class LayerStack:
|
||||
|
||||
def __init__(self, graphic_layers, drill_layers, netlist=None, board_name=None):
|
||||
self.graphic_layers = graphic_layers
|
||||
self.drill_layers = drill_layers
|
||||
self.board_name = board_name
|
||||
self.netlist = netlist
|
||||
|
||||
@classmethod
|
||||
def from_directory(kls, directory, board_name=None, verbose=False):
|
||||
def from_directory(kls, directory, board_name=None):
|
||||
|
||||
directory = Path(directory)
|
||||
if not directory.is_dir():
|
||||
|
|
@ -307,11 +331,73 @@ class LayerStack:
|
|||
board_name = re.sub(r'\W+$', '', board_name)
|
||||
return kls(layers, drill_layers, netlist, board_name=board_name)
|
||||
|
||||
def __init__(self, graphic_layers, drill_layers, netlist=None, board_name=None):
|
||||
self.graphic_layers = graphic_layers
|
||||
self.drill_layers = drill_layers
|
||||
self.board_name = board_name
|
||||
self.netlist = netlist
|
||||
def save_to_directory(self, path, naming_scheme={}, overwrite_existing=True):
|
||||
outdir = Path(path)
|
||||
outdir.mkdir(parents=True, exist_ok=overwrite_existing)
|
||||
|
||||
def check_not_exists(path):
|
||||
if path.exists() and not overwrite_existing:
|
||||
raise SystemError(f'Path exists but overwrite_existing is False: {path}')
|
||||
|
||||
def get_name(layer_type, layer):
|
||||
nonlocal naming_scheme, overwrite_existing
|
||||
|
||||
if (m := re.match('inner_([0-9]*) copper', layer_type)):
|
||||
layer_type = 'inner copper'
|
||||
num = int(m[1])
|
||||
else:
|
||||
num = None
|
||||
|
||||
if layer_type in naming_scheme:
|
||||
path = outdir / naming_scheme[layer_type].format(layer_num=num, board_name=self.board_name)
|
||||
else:
|
||||
path = outdir / layer.original_path.name
|
||||
|
||||
check_not_exists(path)
|
||||
return path
|
||||
|
||||
for (side, use), layer in self.graphic_layers.items():
|
||||
outpath = get_name(f'{side} {use}', layer)
|
||||
layer.save(outpath)
|
||||
|
||||
if naming_scheme:
|
||||
self.normalize_drill_layers()
|
||||
|
||||
def save_layer(layer, layer_name):
|
||||
nonlocal self, outdir, drill_layers, check_not_exists
|
||||
path = outdir / drill_layers[layer_name].format(board_name=self.board_name)
|
||||
check_not_exists(path)
|
||||
layer.save(path)
|
||||
|
||||
drill_layers = { key.partition()[2]: value for key, value in naming_scheme if 'drill' in key }
|
||||
if set(drill_layers) == {'plated', 'nonplated', 'unknown'}:
|
||||
save_layer(self.drill_pth, 'plated')
|
||||
save_layer(self.drill_npth, 'nonplated')
|
||||
save_layer(self.drill_unknown, 'unknown')
|
||||
|
||||
elif 'plated' in drill_layers and len(drill_layers) == 2:
|
||||
save_layer(self.drill_pth, 'plated')
|
||||
merged = copy.copy(self.drill_npth)
|
||||
merged.merge(self.drill_unknown)
|
||||
save_layer(merged, list(set(drill_layers) - {'plated'})[0])
|
||||
|
||||
elif 'unknown' in drill_layers:
|
||||
merged = copy.copy(self.drill_pth)
|
||||
merged.merge(self.drill_npth)
|
||||
merged.merge(self.drill_unknown)
|
||||
save_layer(merged, 'unknown')
|
||||
|
||||
else:
|
||||
raise ValueError('Namin scheme does not specify unknown drill layer')
|
||||
|
||||
else:
|
||||
for layer in self.drill_layers:
|
||||
outpath = outdir / layer.original_path.name
|
||||
check_not_exists(outpath)
|
||||
layer.save(outpath)
|
||||
|
||||
if self.netlist:
|
||||
layer.save(get_name('other netlist', self.netlist))
|
||||
|
||||
def __str__(self):
|
||||
names = [ f'{side} {use}' for side, use in self.graphic_layers ]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue