Making progress
This commit is contained in:
parent
133d97eacb
commit
5c2db13c3e
3 changed files with 294 additions and 71 deletions
File diff suppressed because one or more lines are too long
|
|
@ -136,10 +136,10 @@ class CircleShape(Shape):
|
|||
points.append((xn, yn))
|
||||
dists.append(dist((xp, yp), (xn, yn)))
|
||||
|
||||
return points, sum(dists)
|
||||
return points, sum(dists), None
|
||||
|
||||
|
||||
def project_point(self, r, a):
|
||||
def project_point(self, r, a, r_ref=None):
|
||||
return cos(a) * r, sin(a) * r
|
||||
|
||||
|
||||
|
|
@ -168,16 +168,21 @@ class OffsetShape(Shape):
|
|||
|
||||
def compute_spiral(self, a1, a2, fn=None):
|
||||
# Skeletonator uses a t coordinate from 0 - 1 per revolution instead of a radian angle.
|
||||
points = list(self.sk.do_spiral(a1/(2*pi), a2/(2*pi), self.outer_radius, self.inner_radius))
|
||||
points = []
|
||||
angle_refs = []
|
||||
for point, angle_ref in self.sk.do_spiral(a1/(2*pi), a2/(2*pi), self.outer_radius, self.inner_radius):
|
||||
points.append(point)
|
||||
angle_refs.append(angle_ref)
|
||||
if a2 < a1:
|
||||
points = points[::-1]
|
||||
angle_refs = angle_refs[::-1]
|
||||
arm_length = sum(dist(p1, p2) for p1, p2 in zip(points, points[1:]))
|
||||
return points, arm_length
|
||||
return points, arm_length, angle_refs
|
||||
|
||||
|
||||
def project_point(self, r, a):
|
||||
def project_point(self, r, a, r_ref=None):
|
||||
# Skeletonator uses a t coordinate from 0 - 1 per revolution instead of a radian angle.
|
||||
return self.sk.project_point(a/(2*pi), r)
|
||||
return self.sk.project_point(a/(2*pi), r, r_ref=r_ref)
|
||||
|
||||
|
||||
def offset_exterior(self, margin):
|
||||
|
|
@ -362,7 +367,7 @@ class PlanarInductor():
|
|||
self.logger.info(f'Fill factor: {phi:g}')
|
||||
self.logger.info(f'Approximate inductance: {self.L:g} µH')
|
||||
|
||||
_points, arm_length = self.shape.compute_spiral(a1=0, a2=self.sector_angle)
|
||||
_points, arm_length, _angle_refs = self.shape.compute_spiral(a1=0, a2=self.sector_angle)
|
||||
|
||||
self.track_length = arm_length*self.twists*self.layers
|
||||
self.logger.info(f'Approximate track length: {self.track_length:.2f} mm')
|
||||
|
|
@ -396,6 +401,7 @@ class PlanarInductor():
|
|||
for i in range(self.twists):
|
||||
inverse[i*self.turns%self.twists] = i
|
||||
|
||||
arms_layers = [[], []]
|
||||
# Array where we collect all gerbonara kicad line and arc objects
|
||||
for i in range(self.twists):
|
||||
start_angle = i*self.sector_angle
|
||||
|
|
@ -403,10 +409,13 @@ class PlanarInductor():
|
|||
end_angle = fold_angle + self.sweeping_angle
|
||||
|
||||
# Handle the spiral arm
|
||||
x = inverse[i]*floor(2*self.sweeping_angle / (2*pi)) * 2*pi
|
||||
points_layer0, arm_length = self.shape.compute_spiral(a1=start_angle, a2=fold_angle, fn=circle_segments)
|
||||
points_layer0, arm_length, angle_refs_layer0 = self.shape.compute_spiral(a1=start_angle, a2=fold_angle, fn=circle_segments)
|
||||
x0, y0 = points_layer0[0]
|
||||
xn, yn = points_layer0[-1]
|
||||
if angle_refs_layer0:
|
||||
ref_0, ref_n = angle_refs_layer0[0], angle_refs_layer0[-1]
|
||||
else:
|
||||
ref_0, ref_n = None, None
|
||||
try:
|
||||
if not self.approximate_arcs:
|
||||
raise ValueError()
|
||||
|
|
@ -416,7 +425,7 @@ class PlanarInductor():
|
|||
|
||||
if self.layers > 1:
|
||||
# Handle the returning arm on the bottom layer
|
||||
points_layer1, _ = self.shape.compute_spiral(a1=end_angle, a2=fold_angle, fn=circle_segments)
|
||||
points_layer1, _, angle_refs_layer1 = self.shape.compute_spiral(a1=end_angle, a2=fold_angle, fn=circle_segments)
|
||||
points_layer1 = points_layer1[::-1]
|
||||
try:
|
||||
if not self.approximate_arcs:
|
||||
|
|
@ -430,18 +439,26 @@ class PlanarInductor():
|
|||
xq, yq = shape.project_point(shape.outer_radius, fold_angle)
|
||||
points_layer1 = [(xn, yn), (xq, yq)]
|
||||
footprint.lines.append(kicad.make_line(xn, yn, xq, yq, self.trace_width, self.layer_pair[1]))
|
||||
|
||||
arms_layers[0].append(points_layer0)
|
||||
arms_layers[1].append(points_layer1)
|
||||
|
||||
for i in range(self.twists):
|
||||
start_angle = i*self.sector_angle
|
||||
fold_angle = start_angle + self.sweeping_angle
|
||||
end_angle = fold_angle + self.sweeping_angle
|
||||
|
||||
# Handle inner via ring and process staggering if enabled
|
||||
r = self.inner_via_ring_radius
|
||||
if self.stagger_inner_vias:
|
||||
if i%2 != 0:
|
||||
r -= 2*self.via_offset
|
||||
|
||||
xv, yv = self.shape.project_point(r, fold_angle)
|
||||
xv, yv = self.shape.project_point(r, fold_angle, r_ref=ref_n)
|
||||
|
||||
if self.via_offset:
|
||||
footprint.lines.append(kicad.make_line(*points_layer0[-1], xv, yv, self.trace_width, self.layer_pair[0]))
|
||||
footprint.lines.append(kicad.make_line(xv, yv, *points_layer1[0], self.trace_width, self.layer_pair[1]))
|
||||
footprint.lines.append(kicad.make_line(*arms_layers[0][i][-1], xv, yv, self.trace_width, self.layer_pair[0]))
|
||||
footprint.lines.append(kicad.make_line(xv, yv, *arms_layers[1][i][0], self.trace_width, self.layer_pair[1]))
|
||||
|
||||
footprint.pads.append(kicad.make_via(xv, yv,
|
||||
self.via_diameter, self.via_drill, self.clearance,
|
||||
|
|
@ -456,21 +473,22 @@ class PlanarInductor():
|
|||
if i%2 != 0:
|
||||
r += 2*self.via_offset
|
||||
|
||||
xv, yv = self.shape.project_point(r, start_angle)
|
||||
xv, yv = self.shape.project_point(r, start_angle, r_ref=ref_0)
|
||||
|
||||
if self.via_offset:
|
||||
footprint.lines.append(kicad.make_line(x0, y0, xv, yv, self.trace_width, self.layer_pair[0]))
|
||||
footprint.lines.append(kicad.make_line(x0, y0, xv, yv, self.trace_width, self.layer_pair[1]))
|
||||
footprint.lines.append(kicad.make_line(*arms_layers[0][i][0], xv, yv, self.trace_width, self.layer_pair[0]))
|
||||
footprint.lines.append(kicad.make_line(*arms_layers[1][(i - self.turns) % self.twists][-1], xv, yv, self.trace_width, self.layer_pair[1]))
|
||||
|
||||
footprint.pads.append(kicad.make_via(xv, yv,
|
||||
self.via_diameter, self.via_drill, self.clearance,
|
||||
self.layer_pair))
|
||||
|
||||
# Place the pads on the outer radius
|
||||
px, py = self.shape.project_point(self.shape.outer_radius, 0)
|
||||
footprint.pads.extend([
|
||||
kicad.make_pad(1, [self.layer_pair[0]], px, py, self.trace_width, self.clearance),
|
||||
kicad.make_pad(2, [self.layer_pair[1]], px, py, self.trace_width, self.clearance)])
|
||||
|
||||
else:
|
||||
# Place the pads on the outer radius
|
||||
px, py = self.shape.project_point(self.shape.outer_radius, 0)
|
||||
footprint.pads.extend([
|
||||
kicad.make_pad(1, [self.layer_pair[0]], px, py, self.trace_width, self.clearance),
|
||||
kicad.make_pad(2, [self.layer_pair[1]], px, py, self.trace_width, self.clearance)])
|
||||
|
||||
if self.keepout_zone:
|
||||
pts = self.shape.offset_exterior(self.keepout_margin)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,13 @@ import itertools
|
|||
from py_straight_skeleton import compute_skeleton
|
||||
|
||||
|
||||
def interpolate(p1, p2, t, t_start, t_end):
|
||||
def interpolate(p1, p2, t, t_start=0, t_end=1):
|
||||
""" Interpolate along the line from point p1 to point p2 using t as the interpolation variable.
|
||||
t_start and t_end set the bounds of t, t_start at p1, and t_end at p2.
|
||||
When both interval ends coincide, clips and returns p1.
|
||||
"""
|
||||
if math.isclose(t_start, t_end):
|
||||
return p1
|
||||
t_range = t_end - t_start
|
||||
t = (t - t_start) / t_range
|
||||
x1, y1 = p1
|
||||
|
|
@ -12,11 +18,21 @@ def interpolate(p1, p2, t, t_start, t_end):
|
|||
dx, dy = x2 - x1, y2 - y1
|
||||
return (x1 + t*dx, y1 + t*dy)
|
||||
|
||||
|
||||
def approx_in_range(value, lower, upper):
|
||||
""" Approximate range check """
|
||||
if math.isclose(value, lower) or math.isclose(value, upper):
|
||||
return True
|
||||
return lower <= value <= upper
|
||||
|
||||
|
||||
def edge_cycle(points):
|
||||
""" From a list of points return an iterator of all edges assuming they are a closed loop:
|
||||
[A B C] -> [AB BC CA]
|
||||
"""
|
||||
return itertools.pairwise(itertools.chain(points, points[:1]))
|
||||
|
||||
|
||||
class Skeletonator:
|
||||
def __init__(self, poly):
|
||||
self.poly = poly
|
||||
|
|
@ -49,21 +65,27 @@ class Skeletonator:
|
|||
p0 = (n0.position.x, n0.position.y)
|
||||
p1 = (n1.position.x, n1.position.y)
|
||||
return (n0, n1), interpolate(p0, p1, t, n0.time, n1.time)
|
||||
print(p, r, n0, n1)
|
||||
else:
|
||||
raise ValueError('r is out of bounds!')
|
||||
|
||||
def calc_circumference(self, r):
|
||||
projected = [self.project_arc(p, r)[1] for p in self.poly]
|
||||
return sum(math.dist(p1, p2) for p1, p2 in zip(projected, projected[1:] + projected[:1]))
|
||||
|
||||
def project_point(self, t, r=None):
|
||||
def project_point(self, t, r=None, r_ref=None):
|
||||
t %= 1
|
||||
if r is None:
|
||||
r = self.radius
|
||||
|
||||
if r_ref is None:
|
||||
r_ref = r
|
||||
|
||||
t_start = 0
|
||||
p_cur = None
|
||||
for p1, p2 in self.poly_edges:
|
||||
edge_frac = math.dist(p1, p2) / self.circumference
|
||||
_arcs, points_at_r = self.map_circumference(r_ref)
|
||||
circumference_at_r = sum(math.dist(p1, p2) for p1, p2 in edge_cycle(points_at_r))
|
||||
for (p1, p2), (p1r, p2r) in zip(self.poly_edges, edge_cycle(points_at_r)):
|
||||
edge_frac = math.dist(p1r, p2r) / circumference_at_r
|
||||
t_end = t_start + edge_frac
|
||||
|
||||
if approx_in_range(t, t_start, t_end):
|
||||
|
|
@ -73,58 +95,55 @@ class Skeletonator:
|
|||
|
||||
t_start = t_end
|
||||
|
||||
def map_circumference(self, r):
|
||||
points, arcs = [], []
|
||||
for p in self.poly:
|
||||
arc, pt = self.project_arc(p, r)
|
||||
arcs.append(arc)
|
||||
points.append(pt)
|
||||
return arcs, points
|
||||
|
||||
def do_spiral(self, t1, t2, r1=None, r2=None):
|
||||
if r1 is None:
|
||||
r1 = self.radius
|
||||
if r2 is None:
|
||||
r2 = self.radius - self.min_radius
|
||||
r2 = self.min_radius
|
||||
|
||||
if t2 < t1:
|
||||
t1, t2 = t2, t1
|
||||
r1, r2 = r2, r1
|
||||
|
||||
r_interpolate = lambda t: r1 + (r2 - r1) * ((t - t1) / (t2 - t1))
|
||||
|
||||
yield self.project_point(t1, r1)
|
||||
def r_interpolate(t):
|
||||
t = max(t1, min(t2, t)) # Clip to start/end of spiral
|
||||
f = (t - t1) / (t2 - t1)
|
||||
return r1 + (r2 - r1) * f
|
||||
|
||||
edge_iter = itertools.cycle(self.poly_edges)
|
||||
for t_start in range(math.floor(t1), math.ceil(t2)):
|
||||
t_end = t_start + 1
|
||||
r_outer = r_interpolate(t_start)
|
||||
r_inner = r_interpolate(t_end)
|
||||
oc_arcs, outer_circumference = self.map_circumference(r_outer)
|
||||
ic_arcs, inner_circumference = self.map_circumference(r_inner)
|
||||
|
||||
circumference = self.circumference
|
||||
t_start = 0
|
||||
t = t1
|
||||
for p1, p2 in edge_iter:
|
||||
edge_frac = math.dist(p1, p2) / circumference
|
||||
t_end = t_start + edge_frac
|
||||
|
||||
if approx_in_range(t1, t_start, t_end):
|
||||
if t2 > t_end:
|
||||
yield self.project_arc(p2, r_interpolate(t_end))[1]
|
||||
t_start = t_end
|
||||
break
|
||||
angle = t_start
|
||||
circumference_angles = []
|
||||
inner_circumference_sum = sum(math.dist(p1, p2) for p1, p2 in edge_cycle(inner_circumference))
|
||||
point_angles = []
|
||||
for p1, p2 in edge_cycle(inner_circumference):
|
||||
edge_angle = math.dist(p1, p2) / inner_circumference_sum
|
||||
point_angles.append(angle)
|
||||
angle += edge_angle
|
||||
point_angles.append(t_end)
|
||||
|
||||
t_start = t_end
|
||||
|
||||
last_arc = None
|
||||
r_end = r1
|
||||
for p1, p2 in edge_iter:
|
||||
_arc, p1_proj = self.project_arc(p1, r_end)
|
||||
_arc, p2_proj = self.project_arc(p2, r_end)
|
||||
edge_frac = math.dist(p1_proj, p2_proj) / circumference
|
||||
t_end = t_start + edge_frac
|
||||
r_end = r_interpolate(t_end)
|
||||
print(f'{t1=} {t2=} {t=} {t_start=} {t_end=} | {edge_frac=} | {r_end=}')
|
||||
|
||||
if p2 == self.poly[0]:
|
||||
circumference = self.calc_circumference(r_end)
|
||||
|
||||
if t2 > t_end:
|
||||
arc, point = self.project_arc(p2, r_end)
|
||||
if arc != last_arc:
|
||||
last_arc = arc
|
||||
yield point
|
||||
else:
|
||||
break
|
||||
|
||||
t_start = t_end
|
||||
|
||||
yield interpolate(p1_proj, p2_proj, t2, t_start, t_end)
|
||||
for (p1, p2), (tp1, tp2) in zip(self.poly_edges, itertools.pairwise(point_angles)):
|
||||
rp1 = r_interpolate(tp1)
|
||||
rp2 = r_interpolate(tp2)
|
||||
_arc, p1_proj = self.project_arc(p1, rp1)
|
||||
_arc, p2_proj = self.project_arc(p2, rp2)
|
||||
|
||||
if approx_in_range(t1, tp1, tp2):
|
||||
yield interpolate(p1_proj, p2_proj, t1, tp1, tp2), r_inner
|
||||
if approx_in_range(t2, tp1, tp2):
|
||||
yield interpolate(p1_proj, p2_proj, t2, tp1, tp2), r_inner
|
||||
elif approx_in_range(tp2, t1, t2):
|
||||
yield p2_proj, r_inner
|
||||
Loading…
Add table
Add a link
Reference in a new issue