Trace connectivity WIP

This commit is contained in:
jaseg 2023-09-22 13:30:11 +02:00
parent 5f1350d4f4
commit d2143bdf4d
4 changed files with 146 additions and 48 deletions

View file

@ -431,33 +431,6 @@ class Pad:
""" Find traces and vias of the same net as this pad. """
return self.footprint.board.find_traces(self.net.name, include_vias=include_vias)
def find_connected_traces(self, consider_candidates=5):
board = self.footprint.board
found = set()
search_frontier = [(self.at, 0, self.layer_mask)]
while search_frontier:
coord, size, layers = search_frontier.pop()
x, y = coord.x, coord.y
for cand, attr, cand_size in self.footprint.board.query_trace_index((x, x, y, y), layers,
n=consider_candidates):
if cand in found:
continue
cand_coord = getattr(cand, attr)
cand_x, cand_y = cand_coord.x, cand_coord.y
if math.dist((x, y), (cand_x, cand_y)) <= size/2 + cand_size/2:
found.add(cand)
yield cand
if hasattr(cand, 'at'): # via or pad
search_frontier.append((cand.at, getattr(cand, 'size', 0), cand.layer_mask))
else:
mask = cand.layer_mask
search_frontier.append((cand.start, cand.width, mask))
search_frontier.append((cand.end, cand.width, mask))
def render(self, variables=None, margin=None, cache=None):
#if self.type in (Atom.connect, Atom.np_thru_hole):
# return

View file

@ -8,11 +8,12 @@ from dataclasses import field, KW_ONLY, fields
from itertools import chain
import re
import fnmatch
import functools
from .sexp import *
from .base_types import *
from .primitives import *
from .footprints import Footprint
from .footprints import Footprint, Pad
from . import graphical_primitives as gr
import rtree.index
@ -161,6 +162,11 @@ class TrackSegment:
net: Named(int) = 0
tstamp: Timestamp = field(default_factory=Timestamp)
@classmethod
def from_footprint_line(kls, line, flip=False):
# FIXME flip
return kls(line.start, line.end, line.width or line.stroke.width, line.layer, line.locked, tstamp=line.tstamp)
def __post_init__(self):
self.start = XYCoord(self.start)
self.end = XYCoord(self.end)
@ -385,36 +391,143 @@ class Board:
for field in ('start', 'end'):
obj_id = id(obj)
coord = getattr(obj, field)
_trace_index_map[obj_id] = obj, field, obj.width, obj.layer_mask
idx.insert(obj_id, (coord.x, coord.x, coord.y, coord.y))
id_map[obj_id] = obj, field, obj.width, obj.layer_mask
idx.insert(obj_id, (coord.x, coord.y, coord.x, coord.y))
for fp in self.footprints:
for pad in fp.pads:
obj_id = id(pad)
_trace_index_map[obj_id] = pad, 'at', 0, pad.layer_mask
idx.insert(obj_id, (pad.at.x, pad.at.x, pad.at.y, pad.at.y))
id_map[obj_id] = pad, 'at', 0, pad.layer_mask
idx.insert(obj_id, (pad.at.x, pad.at.y, pad.at.x, pad.at.y))
for via in self.vias:
obj_id = id(via)
_trace_index_map[obj_id] = via, 'at', via.size, via.layer_mask
idx.insert(obj_id, (via.at.x, via.at.x, via.at.y, via.at.y))
id_map[obj_id] = via, 'at', via.size, via.layer_mask
idx.insert(obj_id, (via.at.x, via.at.y, via.at.x, via.at.y))
def query_trace_index(self, point, layers='*.Cu', n=5):
if self._trace_index is None:
self.rebuild_trace_index()
@staticmethod
def _require_trace_index(fun):
@functools.wraps(fun)
def wrapper(self, *args, **kwargs):
if self._trace_index is None:
self.rebuild_trace_index()
if isinstance(layers, str):
layers = [l.strip() for l in layers.split(',')]
return fun(self, *args, **kwargs)
return wrapper
if not isinstance(layers, int):
layers = layer_mask(layers)
@_require_trace_index
def query_trace_index_nearest(self, point, layers='*.Cu', n=1):
layers = layer_mask(layers)
x, y = point
for obj_id in self._trace_index.nearest((x, x, y, y), n):
entry = obj, attr, size, mask = _trace_index_map[obj_id]
for obj_id in self._trace_index.nearest((x, y, x, y), n):
entry = obj, attr, size, mask = self._trace_index_map[obj_id]
if layers & mask:
yield entry
@_require_trace_index
def query_trace_index_tolerance(self, point, layers='*.Cu', tol=10e-6):
layers = layer_mask(layers)
x, y = point
for obj_id in self._trace_index.intersection((x-tol, y-tol, x+tol, y+tol)):
entry = obj, attr, size, mask = self._trace_index_map[obj_id]
attr = getattr(obj, attr)
if layers & mask and math.dist((attr.x, attr.y), (x, y)) <= tol:
yield entry
def find_connected_traces(self, obj, layers='*.Cu', tol=10e-6):
search_frontier = []
visited = set()
def enqueue(obj):
visited.add(id(obj))
if isinstance(obj, (TrackSegment, TrackArc)):
search_frontier.append((obj.start, obj.width, obj.layer_mask))
search_frontier.append((obj.end, obj.width, obj.layer_mask))
elif isinstance(obj, Via):
search_frontier.append((obj.at, obj.size, obj.layer_mask))
elif isinstance(obj, Pad):
search_frontier.append((obj.at, max(obj.size.x, obj.size.y), obj.layer_mask))
elif isinstance(obj, (Footprint)):
for pad in obj.pads:
search_frontier.append((pad.at, max(pad.size.x, pad.size.y), pad.layer_mask))
else:
raise TypeError(f'Finding connected traces for {type(obj)} objects is not (yet) supported.')
enqueue(obj)
filter_layers = layer_mask(layers)
while search_frontier:
coord, size, layers = search_frontier.pop()
x, y = coord.x, coord.y
# First, find all bounding box intersections
found = []
for cand, attr, cand_size, cand_mask in self.query_trace_index_tolerance((x, y), layers&filter_layers, size):
cand_coord = getattr(cand, attr)
dist = math.dist((x, y), (cand_coord.x, cand_coord.y))
if dist <= size/2 + cand_size/2 and layers&cand_mask:
found.append((dist, cand))
if not found:
continue
# Second, filter to match only objects that are within tolerance of closest
min_dist = min(e[0] for e in found)
for dist, cand in found:
if dist < min_dist+tol and id(cand) not in visited:
enqueue(cand)
yield cand
def track_skeleton(self, start, tol=10e-6):
search_frontier = []
visited = set()
def enqueue(obj):
visited.add(id(obj))
if isinstance(obj, (TrackSegment, TrackArc)):
search_frontier.append((obj.start, obj.width, obj.layer_mask))
search_frontier.append((obj.end, obj.width, obj.layer_mask))
elif isinstance(obj, Via):
search_frontier.append((obj.at, obj.size, obj.layer_mask))
elif isinstance(obj, Pad):
search_frontier.append((obj.at, max(obj.size.x, obj.size.y), obj.layer_mask))
else:
raise TypeError(f'Track skeleton starting at {type(obj)} objects is not (yet) supported.')
enqueue(start)
while search_frontier:
coord, size, layers = search_frontier.pop()
x, y = coord.x, coord.y
# First, find all bounding box intersections
found = []
for cand, attr, cand_size, cand_mask in self.query_trace_index_tolerance((x, y), layers, size):
cand_coord = getattr(cand, attr)
dist = math.dist((x, y), (cand_coord.x, cand_coord.y))
if dist <= size/2 + cand_size/2 and layers&cand_mask:
found.append((dist, cand))
if not found:
continue
# Second, filter to match only objects that are within tolerance of closest
min_dist = min(e[0] for e in found)
for dist, cand in found:
if dist < min_dist+tol and id(cand) not in visited:
enqueue(cand)
yield cand
def __after_parse__(self, parent):
self.properties = {prop.key: prop.value for prop in self.properties}

View file

@ -22,6 +22,12 @@ def fuck_layers(layers):
def layer_mask(layers):
if isinstance(layers, int):
return layers
if isinstance(layers, str):
layers = [l.strip() for l in layers.split(',')]
mask = 0
for layer in layers:
match layer:

View file

@ -275,6 +275,7 @@ def generate(outfile, turns, outer_diameter, inner_diameter, via_diameter, via_d
clearance=clearance,
zone_connect=0)
use_arcs = not pcb
pads = []
lines = []
arcs = []
@ -337,9 +338,11 @@ def generate(outfile, turns, outer_diameter, inner_diameter, via_diameter, via_d
path.line(xn, yn)
points.append((xn, yn))
dists.append(dist((xp, yp), (xn, yn)))
#lines.append(make_line(xp, yp, xn, yn, layer_pair[layer]))
if not use_arcs:
lines.append(make_line(xp, yp, xn, yn, layer_pair[layer]))
arcs.extend(arc_approximate(points, layer_pair[layer], arc_tolerance))
if use_arcs:
arcs.extend(arc_approximate(points, layer_pair[layer], arc_tolerance))
svg_stuff.append(Tag('text',
[label],
@ -385,7 +388,7 @@ def generate(outfile, turns, outer_diameter, inner_diameter, via_diameter, via_d
svg_vias.append(Tag('circle', cx=xv, cy=yv, r=via_diameter/2, stroke='none', fill='white'))
svg_vias.append(Tag('circle', cx=xv, cy=yv, r=via_drill/2, stroke='none', fill='black'))
print(f'Approximate track length: {clen*twists*2:.2f} mm')
print(f'Approximate track length: {clen*twists*2:.2f} mm', file=sys.stderr)
pads.append(make_pad(1, [layer_pair[0]], outer_radius, 0))
pads.append(make_pad(2, [layer_pair[1]], outer_radius, 0))
@ -454,9 +457,12 @@ def generate(outfile, turns, outer_diameter, inner_diameter, via_diameter, via_d
if pcb:
obj = kicad_pcb.Board.empty_board(
zones=zones,
lines=[line.to_graphical_primitive() for line in lines],
arcs=[arc.to_graphical_primitive() for arc in arcs],
track_segments=[kicad_pcb.TrackSegment.from_footprint_line(line) for line in lines],
vias=[kicad_pcb.Via.from_pad(pad) for pad in pads if pad.type == kicad_pcb.Atom.thru_hole])
obj.rebuild_trace_index()
seg = obj.track_segments[-1]
for e in obj.find_connected_traces(seg, layers=seg.layer_mask):
print(getattr(e, 'layer', ''), str(e)[:80], file=sys.stderr)
else:
obj = kicad_fp.Footprint(