Trace connectivity WIP
This commit is contained in:
parent
5f1350d4f4
commit
d2143bdf4d
4 changed files with 146 additions and 48 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue