Upstream python2 code
parent
0a519d3be4
commit
9135f9d215
|
@ -0,0 +1,108 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#Copyright (C) 2014 Chris Hinsley All Rights Reserved
|
||||
|
||||
import math, mymath
|
||||
|
||||
class Layer():
|
||||
def __init__(self, dimensions, scale):
|
||||
self.width, self.height = dimensions
|
||||
self.scale = scale
|
||||
self.buckets = [[] for x in xrange(self.width * self.height)]
|
||||
self.test = 0
|
||||
|
||||
def aabb(self, line):
|
||||
x1, y1, x2, y2, r, g = line
|
||||
if x1 > x2:
|
||||
x1, x2 = x2, x1
|
||||
if y1 > y2:
|
||||
y1, y2 = y2, y1
|
||||
r += g
|
||||
minx = int(math.floor((x1 - r) * self.scale))
|
||||
miny = int(math.floor((y1 - r) * self.scale))
|
||||
maxx = int(math.ceil((x2 + r) * self.scale))
|
||||
maxy = int(math.ceil((y2 + r) * self.scale))
|
||||
if minx < 0:
|
||||
minx = 0
|
||||
if miny < 0:
|
||||
miny = 0
|
||||
if maxx > self.width:
|
||||
maxx = self.width
|
||||
if maxy > self.height:
|
||||
maxy = self.height
|
||||
return (minx, miny, maxx, maxy)
|
||||
|
||||
def all_buckets(self, aabb):
|
||||
x1, y1, x2, y2 = aabb
|
||||
for y in xrange(y1, y2):
|
||||
for x in xrange(x1, x2):
|
||||
yield self.buckets[y * self.width + x]
|
||||
|
||||
def all_not_empty_buckets(self, aabb):
|
||||
x1, y1, x2, y2 = aabb
|
||||
for y in xrange(y1, y2):
|
||||
for x in xrange(x1, x2):
|
||||
bucket = self.buckets[y * self.width + x]
|
||||
if bucket:
|
||||
yield bucket
|
||||
|
||||
def add_line(self, line):
|
||||
new_record = [0, line]
|
||||
for bucket in self.all_buckets(self.aabb(line)):
|
||||
found = False
|
||||
for record in bucket:
|
||||
if record[1] == line:
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
bucket.append(new_record)
|
||||
|
||||
def sub_line(self, line):
|
||||
for bucket in self.all_not_empty_buckets(self.aabb(line)):
|
||||
for i in xrange(len(bucket) - 1, -1, -1):
|
||||
if bucket[i][1] == line:
|
||||
del bucket[i]
|
||||
|
||||
def hit_line(self, line):
|
||||
self.test += 1
|
||||
for bucket in self.all_not_empty_buckets(self.aabb(line)):
|
||||
for record in bucket:
|
||||
if record[0] != self.test:
|
||||
record[0] = self.test
|
||||
l1_p1x, l1_p1y, l1_p2x, l1_p2y, l1_r, l1_g = line
|
||||
l2_p1x, l2_p1y, l2_p2x, l2_p2y, l2_r, l2_g = record[1]
|
||||
r = l1_r + l2_r + max(l1_g, l2_g)
|
||||
if mymath.collide_thick_lines_2d((l1_p1x, l1_p1y), (l1_p2x, l1_p2y), (l2_p1x, l2_p1y), (l2_p2x, l2_p2y), r):
|
||||
return True
|
||||
return False
|
||||
|
||||
class Layers():
|
||||
def __init__(self, dimensions, scale):
|
||||
width, height, self.depth = dimensions
|
||||
self.layers = [Layer((width, height), scale) for z in xrange(self.depth)]
|
||||
|
||||
def all_layers(self, z1, z2):
|
||||
if z1 != z2:
|
||||
for z in xrange(self.depth):
|
||||
yield self.layers[z]
|
||||
else:
|
||||
yield self.layers[int(z1)]
|
||||
|
||||
def add_line(self, p1, p2, r, g):
|
||||
x1, y1, z1 = p1
|
||||
x2, y2, z2 = p2
|
||||
for layer in self.all_layers(z1, z2):
|
||||
layer.add_line((x1, y1, x2, y2, r, g))
|
||||
|
||||
def sub_line(self, p1, p2, r, g):
|
||||
x1, y1, z1 = p1
|
||||
x2, y2, z2 = p2
|
||||
for layer in self.all_layers(z1, z2):
|
||||
layer.sub_line((x1, y1, x2, y2, r, g))
|
||||
|
||||
def hit_line(self, p1, p2, r, g):
|
||||
x1, y1, z1 = p1
|
||||
x2, y2, z2 = p2
|
||||
for layer in self.all_layers(z1, z2):
|
||||
if layer.hit_line((x1, y1, x2, y2, r, g)):
|
||||
return True
|
||||
return False
|
|
@ -0,0 +1,337 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#Copyright (C) 2014 Chris Hinsley All Rights Reserved
|
||||
|
||||
import math, random, itertools
|
||||
|
||||
#generic distance metric stuff
|
||||
|
||||
def manhattan_distance(p1, p2):
|
||||
return sum(abs(x - y) for x, y in itertools.izip(p1, p2))
|
||||
|
||||
def euclidean_distance(p1, p2):
|
||||
return math.sqrt(sum((x - y) ** 2 for x, y in itertools.izip(p1, p2)))
|
||||
|
||||
def squared_euclidean_distance(p1, p2):
|
||||
return sum((x - y) ** 2 for x, y in itertools.izip(p1, p2))
|
||||
|
||||
def chebyshev_distance(p1, p2):
|
||||
return max(abs(x - y) for x, y in itertools.izip(p1, p2))
|
||||
|
||||
def reciprical_distance(p1, p2):
|
||||
d = manhattan_distance(p1, p2)
|
||||
return 1.0 / d if d != 0 else 1.0
|
||||
|
||||
def random_distance(p1, p2):
|
||||
return random.random()
|
||||
|
||||
#generic vector stuff
|
||||
|
||||
def sign(x):
|
||||
if x == 0:
|
||||
return 0
|
||||
if x < 0:
|
||||
return -1
|
||||
return 1
|
||||
|
||||
def equal(p1, p2):
|
||||
return manhattan_distance(p1, p2) == 0.0
|
||||
|
||||
def add(p1, p2):
|
||||
return tuple(x + y for x, y in itertools.izip(p1, p2))
|
||||
|
||||
def sub(p1, p2):
|
||||
return tuple(x - y for x, y in itertools.izip(p1, p2))
|
||||
|
||||
def scale(p, s):
|
||||
return tuple(x * s for x in p)
|
||||
|
||||
def dot(p1, p2):
|
||||
return sum(x * y for x, y in itertools.izip(p1, p2))
|
||||
|
||||
def length(p):
|
||||
return math.sqrt(dot(p, p))
|
||||
|
||||
def distance(p1, p2):
|
||||
return length(sub(p2, p1))
|
||||
|
||||
def distance_squared(p1, p2):
|
||||
p = sub(p2, p1)
|
||||
return dot(p, p)
|
||||
|
||||
def norm(p):
|
||||
l = length(p)
|
||||
if l == 0:
|
||||
return scale(p, 0)
|
||||
l = 1.0 / l
|
||||
return scale(p, l)
|
||||
|
||||
def distance_to_line(p, p1, p2):
|
||||
lv = sub(p2, p1)
|
||||
pv = sub(p, p1)
|
||||
c1 = dot(pv, lv)
|
||||
if c1 <= 0:
|
||||
return distance(p, p1)
|
||||
c2 = dot(lv, lv)
|
||||
if c2 <= c1:
|
||||
return distance(p, p2)
|
||||
return distance(p, add(p1, scale(lv, c1 / float(c2))))
|
||||
|
||||
def distance_squared_to_line(p, p1, p2):
|
||||
lv = sub(p2, p1)
|
||||
pv = sub(p, p1)
|
||||
c1 = dot(pv, lv)
|
||||
if c1 <= 0:
|
||||
return distance_squared(p, p1)
|
||||
c2 = dot(lv, lv)
|
||||
if c2 <= c1:
|
||||
return distance_squared(p, p2)
|
||||
return distance_squared(p, add(p1, scale(lv, c1 / float(c2))))
|
||||
|
||||
#specific vector stuff
|
||||
|
||||
def add_2d(p1, p2):
|
||||
x1, y1 = p1
|
||||
x2, y2 = p2
|
||||
return x1 + x2, y1 + y2
|
||||
|
||||
def add_3d(p1, p2):
|
||||
x1, y1, z1 = p1
|
||||
x2, y2, z2 = p2
|
||||
return x1 + x2, y1 + y2, z1 + z2
|
||||
|
||||
def sub_2d(p1, p2):
|
||||
x1, y1 = p1
|
||||
x2, y2 = p2
|
||||
return x1 - x2, y1 - y2
|
||||
|
||||
def sub_3d(p1, p2):
|
||||
x1, y1, z1 = p1
|
||||
x2, y2, z2 = p2
|
||||
return x1 - x2, y1 - y2, z1 - z2
|
||||
|
||||
def scale_2d(p, s):
|
||||
x, y = p
|
||||
return x * s, y * s
|
||||
|
||||
def scale_3d(p, s):
|
||||
x, y, z = p
|
||||
return x * s, y * s, z * s
|
||||
|
||||
def perp_2d(p):
|
||||
x, y = p
|
||||
return y, -x
|
||||
|
||||
def cross_3d(p1, p2):
|
||||
x1, y1, z1 = p1
|
||||
x2, y2, z2 = p2
|
||||
return (y1 * z2 - z1 * y2, z1 * x2 - x1 * z2, x1 * y2 - y1 * x2)
|
||||
|
||||
def dot_2d(p1, p2):
|
||||
x1, y1 = p1
|
||||
x2, y2 = p2
|
||||
return x1 * x2 + y1 * y2
|
||||
|
||||
def dot_3d(p1, p2):
|
||||
x1, y1, z1 = p1
|
||||
x2, y2, z2 = p2
|
||||
return x1 * x2 + y1 * y2 + z1 * z2
|
||||
|
||||
def length_2d(p):
|
||||
return math.sqrt(dot_2d(p, p))
|
||||
|
||||
def length_3d(p):
|
||||
return math.sqrt(dot_3d(p, p))
|
||||
|
||||
def norm_2d(p):
|
||||
l = length_2d(p)
|
||||
if l == 0:
|
||||
return (0, 0)
|
||||
x, y = p
|
||||
return (x / l, y / l)
|
||||
|
||||
def norm_3d(p):
|
||||
l = length_3d(p)
|
||||
if l == 0:
|
||||
return (0, 0, 0)
|
||||
x, y, z = p
|
||||
return (x / l, y / l, z / l)
|
||||
|
||||
def distance_2d(p1, p2):
|
||||
return length_2d(sub_2d(p2 - p1))
|
||||
|
||||
def distance_3d(p1, p2):
|
||||
return length_3d(sub_3d(p2 - p1))
|
||||
|
||||
def distance_squared_2d(p1, p2):
|
||||
x1, y1 = p1
|
||||
x2, y2 = p2
|
||||
p = x2 - x1, y2 - y1
|
||||
return dot_2d(p, p)
|
||||
|
||||
def distance_squared_3d(p1, p2):
|
||||
x1, y1, z1 = p1
|
||||
x2, y2, z2 = p2
|
||||
p = x2 - x1, y2 - y1, z2 - z1
|
||||
return dot_3d(p, p)
|
||||
|
||||
def distance_to_line_2d(p, p1, p2):
|
||||
lv = sub_2d(p2, p1)
|
||||
pv = sub_2d(p, p1)
|
||||
c1 = dot_2d(pv, lv)
|
||||
if c1 <= 0:
|
||||
return distance_2d(p, p1)
|
||||
c2 = dot_2d(lv, lv)
|
||||
if c2 <= c1:
|
||||
return distance_2d(p, p2)
|
||||
return distance_2d(p, add_2d(p1, scale_2d(lv, c1 / float(c2))))
|
||||
|
||||
def distance_to_line_3d(p, p1, p2):
|
||||
lv = sub_3d(p2, p1)
|
||||
pv = sub_3d(p, p1)
|
||||
c1 = dot_3d(pv, lv)
|
||||
if c1 <= 0:
|
||||
return distance_3d(p, p1)
|
||||
c2 = dot_3d(lv, lv)
|
||||
if c2 <= c1:
|
||||
return distance_3d(p, p2)
|
||||
return distance_3d(p, add_3d(p1, scale_3d(lv, c1 / float(c2))))
|
||||
|
||||
def distance_squared_to_line_2d(p, p1, p2):
|
||||
lv = sub_2d(p2, p1)
|
||||
pv = sub_2d(p, p1)
|
||||
c1 = dot_2d(pv, lv)
|
||||
if c1 <= 0:
|
||||
return distance_squared_2d(p, p1)
|
||||
c2 = dot_2d(lv, lv)
|
||||
if c2 <= c1:
|
||||
return distance_squared_2d(p, p2)
|
||||
return distance_squared_2d(p, add_2d(p1, scale_2d(lv, c1 / float(c2))))
|
||||
|
||||
def distance_squared_to_line_3d(p, p1, p2):
|
||||
lv = sub_3d(p2, p1)
|
||||
pv = sub_3d(p, p1)
|
||||
c1 = dot_3d(pv, lv)
|
||||
if c1 <= 0:
|
||||
return distance_squared_3d(p, p1)
|
||||
c2 = dot_3d(lv, lv)
|
||||
if c2 <= c1:
|
||||
return distance_squared_3d(p, p2)
|
||||
return distance_squared_3d(p, add_3d(p1, scale_3d(lv, c1 / float(c2))))
|
||||
|
||||
def collide_lines_2d(l1_p1, l1_p2, l2_p1, l2_p2):
|
||||
l1_x1, l1_y1 = l1_p1
|
||||
l1_x2, l1_y2 = l1_p2
|
||||
l2_x1, l2_y1 = l2_p1
|
||||
l2_x2, l2_y2 = l2_p2
|
||||
ax = l1_x2 - l1_x1
|
||||
ay = l1_y2 - l1_y1
|
||||
bx = l2_x1 - l2_x2
|
||||
by = l2_y1 - l2_y2
|
||||
cx = l1_x1 - l2_x1
|
||||
cy = l1_y1 - l2_y1
|
||||
an = by * cx - bx * cy
|
||||
ad = ay * bx - ax * by
|
||||
bn = ax * cy - ay * cx
|
||||
bd = ay * bx - ax * by
|
||||
if (ad == 0 or bd == 0):
|
||||
return False
|
||||
else:
|
||||
if (ad > 0):
|
||||
if (an < 0 or an > ad):
|
||||
return False
|
||||
else:
|
||||
if (an > 0 or an < ad):
|
||||
return False;
|
||||
if (bd > 0):
|
||||
if (bn < 0 or bn > bd):
|
||||
return False
|
||||
else:
|
||||
if (bn > 0 or bn < bd):
|
||||
return False
|
||||
return True
|
||||
|
||||
def collide_thick_lines_2d(tl1_p1, tl1_p2, tl2_p1, tl2_p2, r):
|
||||
if collide_lines_2d(tl1_p1, tl1_p2, tl2_p1, tl2_p2):
|
||||
return True
|
||||
r *= r
|
||||
if distance_squared_to_line_2d(tl2_p1, tl1_p1, tl1_p2) <= r:
|
||||
return True
|
||||
if distance_squared_to_line_2d(tl2_p2, tl1_p1, tl1_p2) <= r:
|
||||
return True
|
||||
if distance_squared_to_line_2d(tl1_p1, tl2_p1, tl2_p2) <= r:
|
||||
return True
|
||||
if distance_squared_to_line_2d(tl1_p2, tl2_p1, tl2_p2) <= r:
|
||||
return True
|
||||
return False
|
||||
|
||||
#generic path stuff
|
||||
|
||||
def thicken_path_2d(points, radius, capstyle, joinstyle):
|
||||
if radius == 0:
|
||||
return points[:] + points[::-1]
|
||||
index = 0
|
||||
step = 1
|
||||
out_points = []
|
||||
resolution = int(math.pi * radius) + 1
|
||||
while True:
|
||||
p1 = points[index]; index += step
|
||||
p2 = points[index]; index += step
|
||||
l2_v = sub_2d(p2, p1)
|
||||
l2_pv = perp_2d(l2_v)
|
||||
l2_npv = norm_2d(l2_pv)
|
||||
rv = scale_2d(l2_npv, radius)
|
||||
if capstyle == 0:
|
||||
#butt cap
|
||||
out_points.append(sub_2d(p1, rv))
|
||||
out_points.append(add_2d(p1, rv))
|
||||
elif capstyle == 1:
|
||||
#square cap
|
||||
p0 = add_2d(p1, perp_2d(rv))
|
||||
out_points.append(sub_2d(p0, rv))
|
||||
out_points.append(add_2d(p0, rv))
|
||||
elif capstyle == 2:
|
||||
#triangle cap
|
||||
out_points.append(sub_2d(p1, rv))
|
||||
out_points.append(add_2d(p1, perp_2d(rv)))
|
||||
out_points.append(add_2d(p1, rv))
|
||||
elif capstyle == 3:
|
||||
#round cap
|
||||
rvx, rvy = rv
|
||||
for i in xrange(resolution + 1):
|
||||
angle = (i * math.pi) / resolution
|
||||
s = math.sin(angle)
|
||||
c = math.cos(angle)
|
||||
rv = rvx * c - rvy * s, rvx * s + rvy * c
|
||||
out_points.append(sub_2d(p1, rv))
|
||||
while index != -1 and index != len(points):
|
||||
p1, l1_v, l1_npv = p2, l2_v, l2_npv
|
||||
p2 = points[index]; index += step
|
||||
l2_v = sub_2d(p2, p1)
|
||||
l2_pv = perp_2d(l2_v)
|
||||
l2_npv = norm_2d(l2_pv)
|
||||
nbv = norm_2d(scale_2d(add_2d(l1_npv, l2_npv), 0.5))
|
||||
c = dot_2d(nbv, norm_2d(l1_v))
|
||||
if c <= 0 or joinstyle == 0:
|
||||
#mitre join
|
||||
s = math.sin(math.acos(c))
|
||||
bv = scale_2d(nbv, radius / s)
|
||||
out_points.append(add_2d(p1, bv))
|
||||
elif joinstyle == 1:
|
||||
#bevel join
|
||||
out_points.append(add_2d(p1, scale_2d(l1_npv, radius)))
|
||||
out_points.append(add_2d(p1, scale_2d(l2_npv, radius)))
|
||||
elif joinstyle == 2:
|
||||
#round join
|
||||
rvx, rvy = scale_2d(l1_npv, radius)
|
||||
theta = math.acos(dot_2d(l1_npv, l2_npv))
|
||||
segs = int((theta / math.pi) * resolution) + 1
|
||||
for i in xrange(segs + 1):
|
||||
angle = (i * theta) / segs
|
||||
s = math.sin(angle)
|
||||
c = math.cos(angle)
|
||||
rv = rvx * c - rvy * s, rvx * s + rvy * c
|
||||
out_points.append(add_2d(p1, rv))
|
||||
if step < 0:
|
||||
break
|
||||
step = -step; index += step
|
||||
return out_points
|
|
@ -0,0 +1,74 @@
|
|||
#!/opt/local/bin/pypy -tt
|
||||
# -*- coding: utf-8 -*-
|
||||
#Copyright (C) 2014 Chris Hinsley All Rights Reserved
|
||||
|
||||
import sys, argparse, router
|
||||
from copy import deepcopy
|
||||
from ast import literal_eval
|
||||
from mymath import *
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description = 'Pcb layout optimizer.', formatter_class = argparse.RawTextHelpFormatter)
|
||||
parser.add_argument('infile', nargs = '?', type = argparse.FileType('r'), default = sys.stdin, help = 'filename, default stdin')
|
||||
parser.add_argument('--t', nargs = 1, type = int, default = [600], help = 'timeout in seconds, default 600')
|
||||
parser.add_argument('--v', nargs = 1, type = int, default = [0], choices = range(0, 2), help = 'verbosity level 0..1, default 0')
|
||||
parser.add_argument('--s', nargs = 1, type = int, default = [1], help = 'number of samples, default 1')
|
||||
parser.add_argument('--r', nargs = 1, type = int, default = [1], choices = range(1, 5), help = 'grid resolution 1..4, default 1')
|
||||
parser.add_argument('--z', nargs = 1, type = int, default = [0], choices = range(0, 2), help = 'minimize vias 0..1, default 0')
|
||||
parser.add_argument('--d', nargs = 1, type = int, default = [0], choices = range(0, 6), \
|
||||
help = 'distance metric 0..5, default 0.\n' \
|
||||
'0 -> manhattan\n1 -> squared_euclidean\n2 -> euclidean\n3 -> chebyshev\n4 -> reciprocal\n5 -> random')
|
||||
parser.add_argument('--fr', nargs = 1, type = int, default = [2], choices = range(1, 6), help = 'flood range 1..5, default 2')
|
||||
parser.add_argument('--xr', nargs = 1, type = int, default = [1], choices = range(0, 6), help = 'even layer x range 0..5, default 1')
|
||||
parser.add_argument('--yr', nargs = 1, type = int, default = [1], choices = range(0, 6), help = 'odd layer y range 0..5, default 1')
|
||||
args = parser.parse_args()
|
||||
|
||||
flood_range = args.fr[0]
|
||||
flood_range_x_even_layer = args.xr[0]
|
||||
flood_range_y_odd_layer = args.yr[0]
|
||||
path_range = flood_range + 0
|
||||
path_range_x_even_layer = flood_range_x_even_layer + 0
|
||||
path_range_y_odd_layer = flood_range_y_odd_layer + 0
|
||||
|
||||
routing_flood_vectors = [[(x, y, 0) for x in xrange(-flood_range_x_even_layer, flood_range_x_even_layer + 1) for y in xrange(-flood_range, flood_range + 1) \
|
||||
if length_2d((x, y)) > 0.1 and length_2d((x, y)) <= flood_range] + [(0, 0, -1), (0, 0, 1)], \
|
||||
[(x, y, 0) for x in xrange(-flood_range, flood_range + 1) for y in xrange(-flood_range_y_odd_layer, flood_range_y_odd_layer + 1) \
|
||||
if length_2d((x, y)) > 0.1 and length_2d((x, y)) <= flood_range] + [(0, 0, -1), (0, 0, 1)]]
|
||||
|
||||
routing_path_vectors = [[(x, y, 0) for x in xrange(-path_range_x_even_layer, path_range_x_even_layer + 1) for y in xrange(-path_range, path_range + 1) \
|
||||
if length_2d((x, y)) > 0.1 and length_2d((x, y)) <= path_range] + [(0, 0, -1), (0, 0, 1)], \
|
||||
[(x, y, 0) for x in xrange(-path_range, path_range + 1) for y in xrange(-path_range_y_odd_layer, path_range_y_odd_layer + 1) \
|
||||
if length_2d((x, y)) > 0.1 and length_2d((x, y)) <= path_range] + [(0, 0, -1), (0, 0, 1)]]
|
||||
|
||||
dfunc = [manhattan_distance, squared_euclidean_distance, euclidean_distance, \
|
||||
chebyshev_distance, reciprical_distance, random_distance][args.d[0]]
|
||||
|
||||
dimensions = literal_eval(args.infile.readline().strip())
|
||||
pcb = router.Pcb(dimensions, routing_flood_vectors, routing_path_vectors, dfunc, args.r[0], args.v[0], args.z[0])
|
||||
for line in args.infile:
|
||||
track = literal_eval(line.strip())
|
||||
if not track:
|
||||
break
|
||||
pcb.add_track(track)
|
||||
args.infile.close()
|
||||
|
||||
pcb.print_pcb()
|
||||
best_cost = None
|
||||
best_pcb = None
|
||||
for i in xrange(args.s[0]):
|
||||
if not pcb.route(args.t[0]):
|
||||
pcb.shuffle_netlist()
|
||||
continue
|
||||
cost = pcb.cost()
|
||||
if best_cost == None or cost < best_cost:
|
||||
best_cost = cost
|
||||
best_pcb = deepcopy(pcb)
|
||||
pcb.shuffle_netlist()
|
||||
if best_pcb != None:
|
||||
best_pcb.print_netlist()
|
||||
best_pcb.print_stats()
|
||||
else:
|
||||
print []
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,296 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#Copyright (C) 2014 Chris Hinsley All Rights Reserved
|
||||
|
||||
import sys, time
|
||||
from array import array
|
||||
from itertools import islice, izip, groupby
|
||||
from random import shuffle
|
||||
from mymath import *
|
||||
from layer import *
|
||||
|
||||
def shift(l, n):
|
||||
n = n % len(l)
|
||||
head = l[:n]
|
||||
del l[:n]
|
||||
l.extend(head)
|
||||
return l
|
||||
|
||||
class Pcb():
|
||||
def __init__(self, dimensions, routing_flood_vectors, routing_path_vectors, dfunc, resolution, verbosity, minz):
|
||||
self.dfunc = dfunc
|
||||
self.verbosity = verbosity
|
||||
self.routing_flood_vectors = routing_flood_vectors
|
||||
self.routing_path_vectors = routing_path_vectors
|
||||
self.layers = Layers(dimensions, 1.0 / resolution)
|
||||
self.width, self.height, self.depth = dimensions
|
||||
self.resolution = resolution
|
||||
self.minz = minz
|
||||
self.width *= resolution
|
||||
self.height *= resolution
|
||||
self.stride = self.width * self.height
|
||||
self.nodes = array('i', [0 for x in xrange(self.stride * self.depth)])
|
||||
self.netlist = []
|
||||
self.deform = {}
|
||||
|
||||
def grid_to_space_point(self, node):
|
||||
if node in self.deform:
|
||||
return self.deform[node]
|
||||
return (float(node[0]), float(node[1]), float(node[2]))
|
||||
|
||||
def set_node(self, node, value):
|
||||
self.nodes[(self.stride * node[2]) + (node[1] * self.width) + node[0]] = value
|
||||
|
||||
def get_node(self, node):
|
||||
return self.nodes[(self.stride * node[2]) + (node[1] * self.width) + node[0]]
|
||||
|
||||
def all_marked(self, vectors, node):
|
||||
x, y, z = node
|
||||
for dx, dy, dz in vectors[z % 2]:
|
||||
nx = x + dx; ny = y + dy; nz = z + dz
|
||||
if (0 <= nx < self.width) and (0 <= ny < self.height) and (0 <= nz < self.depth):
|
||||
mark = self.get_node((nx, ny, nz))
|
||||
if mark != 0:
|
||||
yield (mark, (nx, ny, nz))
|
||||
|
||||
def all_not_marked(self, vectors, node):
|
||||
x, y, z = node
|
||||
for dx, dy, dz in vectors[z % 2]:
|
||||
nx = x + dx; ny = y + dy; nz = z + dz
|
||||
if (0 <= nx < self.width) and (0 <= ny < self.height) and (0 <= nz < self.depth):
|
||||
if self.get_node((nx, ny, nz)) == 0:
|
||||
yield (nx, ny, nz)
|
||||
|
||||
def all_nearer_sorted(self, vectors, node, goal, func):
|
||||
distance = self.get_node(node)
|
||||
sgoal = self.grid_to_space_point(goal)
|
||||
nodes = [(func(self.grid_to_space_point(marked[1]), sgoal), marked[1]) \
|
||||
for marked in self.all_marked(vectors, node) \
|
||||
if (distance - marked[0]) > 0]
|
||||
nodes.sort()
|
||||
for node in nodes:
|
||||
yield node[1]
|
||||
|
||||
def all_not_shorting(self, gather, params, node, radius, via, gap):
|
||||
np = self.grid_to_space_point(node)
|
||||
for new_node in gather(*params):
|
||||
nnp = self.grid_to_space_point(new_node)
|
||||
if node[2] != new_node[2]:
|
||||
if not self.layers.hit_line(np, nnp, via, gap):
|
||||
yield new_node
|
||||
else:
|
||||
if not self.layers.hit_line(np, nnp, radius, gap):
|
||||
yield new_node
|
||||
|
||||
def mark_distances(self, vectors, radius, via, gap, starts, ends = []):
|
||||
distance = 1
|
||||
nodes = list(starts)
|
||||
for node in nodes:
|
||||
self.set_node(node, distance)
|
||||
while nodes:
|
||||
distance += 1
|
||||
new_nodes = []
|
||||
for node in nodes:
|
||||
for new_node in self.all_not_shorting(self.all_not_marked, (vectors, node), node, radius, via, gap):
|
||||
self.set_node(new_node, distance)
|
||||
new_nodes.append(new_node)
|
||||
nodes = new_nodes
|
||||
if 0 not in [self.get_node(node) for node in ends]:
|
||||
break
|
||||
|
||||
def unmark_distances(self):
|
||||
self.nodes = array('i', [0 for x in xrange(self.stride * self.depth)])
|
||||
|
||||
def add_track(self, track):
|
||||
radius, via, gap, net = track
|
||||
self.netlist.append(Net(net, radius, via, gap, self))
|
||||
|
||||
def route(self, timeout):
|
||||
self.remove_netlist()
|
||||
self.unmark_distances()
|
||||
now = time.time()
|
||||
self.netlist.sort(key = lambda i: i.radius, reverse = True)
|
||||
index = 0
|
||||
while index < len(self.netlist):
|
||||
if self.netlist[index].route(self.minz):
|
||||
index += 1
|
||||
else:
|
||||
if index == 0:
|
||||
self.shuffle_netlist()
|
||||
else:
|
||||
self.netlist.insert(0, self.netlist.pop(index))
|
||||
while index != 0:
|
||||
self.netlist[index].remove()
|
||||
self.netlist[index].shuffle_topology()
|
||||
index -= 1
|
||||
if time.time() - now > timeout:
|
||||
return False
|
||||
if self.verbosity >= 1:
|
||||
self.print_netlist()
|
||||
return True
|
||||
|
||||
def cost(self):
|
||||
return sum(len(path) for net in self.netlist for path in net.paths)
|
||||
|
||||
def shuffle_netlist(self):
|
||||
for net in self.netlist:
|
||||
net.remove()
|
||||
net.shuffle_topology()
|
||||
shuffle(self.netlist)
|
||||
|
||||
def remove_netlist(self):
|
||||
for net in self.netlist:
|
||||
net.remove()
|
||||
|
||||
def print_netlist(self):
|
||||
for net in self.netlist:
|
||||
net.print_net()
|
||||
print []
|
||||
sys.stdout.flush()
|
||||
|
||||
def print_pcb(self):
|
||||
scale = 1.0 / self.resolution
|
||||
print [self.width * scale, self.height * scale, self.depth]
|
||||
sys.stdout.flush()
|
||||
|
||||
def print_stats(self):
|
||||
num_vias = 0
|
||||
num_terminals = 0
|
||||
num_nets = len(self.netlist)
|
||||
for net in self.netlist:
|
||||
num_terminals += len(net.terminals)
|
||||
for path in net.paths:
|
||||
for a, b in izip(path, islice(path, 1, None)):
|
||||
if a[2] != b[2]:
|
||||
num_vias += 1
|
||||
print >> sys.stderr, "Number of Terminals:", num_terminals
|
||||
print >> sys.stderr, "Number of Nets:", num_nets
|
||||
print >> sys.stderr, "Number of Vias:", num_vias
|
||||
|
||||
class Net():
|
||||
def __init__(self, terminals, radius, via, gap, pcb):
|
||||
self.pcb = pcb
|
||||
self.terminals = [(r * pcb.resolution, g * pcb.resolution, \
|
||||
(x * pcb.resolution, y * pcb.resolution, z), \
|
||||
[(cx * pcb.resolution, cy * pcb.resolution) for cx, cy in s]) \
|
||||
for r, g, (x, y, z), s in terminals]
|
||||
self.radius = radius * pcb.resolution
|
||||
self.via = via * pcb.resolution
|
||||
self.gap = gap * pcb.resolution
|
||||
self.paths = []
|
||||
self.remove()
|
||||
for term in self.terminals:
|
||||
for z in xrange(pcb.depth):
|
||||
pcb.deform[(int(term[2][0] + 0.5), int(term[2][1] + 0.5), z)] = (term[2][0], term[2][1], float(z))
|
||||
|
||||
def optimise_paths(self, paths):
|
||||
opt_paths = []
|
||||
for path in paths:
|
||||
opt_path = []
|
||||
d = (0, 0, 0)
|
||||
for a, b in izip(path, islice(path, 1, None)):
|
||||
p0 = self.pcb.grid_to_space_point(a)
|
||||
p1 = self.pcb.grid_to_space_point(b)
|
||||
d1 = norm_3d(sub_3d(p1, p0))
|
||||
if d1 != d:
|
||||
opt_path.append(a)
|
||||
d = d1
|
||||
opt_path.append(path[-1])
|
||||
opt_paths.append(opt_path)
|
||||
return opt_paths
|
||||
|
||||
def shuffle_topology(self):
|
||||
shuffle(self.terminals)
|
||||
|
||||
def add_paths_collision_lines(self):
|
||||
for path in self.paths:
|
||||
for a, b in izip(path, islice(path, 1, None)):
|
||||
p0 = self.pcb.grid_to_space_point(a)
|
||||
p1 = self.pcb.grid_to_space_point(b)
|
||||
if a[2] != b[2]:
|
||||
self.pcb.layers.add_line(p0, p1, self.via, self.gap)
|
||||
else:
|
||||
self.pcb.layers.add_line(p0, p1, self.radius, self.gap)
|
||||
|
||||
def sub_paths_collision_lines(self):
|
||||
for path in self.paths:
|
||||
for a, b in izip(path, islice(path, 1, None)):
|
||||
p0 = self.pcb.grid_to_space_point(a)
|
||||
p1 = self.pcb.grid_to_space_point(b)
|
||||
if a[2] != b[2]:
|
||||
self.pcb.layers.sub_line(p0, p1, self.via, self.gap)
|
||||
else:
|
||||
self.pcb.layers.sub_line(p0, p1, self.radius, self.gap)
|
||||
|
||||
def add_terminal_collision_lines(self):
|
||||
for node in self.terminals:
|
||||
r, g, (x, y, _), s = node
|
||||
if not s:
|
||||
self.pcb.layers.add_line((x, y, 0), (x, y, self.pcb.depth - 1), r, g)
|
||||
else:
|
||||
for z in xrange(self.pcb.depth):
|
||||
for a, b in izip(s, islice(s, 1, None)):
|
||||
self.pcb.layers.add_line((x + a[0], y + a[1], z), (x + b[0], y + b[1], z), r, g)
|
||||
|
||||
def sub_terminal_collision_lines(self):
|
||||
for node in self.terminals:
|
||||
r, g, (x, y, _), s = node
|
||||
if not s:
|
||||
self.pcb.layers.sub_line((x, y, 0), (x, y, self.pcb.depth - 1), r, g)
|
||||
else:
|
||||
for z in xrange(self.pcb.depth):
|
||||
for a, b in izip(s, islice(s, 1, None)):
|
||||
self.pcb.layers.sub_line((x + a[0], y + a[1], z), (x + b[0], y + b[1], z), r, g)
|
||||
|
||||
def remove(self):
|
||||
self.sub_paths_collision_lines()
|
||||
self.sub_terminal_collision_lines()
|
||||
self.paths = []
|
||||
self.add_terminal_collision_lines()
|
||||
|
||||
def route(self, minz):
|
||||
try:
|
||||
self.paths = []
|
||||
self.sub_terminal_collision_lines()
|
||||
visited = set()
|
||||
for index in xrange(1, len(self.terminals)):
|
||||
visited |= set([(int(self.terminals[index - 1][2][0]+0.5), int(self.terminals[index - 1][2][1]+0.5), z) for z in xrange(self.pcb.depth)])
|
||||
ends = [(int(self.terminals[index][2][0]+0.5), int(self.terminals[index][2][1]+0.5), z) for z in xrange(self.pcb.depth)]
|
||||
self.pcb.mark_distances(self.pcb.routing_flood_vectors, self.radius, self.via, self.gap, visited, ends)
|
||||
ends = [(self.pcb.get_node(node), node) for node in ends]
|
||||
ends.sort()
|
||||
_, end = ends[0]
|
||||
path = [end]
|
||||
while path[-1] not in visited:
|
||||
nearer_nodes = self.pcb.all_not_shorting(self.pcb.all_nearer_sorted, \
|
||||
(self.pcb.routing_path_vectors, path[-1], end, self.pcb.dfunc), path[-1], self.radius, self.via, self.gap)
|
||||
next_node = next(nearer_nodes)
|
||||
if minz:
|
||||
for node in nearer_nodes:
|
||||
if node[2] == path[-1][2]:
|
||||
next_node = node
|
||||
break
|
||||
path.append(next_node)
|
||||
visited |= set(path)
|
||||
self.paths.append(path)
|
||||
self.pcb.unmark_distances()
|
||||
self.paths = self.optimise_paths(self.paths)
|
||||
self.add_paths_collision_lines()
|
||||
self.add_terminal_collision_lines()
|
||||
return True
|
||||
except StopIteration:
|
||||
self.pcb.unmark_distances()
|
||||
self.remove()
|
||||
return False
|
||||
|
||||
def print_net(self):
|
||||
scale = 1.0 / self.pcb.resolution
|
||||
spaths = []
|
||||
for path in self.paths:
|
||||
spath = []
|
||||
for node in path:
|
||||
spath += [self.pcb.grid_to_space_point(node)]
|
||||
spaths += [spath]
|
||||
print [self.radius * scale, self.via * scale, self.gap * scale, \
|
||||
[(r * scale, g * scale, (x * scale, y * scale, z), \
|
||||
[(cx * scale, cy * scale) for cx, cy in s]) for r, g, (x, y, z), s in self.terminals], \
|
||||
[[(x * scale, y * scale, z) for x, y, z in spath] for spath in spaths]]
|
|
@ -0,0 +1,224 @@
|
|||
#!/opt/local/bin/python -tt
|
||||
# -*- coding: utf-8 -*-
|
||||
#Copyright (C) 2014 Chris Hinsley All Rights Reserved
|
||||
|
||||
import os, sys, argparse, select, Tkinter, aggdraw
|
||||
from ast import literal_eval
|
||||
from itertools import izip, islice, chain
|
||||
from PIL import Image, ImageTk
|
||||
from mymath import *
|
||||
|
||||
MARGIN = 2
|
||||
|
||||
args = None
|
||||
photo = None
|
||||
image = None
|
||||
|
||||
def split_paths(paths):
|
||||
new_paths = []
|
||||
for path in paths:
|
||||
new_path = []
|
||||
for a, b in izip(path, islice(path, 1, None)):
|
||||
_, _, za = a
|
||||
_, _, zb = b
|
||||
if za != zb:
|
||||
if new_path:
|
||||
new_path.append(a)
|
||||
new_paths.append(new_path)
|
||||
new_paths.append([a, b])
|
||||
new_path = []
|
||||
else:
|
||||
new_path.append(a)
|
||||
if new_path:
|
||||
new_path.append(path[-1])
|
||||
new_paths.append(new_path)
|
||||
return new_paths
|
||||
|
||||
def scale_and_split_tracks(tracks, scale):
|
||||
for track in tracks:
|
||||
track[0] *= scale
|
||||
track[1] *= scale
|
||||
track[2] *= scale
|
||||
track[4] = split_paths(track[4])
|
||||
for i in xrange(len(track[3])):
|
||||
r, g, (x, y, z), s = track[3][i]
|
||||
track[3][i] = r * scale, g * scale, ((x + MARGIN) * scale, (y + MARGIN) * scale, z), [(cx * scale, cy * scale) for cx, cy in s]
|
||||
for path in track[4]:
|
||||
for i in xrange(len(path)):
|
||||
x, y, z = path[i]
|
||||
path[i] = (x + MARGIN) * scale, (y + MARGIN) * scale, z
|
||||
|
||||
def get_tracks():
|
||||
tracks = []
|
||||
while True:
|
||||
line = args.infile.readline()
|
||||
if not line:
|
||||
tracks = []
|
||||
break
|
||||
track = literal_eval(line.strip())
|
||||
if not track:
|
||||
break
|
||||
tracks.append(track)
|
||||
return tracks
|
||||
|
||||
def doframe(dimensions, root, canvas, poll):
|
||||
global photo, image
|
||||
|
||||
tracks = get_tracks()
|
||||
if poll:
|
||||
while poll.poll(1):
|
||||
new_tracks = get_tracks()
|
||||
if not new_tracks:
|
||||
break
|
||||
tracks = new_tracks
|
||||
if not tracks:
|
||||
return
|
||||
|
||||
pcb_width, pcb_height, pcb_depth = dimensions
|
||||
scale = args.s[0]
|
||||
scale_and_split_tracks(tracks, scale)
|
||||
pcb_width += MARGIN * 2; pcb_height += MARGIN * 2
|
||||
img_width = int(pcb_width * scale)
|
||||
if args.o[0] == 0:
|
||||
img_height = int(pcb_height * scale)
|
||||
else:
|
||||
img_height = int(pcb_height * pcb_depth * scale)
|
||||
|
||||
canvas.delete("all")
|
||||
image = Image.new("RGB", (img_width, img_height), "black")
|
||||
ctx = aggdraw.Draw(image)
|
||||
black_brush = aggdraw.Brush('black', opacity = 255)
|
||||
white_brush = aggdraw.Brush('white', opacity = 255)
|
||||
red_brush = aggdraw.Brush('red', opacity = 255)
|
||||
|
||||
if args.o[0] == 0:
|
||||
colors = ['red', 'green', 'blue', 'yellow', 'fuchsia', 'aqua']
|
||||
for depth in xrange(pcb_depth - 1, -1, -1):
|
||||
brush = aggdraw.Brush(colors[depth % len(colors)], opacity = 128)
|
||||
for track in tracks:
|
||||
radius, via, gap, terminals, paths = track
|
||||
for path in paths:
|
||||
if path[0][2] == path[-1][2] == depth:
|
||||
points = list(chain.from_iterable(thicken_path_2d([(x, y) for x, y, _ in path], radius, 3, 2)))
|
||||
ctx.polygon(points, brush)
|
||||
for track in tracks:
|
||||
radius, via, gap, terminals, paths = track
|
||||
for path in paths:
|
||||
if path[0][2] != path[-1][2]:
|
||||
x, y, _ = path[0]
|
||||
ctx.ellipse((x - via, y - via, x + via, y + via), white_brush)
|
||||
for r, g, (x, y, _), s in terminals:
|
||||
if not s:
|
||||
ctx.ellipse((x - r, y - r, x + r, y + r), white_brush)
|
||||
else:
|
||||
if r != 0:
|
||||
points = list(chain.from_iterable(thicken_path_2d([(cx + x, cy + y) for cx, cy in s], r, 3, 2)))
|
||||
ctx.polygon(points, white_brush)
|
||||
else:
|
||||
points = list(chain.from_iterable([(cx + x, cy + y) for cx, cy in s]))
|
||||
ctx.polygon(points, white_brush)
|
||||
else:
|
||||
for depth in xrange(pcb_depth):
|
||||
for track in tracks:
|
||||
radius, via, gap, terminals, paths = track
|
||||
for path in paths:
|
||||
if path[0][2] == path[-1][2] == depth:
|
||||
points = list(chain.from_iterable(thicken_path_2d([(x, y + depth * pcb_height * scale) for x, y, _ in path], radius + gap, 3, 2)))
|
||||
ctx.polygon(points, white_brush)
|
||||
for track in tracks:
|
||||
radius, via, gap, terminals, paths = track
|
||||
for path in paths:
|
||||
if path[0][2] != path[-1][2]:
|
||||
x, y, _ = path[0]
|
||||
y += depth * pcb_height * scale
|
||||
ctx.ellipse((x - via - gap, y - via - gap, x + via + gap, y + via + gap), white_brush)
|
||||
for r, g, (x, y, _), s in terminals:
|
||||
y += depth * pcb_height * scale
|
||||
if not s:
|
||||
ctx.ellipse((x - r - g, y - r - g, x + r + g, y + r + g), white_brush)
|
||||
else:
|
||||
points = list(chain.from_iterable(thicken_path_2d([(cx + x, cy + y) for cx, cy in s], r + g, 3, 2)))
|
||||
ctx.polygon(points, white_brush)
|
||||
if r == 0:
|
||||
points = list(chain.from_iterable([(cx + x, cy + y) for cx, cy in s]))
|
||||
ctx.polygon(points, white_brush)
|
||||
for depth in xrange(pcb_depth):
|
||||
for track in tracks:
|
||||
radius, via, gap, terminals, paths = track
|
||||
for path in paths:
|
||||
if path[0][2] == path[-1][2] == depth:
|
||||
points = list(chain.from_iterable(thicken_path_2d([(x, y + depth * pcb_height * scale) for x, y, _ in path], radius, 3, 2)))
|
||||
ctx.polygon(points, black_brush)
|
||||
for track in tracks:
|
||||
radius, via, gap, terminals, paths = track
|
||||
for path in paths:
|
||||
if path[0][2] != path[-1][2]:
|
||||
x, y, _ = path[0]
|
||||
y += depth * pcb_height * scale
|
||||
ctx.ellipse((x - via, y - via, x + via, y + via), black_brush)
|
||||
for r, g, (x, y, _), s in terminals:
|
||||
y += depth * pcb_height * scale
|
||||
if not s:
|
||||
ctx.ellipse((x - r, y - r, x + r, y + r), black_brush)
|
||||
else:
|
||||
if r != 0:
|
||||
points = list(chain.from_iterable(thicken_path_2d([(cx + x, cy + y) for cx, cy in s], r, 3, 2)))
|
||||
ctx.polygon(points, black_brush)
|
||||
else:
|
||||
points = list(chain.from_iterable([(cx + x, cy + y) for cx, cy in s]))
|
||||
ctx.polygon(points, black_brush)
|
||||
ctx.flush()
|
||||
photo = ImageTk.PhotoImage(image)
|
||||
canvas.create_image(0, 0, image = photo, anchor = Tkinter.NW)
|
||||
root.update()
|
||||
root.after(0, doframe, dimensions, root, canvas, poll)
|
||||
|
||||
def about_menu_handler():
|
||||
pass
|
||||
|
||||
def main():
|
||||
global args, image
|
||||
|
||||
parser = argparse.ArgumentParser(description = 'Pcb layout viewer.')
|
||||
parser.add_argument('infile', nargs = '?', type = argparse.FileType('r'), default = sys.stdin, help = 'filename, default stdin')
|
||||
parser.add_argument('--s', nargs = 1, type = int, default = [9], help = 'scale factor, default 9')
|
||||
parser.add_argument('--f', nargs = 1, type = float, default = [100.0], help = 'framerate, default 100.0')
|
||||
parser.add_argument('--i', nargs = 1, default = ['pcb.png'], help = 'filename, default pcb.png')
|
||||
parser.add_argument('--o', nargs = 1, type = int, default = [0], choices=range(0, 2), help = 'overlay modes 0..1, default 0')
|
||||
args = parser.parse_args()
|
||||
|
||||
poll = 0
|
||||
if os.name != 'nt':
|
||||
if args.infile == sys.stdin:
|
||||
poll = select.poll()
|
||||
poll.register(args.infile, select.POLLIN)
|
||||
|
||||
dimensions = literal_eval(args.infile.readline().strip())
|
||||
pcb_width, pcb_height, pcb_depth = dimensions
|
||||
pcb_width += MARGIN * 2; pcb_height += MARGIN * 2
|
||||
scale = args.s[0]
|
||||
pcb_width = int(pcb_width * scale)
|
||||
if args.o[0] == 0:
|
||||
pcb_height = int(pcb_height * scale)
|
||||
else:
|
||||
pcb_height = int(pcb_height * pcb_depth * scale)
|
||||
|
||||
root = Tkinter.Tk()
|
||||
root.maxsize(pcb_width, pcb_height)
|
||||
root.minsize(pcb_width, pcb_height)
|
||||
root.title("PCB Veiwer")
|
||||
menu_bar = Tkinter.Menu(root)
|
||||
sub_menu = Tkinter.Menu(menu_bar)
|
||||
menu_bar.add_cascade(label = 'Help', menu = sub_menu)
|
||||
sub_menu.add_command(label = 'About', command = about_menu_handler)
|
||||
root['menu'] = menu_bar
|
||||
canvas = Tkinter.Canvas(root, width = pcb_width, height = pcb_height)
|
||||
canvas['background'] = 'black'
|
||||
canvas.pack()
|
||||
|
||||
root.after(0, doframe, dimensions, root, canvas, poll)
|
||||
root.mainloop()
|
||||
image.save(args.i[0])
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,220 @@
|
|||
#!/opt/local/bin/python -tt
|
||||
# -*- coding: utf-8 -*-
|
||||
#Copyright (C) 2014 Chris Hinsley All Rights Reserved
|
||||
|
||||
import os, sys, argparse, select
|
||||
from ast import literal_eval
|
||||
from itertools import izip, islice, chain
|
||||
from mymath import *
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.patches as patches
|
||||
import matplotlib.path as path
|
||||
import matplotlib.animation as animation
|
||||
import pylab
|
||||
|
||||
MARGIN = 2
|
||||
|
||||
args = None
|
||||
|
||||
def split_paths(paths):
|
||||
new_paths = []
|
||||
for path in paths:
|
||||
new_path = []
|
||||
for a, b in izip(path, islice(path, 1, None)):
|
||||
_, _, za = a
|
||||
_, _, zb = b
|
||||
if za != zb:
|
||||
if new_path:
|
||||
new_path.append(a)
|
||||
new_paths.append(new_path)
|
||||
new_paths.append([a, b])
|
||||
new_path = []
|
||||
else:
|
||||
new_path.append(a)
|
||||
if new_path:
|
||||
new_path.append(path[-1])
|
||||
new_paths.append(new_path)
|
||||
return new_paths
|
||||
|
||||
def scale_and_split_tracks(tracks, scale):
|
||||
for track in tracks:
|
||||
track[0] *= scale
|
||||
track[1] *= scale
|
||||
track[2] *= scale
|
||||
track[4] = split_paths(track[4])
|
||||
for i in xrange(len(track[3])):
|
||||
r, g, (x, y, z), s = track[3][i]
|
||||
track[3][i] = r * scale, g * scale, ((x + MARGIN) * scale, (y + MARGIN) * scale, z), [(cx * scale, cy * scale) for cx, cy in s]
|
||||
for path in track[4]:
|
||||
for i in xrange(len(path)):
|
||||
x, y, z = path[i]
|
||||
path[i] = (x + MARGIN) * scale, (y + MARGIN) * scale, z
|
||||
|
||||
def get_tracks():
|
||||
tracks = []
|
||||
while True:
|
||||
line = args.infile.readline()
|
||||
if not line:
|
||||
tracks = []
|
||||
break
|
||||
track = literal_eval(line.strip())
|
||||
if not track:
|
||||
break
|
||||
tracks.append(track)
|
||||
return tracks
|
||||
|
||||
def doframe(frame_num, dimensions, poll, fig, ax):
|
||||
tracks = get_tracks()
|
||||
if poll:
|
||||
while poll.poll(1):
|
||||
new_tracks = get_tracks()
|
||||
if not new_tracks:
|
||||
break
|
||||
tracks = new_tracks
|
||||
if not tracks:
|
||||
return
|
||||
|
||||
pcb_width, pcb_height, pcb_depth = dimensions
|
||||
scale = args.s[0]
|
||||
scale_and_split_tracks(tracks, scale)
|
||||
pcb_width += MARGIN * 2; pcb_height += MARGIN * 2
|
||||
img_width = int(pcb_width * scale)
|
||||
if args.o[0] == 0:
|
||||
img_height = int(pcb_height * scale)
|
||||
else:
|
||||
img_height = int(pcb_height * pcb_depth * scale)
|
||||
|
||||
ax.clear()
|
||||
pylab.subplots_adjust(left = 0.0, right = 1.0, bottom = 0.0, top = 1.0)
|
||||
ax.set_xlim([0, img_width])
|
||||
ax.set_ylim([0, img_height][::-1])
|
||||
ax.set(aspect = 1)
|
||||
ax.axis('off')
|
||||
|
||||
if args.o[0] == 0:
|
||||
colors = ['red', 'green', 'blue', 'yellow', 'fuchsia', 'aqua']
|
||||
for depth in xrange(pcb_depth - 1, -1, -1):
|
||||
brush = colors[depth % len(colors)]
|
||||
for track in tracks:
|
||||
radius, via, gap, terminals, paths = track
|
||||
for path in paths:
|
||||
if path[0][2] == path[-1][2] == depth:
|
||||
points = thicken_path_2d([(x, y) for x, y, _ in path], radius, 3, 2)
|
||||
poly = plt.Polygon(points, facecolor = brush, edgecolor = 'none', alpha = 0.5)
|
||||
ax.add_patch(poly)
|
||||
for track in tracks:
|
||||
radius, via, gap, terminals, paths = track
|
||||
for path in paths:
|
||||
if path[0][2] != path[-1][2]:
|
||||
x, y, _ = path[0]
|
||||
circ = plt.Circle((x, y), radius = via, color = 'white')
|
||||
ax.add_patch(circ)
|
||||
for r, g, (x, y, _), s in terminals:
|
||||
if not s:
|
||||
circ = plt.Circle((x, y), radius = r, color = 'white')
|
||||
ax.add_patch(circ)
|
||||
else:
|
||||
if r !=0:
|
||||
points = thicken_path_2d([(cx + x, cy + y) for cx, cy in s], r, 3, 2)
|
||||
poly = plt.Polygon(points, facecolor = 'white', edgecolor = 'none')
|
||||
ax.add_patch(poly)
|
||||
else:
|
||||
points = [(cx + x, cy + y) for cx, cy in s]
|
||||
poly = plt.Polygon(points, facecolor = 'white', edgecolor = 'none')
|
||||
ax.add_patch(poly)
|
||||
else:
|
||||
for depth in xrange(pcb_depth):
|
||||
for track in tracks:
|
||||
radius, via, gap, terminals, paths = track
|
||||
for path in paths:
|
||||
if path[0][2] == path[-1][2] == depth:
|
||||
points = thicken_path_2d([(x, y + depth * pcb_height * scale) for x, y, _ in path], radius + gap, 3, 2)
|
||||
poly = plt.Polygon(points, facecolor = 'white', edgecolor = 'none')
|
||||
ax.add_patch(poly)
|
||||
for track in tracks:
|
||||
radius, via, gap, terminals, paths = track
|
||||
for path in paths:
|
||||
if path[0][2] != path[-1][2]:
|
||||
x, y, _ = path[0]
|
||||
y += depth * pcb_height * scale
|
||||
circ = plt.Circle((x, y), radius = via + gap, color = 'white')
|
||||
ax.add_patch(circ)
|
||||
for r, g, (x, y, _), s in terminals:
|
||||
y += depth * pcb_height * scale
|
||||
if not s:
|
||||
circ = plt.Circle((x, y), radius = r + g, color = 'white')
|
||||
ax.add_patch(circ)
|
||||
else:
|
||||
points = thicken_path_2d([(cx + x, cy + y) for cx, cy in s], r + g, 3, 2)
|
||||
poly = plt.Polygon(points, facecolor = 'white', edgecolor = 'none')
|
||||
ax.add_patch(poly)
|
||||
if r ==0:
|
||||
points = [(cx + x, cy + y) for cx, cy in s]
|
||||
poly = plt.Polygon(points, facecolor = 'white', edgecolor = 'none')
|
||||
ax.add_patch(poly)
|
||||
for depth in xrange(pcb_depth):
|
||||
for track in tracks:
|
||||
radius, via, gap, terminals, paths = track
|
||||
for path in paths:
|
||||
if path[0][2] == path[-1][2] == depth:
|
||||
points = thicken_path_2d([(x, y + depth * pcb_height * scale) for x, y, _ in path], radius, 3, 2)
|
||||
poly = plt.Polygon(points, facecolor = 'black', edgecolor = 'none')
|
||||
ax.add_patch(poly)
|
||||
for track in tracks:
|
||||
radius, via, gap, terminals, paths = track
|
||||
for path in paths:
|
||||
if path[0][2] != path[-1][2]:
|
||||
x, y, _ = path[0]
|
||||
y += depth * pcb_height * scale
|
||||
circ = plt.Circle((x, y), radius = via, color = 'black')
|
||||
ax.add_patch(circ)
|
||||
for r, g, (x, y, _), s in terminals:
|
||||
y += depth * pcb_height * scale
|
||||
if not s:
|
||||
circ = plt.Circle((x, y), radius = r, color = 'black')
|
||||
ax.add_patch(circ)
|
||||
else:
|
||||
if r !=0:
|
||||
points = thicken_path_2d([(cx + x, cy + y) for cx, cy in s], r, 3, 2)
|
||||
poly = plt.Polygon(points, facecolor = 'black', edgecolor = 'none')
|
||||
ax.add_patch(poly)
|
||||
else:
|
||||
points = [(cx + x, cy + y) for cx, cy in s]
|
||||
poly = plt.Polygon(points, facecolor = 'black', edgecolor = 'none')
|
||||
ax.add_patch(poly)
|
||||
return []
|
||||
|
||||
def main():
|
||||
global args
|
||||
|
||||
parser = argparse.ArgumentParser(description = 'Pcb layout viewer.')
|
||||
parser.add_argument('infile', nargs = '?', type = argparse.FileType('r'), default = sys.stdin, help = 'filename, default stdin')
|
||||
parser.add_argument('--s', nargs = 1, type = int, default = [9], help = 'scale factor, default 9')
|
||||
parser.add_argument('--f', nargs = 1, type = float, default = [100.0], help = 'framerate, default 100.0')
|
||||
parser.add_argument('--i', nargs = 1, default = ['pcb.png'], help = 'filename, default pcb.png')
|
||||
parser.add_argument('--o', nargs = 1, type = int, default = [0], choices=range(0, 2), help = 'overlay modes 0..1, default 0')
|
||||
args = parser.parse_args()
|
||||
|
||||
poll = None
|
||||
if os.name != 'nt':
|
||||
if args.infile == sys.stdin:
|
||||
poll = select.poll()
|
||||
poll.register(args.infile, select.POLLIN)
|
||||
|
||||
dimensions = literal_eval(args.infile.readline().strip())
|
||||
pcb_width, pcb_height, pcb_depth = dimensions
|
||||
pcb_width += MARGIN * 2; pcb_height += MARGIN * 2
|
||||
scale = args.s[0]
|
||||
pcb_width = int(pcb_width * scale)
|
||||
if args.o[0] == 0:
|
||||
pcb_height = int(pcb_height * scale)
|
||||
else:
|
||||
pcb_height = int(pcb_height * pcb_depth * scale)
|
||||
|
||||
fig, ax = plt.subplots(frameon = True, facecolor = 'black')
|
||||
ani = animation.FuncAnimation(fig, doframe, fargs = (dimensions, poll, fig, ax), interval = 10, blit = False, repeat = True)
|
||||
plt.show()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue