WIP
This commit is contained in:
parent
27c00d0b03
commit
f9a1d61e59
2 changed files with 52 additions and 145 deletions
|
|
@ -58,30 +58,6 @@ def angle_between_vectors(va, vb):
|
|||
return angle
|
||||
|
||||
|
||||
def traces_to_magneticalc(traces, out, pcb_thickness=0.8):
|
||||
coords = []
|
||||
last_x, last_y, last_z = None, None, None
|
||||
def coord(x, y, z):
|
||||
nonlocal coords, last_x, last_y, last_z
|
||||
if (x, y, z) != (last_x, last_y, last_z):
|
||||
coords.append((x, y, z))
|
||||
|
||||
render_cache = {}
|
||||
for tr in traces:
|
||||
z = pcb_thickness if tr[1].layer == 'F.Cu' else 0
|
||||
objs = [obj
|
||||
for elem in tr
|
||||
for obj in elem.render(cache=render_cache)
|
||||
if isinstance(elem, (kicad_pcb.TrackSegment, kicad_pcb.TrackArc))]
|
||||
|
||||
# start / switch layer
|
||||
coord(objs[0].x1, objs[0].y1, z)
|
||||
|
||||
for ob in objs:
|
||||
coord(ob.x2, ob.y2, z)
|
||||
|
||||
np.savetxt(out, np.array(coords) / 10) # magneticalc expects centimeters, not millimeters.
|
||||
|
||||
# https://en.wikipedia.org/wiki/Farey_sequence#Next_term
|
||||
def farey_sequence(n: int, descending: bool = False) -> None:
|
||||
"""Print the n'th Farey sequence. Allow for either ascending or descending."""
|
||||
|
|
@ -190,7 +166,7 @@ def compute_spiral(r1, r2, a1, a2, start_frac, end_frac, fn=64):
|
|||
@click.option('--show-twists', callback=print_valid_twists, expose_value=False, type=int, is_eager=True, help='Calculate and show valid --twists counts for the given number of turns. Takes the number of turns as a value.')
|
||||
@click.option('--clearance', type=float, default=None)
|
||||
@click.option('--arc-tolerance', type=float, default=0.02)
|
||||
@click.option('--format', type=click.Choice(['svg', 'gerber', 'kicad-footprint', 'kicad-pcb', 'magneticalc', 'show']), default='kicad-footprint')
|
||||
@click.option('--format', type=click.Choice(['svg', 'gerber', 'kicad-footprint', 'kicad-pcb', 'show']), default='kicad-footprint')
|
||||
@click.option('--clipboard/--no-clipboard', help='Use clipboard integration (requires wl-clipboard)')
|
||||
@click.option('--counter-clockwise/--clockwise', help='Direction of generated spiral. Default: clockwise when wound from the inside.')
|
||||
@click.version_option()
|
||||
|
|
@ -295,9 +271,20 @@ def generate(outfile, turns, outer_diameter, inner_diameter, via_diameter, via_d
|
|||
print(f'Fill factor: {phi:g}', file=sys.stderr)
|
||||
print(f'Approximate inductance: {L:g} µH', file=sys.stderr)
|
||||
|
||||
pads = []
|
||||
lines = []
|
||||
arcs = []
|
||||
if footprint_name:
|
||||
name = footprint_name
|
||||
elif outfile:
|
||||
name = outfile.stem,
|
||||
else:
|
||||
name = f'generated-coil-{outer_diameter:.2f}x{inner_diameter:.2f}-n{turns}-k{twists}'
|
||||
|
||||
footprint = kicad_fp.Footprint(
|
||||
name=name,
|
||||
generator=kicad_fp.Atom('KicoilV1'),
|
||||
layer='F.Cu',
|
||||
descr=f"{turns} turn {outer_diameter:.2f} mm diameter twisted coil footprint, inductance approximately {L:.6f} µH. Generated by gerbonara'c Twisted Coil generator, version {__version__}.",
|
||||
clearance=clearance,
|
||||
zone_connect=0)
|
||||
|
||||
sector_angle = 2*pi / twists
|
||||
total_angle = twists*2*sweeping_angle if two_layer else twists*sweeping_angle
|
||||
|
|
@ -306,18 +293,20 @@ def generate(outfile, turns, outer_diameter, inner_diameter, via_diameter, via_d
|
|||
for i in range(twists):
|
||||
inverse[i*turns%twists] = i
|
||||
|
||||
layer_sections = []
|
||||
# Array where we collect all gerbonara kicad line and arc objects
|
||||
for i in range(twists):
|
||||
start_angle = i*sector_angle
|
||||
fold_angle = start_angle + sweeping_angle
|
||||
end_angle = fold_angle + sweeping_angle
|
||||
|
||||
# Handle the spiral arm
|
||||
x = inverse[i]*floor(2*sweeping_angle / (2*pi)) * 2*pi
|
||||
points_layer0, arm_length = compute_spiral(outer_radius, inner_radius, start_angle, fold_angle, (x + start_angle)/total_angle, (x + fold_angle)/total_angle, circle_segments)
|
||||
x0, y0 = points_layer0[0]
|
||||
xn, yn = points_layer0[-1]
|
||||
|
||||
if two_layer:
|
||||
# Handle the returning arm on the bottom layer
|
||||
points_layer1, _ = compute_spiral(inner_radius, outer_radius, fold_angle, end_angle, (x + fold_angle)/total_angle, (x + end_angle)/total_angle, circle_segments)
|
||||
|
||||
else:
|
||||
|
|
@ -327,167 +316,78 @@ def generate(outfile, turns, outer_diameter, inner_diameter, via_diameter, via_d
|
|||
yq = yn - sin(fold_angle) * dr
|
||||
points_layer1 = [(xn, yn), (xq, yq)]
|
||||
|
||||
#r, g, b, _a = mpl.cm.plasma(start_frac + (end_frac - start_frac)/fn * (i + 0.5))
|
||||
#path = SVGPath(fill='none', stroke=f'#{round(r*255):02x}{round(g*255):02x}{round(b*255):02x}', stroke_width=trace_width, stroke_linejoin='round', stroke_linecap='round')
|
||||
#svg_stuff.append(path)
|
||||
#path.move(xp, yp)
|
||||
#path.line(xn, yn)
|
||||
# lines.append(make_line(xp, yp, xn, yn, layer_pair[layer]))
|
||||
# if use_arcs:
|
||||
# arcs.extend(arc_approximate(points, layer_pair[layer], arc_tolerance))
|
||||
#svg_vias.append(Tag('circle', cx=xv, cy=yv, r=via_diameter/2, stroke='none', fill='white'))
|
||||
#svg_vias.append(Tag('circle', cx=xv, cy=yv, r=via_drill/2, stroke='none', fill='black'))
|
||||
#pads.append(make_via(xv, yv, layer_pair))
|
||||
footprint.arcs.extend(arc_approximate(points_layer0, layer_pair[0], arc_tolerance))
|
||||
footprint.arcs.extend(arc_approximate(points_layer1, layer_pair[1], arc_tolerance))
|
||||
|
||||
# Handle inner via ring and process staggering if enabled
|
||||
r = inner_via_ring_radius
|
||||
if stagger_inner_vias:
|
||||
if i%2 != 0:
|
||||
r -= 2*via_offset
|
||||
xv, yv = r*cos(fold_angle), r*sin(fold_angle)
|
||||
if not isclose(via_offset, 0, abs_tol=1e-6):
|
||||
points_layer0.append([xv, yv])
|
||||
points_layer1.insert(0, [xv, yv])
|
||||
|
||||
xv, yv = r*cos(fold_angle), r*sin(fold_angle)
|
||||
|
||||
if not isclose(via_offset, 0, abs_tol=1e-6):
|
||||
footprint.lines.append(make_line(*points_layer0[-1], xv, yv, layer_pair[0]))
|
||||
footprint.lines.append(make_line(xv, yv, *points_layer1[0], layer_pair[1]))
|
||||
|
||||
# Handle outer via ring and process staggering if enabled unless we are at the start of the coil, where we will
|
||||
# place pads below.
|
||||
if i > 0:
|
||||
r = outer_via_ring_radius
|
||||
|
||||
if stagger_outer_vias:
|
||||
if i%2 != 0:
|
||||
r += 2*via_offset
|
||||
|
||||
xv, yv = r*cos(start_angle), r*sin(start_angle)
|
||||
|
||||
if not isclose(via_offset, 0, abs_tol=1e-6):
|
||||
points_layer0.insert(0, [xv, yv])
|
||||
points_layer1.insert(0, [xv, yv])
|
||||
lines.append(make_line(x0, y0, xv, yv, layer_pair[0]))
|
||||
lines.append(make_line(x0, y0, xv, yv, layer_pair[1]))
|
||||
svg_vias.append(Tag('circle', cx=xv, cy=yv, r=via_diameter/2, stroke='none', fill='white'))
|
||||
svg_vias.append(Tag('circle', cx=xv, cy=yv, r=via_drill/2, stroke='none', fill='black'))
|
||||
footprint.lines.append(make_line(x0, y0, xv, yv, layer_pair[0]))
|
||||
footprint.lines.append(make_line(x0, y0, xv, yv, layer_pair[1]))
|
||||
|
||||
l_total = arm_length*twists*(2 if two_layer else 1)
|
||||
print(f'Approximate track length: {l_total:.2f} mm', file=sys.stderr)
|
||||
|
||||
A = copper_thickness/1e3 * trace_width/1e3
|
||||
rho = 1.68e-8
|
||||
R = l_total/1e3 * rho / A
|
||||
print(f'Approximate resistance: {R:g} Ω', file=sys.stderr)
|
||||
|
||||
# Place the pads on the outer radius
|
||||
top_pad = make_pad(1, [layer_pair[0]], outer_radius, 0)
|
||||
pads.append(top_pad)
|
||||
bottom_pad = make_pad(2, [layer_pair[1]], outer_radius, 0)
|
||||
pads.append(bottom_pad)
|
||||
|
||||
svg_stuff += svg_vias
|
||||
|
||||
svg_stuff.append(Tag('path', d=f'M {inner_radius} 0 L {outer_radius} 0', stroke=rainbow[n+1], fill='none',
|
||||
stroke_width='0.05mm', stroke_linecap='round'))
|
||||
ntraces = int(turns_per_layer)+1
|
||||
alpha = [0] * ntraces
|
||||
for i in range(ntraces):
|
||||
c = inner_radius + (outer_radius-inner_radius) / turns_per_layer * i
|
||||
#dalpha = dy / c
|
||||
#dx / dalpha = (outer_radius - inner_radius) / sweeping_angle
|
||||
#c * (dx / dy) = (outer_radius - inner_radius) / sweeping_angle
|
||||
#dx / dy = (outer_radius - inner_radius) / sweeping_angle / c
|
||||
dx = (outer_radius - inner_radius) / sweeping_angle / c
|
||||
alpha[i] = atan(dx)
|
||||
dy = 0.3
|
||||
dx *= dy
|
||||
r = trace_width/2 / cos(alpha[i])
|
||||
svg_stuff.append(Tag('path', d=f'M {c-r+dx} {-dy} L {c-r-dx} {dy}', stroke=rainbow[n+1], fill='none',
|
||||
stroke_width='0.05mm', stroke_linecap='round'))
|
||||
svg_stuff.append(Tag('path', d=f'M {c+r+dx} {-dy} L {c+r-dx} {dy}', stroke=rainbow[n+1], fill='none',
|
||||
stroke_width='0.05mm', stroke_linecap='round'))
|
||||
|
||||
#print(f'spiral angle {degrees(alpha[i]):.2f}', file=sys.stderr)
|
||||
|
||||
for i, (a1, a2) in enumerate(zip(alpha[::-1], alpha[1::])):
|
||||
amean = (a2+a1)/2
|
||||
pitch = (outer_radius - inner_radius) / turns_per_layer
|
||||
clearance = pitch - trace_width
|
||||
clearance *= cos(amean)
|
||||
|
||||
x, y = inner_radius + (i + 1/2)*pitch, -0.5
|
||||
svg_stuff.append(Tag('text',
|
||||
[f'{clearance:.5f}mm'],
|
||||
x=x,
|
||||
y=y,
|
||||
text_anchor='start',
|
||||
transform=f'rotate(-45 {x} {y})',
|
||||
style=f'font: 1px bold sans-serif; fill: {rainbow[n+1]}'))
|
||||
|
||||
if svg_out:
|
||||
svg_file(svg_out, svg_stuff, 100, 100, -50, -50)
|
||||
|
||||
if footprint_name:
|
||||
name = footprint_name
|
||||
elif outfile:
|
||||
name = outfile.stem,
|
||||
else:
|
||||
name = 'generated_coil'
|
||||
|
||||
if keepout_zone:
|
||||
r = outer_diameter/2 + keepout_margin
|
||||
tol = 0.05 # mm
|
||||
n = ceil(pi / acos(1 - tol/r))
|
||||
pts = [(r*cos(a*2*pi/n), r*sin(a*2*pi/n)) for a in range(n)]
|
||||
zones = [kicad_pr.Zone(layers=['*.Cu'],
|
||||
footprint.zones.append(kicad_pr.Zone(layers=['*.Cu'],
|
||||
hatch=kicad_pr.Hatch(),
|
||||
filled_areas_thickness=False,
|
||||
keepout=kicad_pr.ZoneKeepout(copperpour_allowed=False),
|
||||
polygon=kicad_pr.ZonePolygon(pts=[kicad_pr.XYCoord(x=x, y=y) for x, y in pts]))]
|
||||
else:
|
||||
zones = []
|
||||
polygon=kicad_pr.ZonePolygon(pts=[kicad_pr.XYCoord(x=x, y=y) for x, y in pts])))
|
||||
|
||||
if pcb:
|
||||
obj = kicad_pcb.Board.empty_board(
|
||||
zones=zones,
|
||||
track_segments=[kicad_pcb.TrackSegment.from_footprint_line(line) for line in lines],
|
||||
vias=[kicad_pcb.Via.from_pad(pad) for pad in pads if pad.type == kicad_pcb.Atom.thru_hole])
|
||||
obj.rebuild_trace_index()
|
||||
seg = obj.track_segments[-1]
|
||||
traces = []
|
||||
end = top_pad
|
||||
layer = 'F.Cu'
|
||||
while True:
|
||||
tr = list(obj.find_connected_traces(end, layers=[layer]))
|
||||
traces.append(tr)
|
||||
if not isinstance(tr[-1], kicad_pcb.Via):
|
||||
break
|
||||
layer = 'B.Cu' if layer == 'F.Cu' else 'F.Cu'
|
||||
end = tr[-1]
|
||||
# remove start pad
|
||||
traces[0] = traces[0][1:]
|
||||
|
||||
r = outer_diameter/2 + 20
|
||||
|
||||
if magneticalc_out:
|
||||
traces_to_magneticalc(traces, magneticalc_out)
|
||||
|
||||
else:
|
||||
obj = kicad_fp.Footprint(
|
||||
name=name,
|
||||
generator=kicad_fp.Atom('GerbonaraTwistedCoilGenV1'),
|
||||
layer='F.Cu',
|
||||
descr=f"{turns} turn {outer_diameter:.2f} mm diameter twisted coil footprint, inductance approximately {L:.6f} µH. Generated by gerbonara'c Twisted Coil generator, version {__version__}.",
|
||||
clearance=clearance,
|
||||
zone_connect=0,
|
||||
lines=lines,
|
||||
arcs=arcs,
|
||||
pads=pads,
|
||||
zones=zones,
|
||||
)
|
||||
if format == 'kicad-footprint':
|
||||
data = footprint.serialize()
|
||||
elif format == 'kicad-pcb':
|
||||
data = footprint_to_board(footprint).serialize()
|
||||
elif format == 'gerber':
|
||||
|
||||
if clipboard:
|
||||
try:
|
||||
data = obj.serialize()
|
||||
print(f'Running {copy[0]}.', file=sys.stderr)
|
||||
proc = subprocess.Popen(copy, stdin=subprocess.PIPE, text=True)
|
||||
proc.communicate(data)
|
||||
print('passed to wl-clip:', data)
|
||||
except FileNotFoundError:
|
||||
print(f'Error: --clipboard requires the {copy[0]} and {paste[0]} utilities from {cliputil} to be installed.', file=sys.stderr)
|
||||
elif not outfile:
|
||||
print(obj.serialize())
|
||||
print(data)
|
||||
else:
|
||||
obj.write(outfile)
|
||||
outfile.write_text(data)
|
||||
|
||||
if __name__ == '__main__':
|
||||
generate()
|
||||
|
|
|
|||
|
|
@ -39,3 +39,10 @@ def make_via(x, y, layers):
|
|||
clearance=clearance,
|
||||
zone_connect=0)
|
||||
|
||||
|
||||
def footprint_to_board(footprint):
|
||||
return kicad_pcb.Board.empty_board(
|
||||
zones=zones,
|
||||
track_segments=[kicad_pcb.TrackSegment.from_footprint_line(line) for line in lines],
|
||||
vias=[kicad_pcb.Via.from_pad(pad) for pad in pads if pad.type == kicad_pcb.Atom.thru_hole])
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue