summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2023-09-22 13:30:11 +0200
committerjaseg <git@jaseg.de>2023-09-22 13:30:11 +0200
commitd2143bdf4df2204701a1dc116572fdf7d12baf05 (patch)
treea27a0bd005fa9897d7149010aa0c87b85d5f17b0
parent5f1350d4f4e1c190bf0b2db12b8183519b2d3413 (diff)
downloadgerbonara-d2143bdf4df2204701a1dc116572fdf7d12baf05.tar.gz
gerbonara-d2143bdf4df2204701a1dc116572fdf7d12baf05.tar.bz2
gerbonara-d2143bdf4df2204701a1dc116572fdf7d12baf05.zip
Trace connectivity WIP
-rw-r--r--gerbonara/cad/kicad/footprints.py27
-rw-r--r--gerbonara/cad/kicad/pcb.py145
-rw-r--r--gerbonara/cad/kicad/primitives.py6
-rw-r--r--twisted_coil_gen_twolayer.py16
4 files changed, 146 insertions, 48 deletions
diff --git a/gerbonara/cad/kicad/footprints.py b/gerbonara/cad/kicad/footprints.py
index 90050ef..7bdeec6 100644
--- a/gerbonara/cad/kicad/footprints.py
+++ b/gerbonara/cad/kicad/footprints.py
@@ -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
diff --git a/gerbonara/cad/kicad/pcb.py b/gerbonara/cad/kicad/pcb.py
index a97986b..06023a5 100644
--- a/gerbonara/cad/kicad/pcb.py
+++ b/gerbonara/cad/kicad/pcb.py
@@ -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}
diff --git a/gerbonara/cad/kicad/primitives.py b/gerbonara/cad/kicad/primitives.py
index d5ee205..2f24fb5 100644
--- a/gerbonara/cad/kicad/primitives.py
+++ b/gerbonara/cad/kicad/primitives.py
@@ -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:
diff --git a/twisted_coil_gen_twolayer.py b/twisted_coil_gen_twolayer.py
index 4284e8a..8103fb6 100644
--- a/twisted_coil_gen_twolayer.py
+++ b/twisted_coil_gen_twolayer.py
@@ -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(