diff --git a/layer.py b/layer.py new file mode 100644 index 0000000..267cd5b --- /dev/null +++ b/layer.py @@ -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 diff --git a/mymath.py b/mymath.py new file mode 100644 index 0000000..9e9481f --- /dev/null +++ b/mymath.py @@ -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 diff --git a/pcb.py b/pcb.py new file mode 100755 index 0000000..d317a52 --- /dev/null +++ b/pcb.py @@ -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() diff --git a/router.py b/router.py new file mode 100644 index 0000000..6847c68 --- /dev/null +++ b/router.py @@ -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]] diff --git a/view.py b/view.py new file mode 100755 index 0000000..bbc3aac --- /dev/null +++ b/view.py @@ -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() diff --git a/view_mpl.py b/view_mpl.py new file mode 100755 index 0000000..7b886ea --- /dev/null +++ b/view_mpl.py @@ -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()