├── .gitignore ├── LICENSE ├── README.md ├── hexagon.py ├── padTeardrop.py ├── roundTracks.py ├── runall.py ├── teardrop.py └── trackUtils.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Julian Loiacono 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flexRoundingSuite 2 | ## Introduction 3 | 4 | This repository contains three important functions for preparing a high density board for flexpcb production: 5 | 1) **VIA teardrops**; usage: import teardrop; teardrop.generate() 6 | 2) **PAD teardrops**; usage: import padTeardrop; padTeardrop.generate() 7 | 3) **Track rounding**; usage: import roundTracks; roundTracks.rounder() 8 | 9 | VIA teardrops script is adapted from the one here: 10 | https://github.com/svofski/kicad-teardrops 11 | 12 | I am most proud of the track rounding function, which iteratively places intermediate tracks at every intersection where the track angle difference is sufficiently large. 13 | 14 | Neither padTeardrop nor roundTracks will operate above an ECO1 zone, which is assumed to be the stiffener layer. 15 | 16 | This script may also be useful in RF applications. 17 | 18 | Make sure you run DRC after running these scripts!!! You may need to edit tracks in pre to make sure there is room for the curves. 19 | 20 | Enjoy! 21 | jcloiacon 22 | 23 | ## Examples: 24 | 25 | ### LED Footprints 26 | 27 | #### LED footprints before any script: 28 | 29 | ![LED footprints before any script](https://imgur.com/brsHhDN.png) 30 | 31 | #### LED footprints after pad teardrops: 32 | 33 | ![LED footprints after pad teardrops](https://imgur.com/rDwSO6a.png) 34 | 35 | #### LED footprints after pad teardrops and rounding: 36 | 37 | ![LED footprints after pad teardrops and rounding](https://imgur.com/GCDScS4.png) 38 | 39 | ### VIAs 40 | 41 | #### VIAs before any script 42 | 43 | ![VIAs before any script](https://imgur.com/S8dzbRL.png) 44 | 45 | #### VIAs after teardrops and padTeardrops 46 | 47 | ![VIAs after teardrops and padTeardrops](https://imgur.com/QMF7foi.png) 48 | 49 | #### VIAs after teardrops and padTeardrops and rounding 50 | 51 | ![VIAs after teardrops and padTeardrops and rounding](https://imgur.com/etUYDx7.png) 52 | -------------------------------------------------------------------------------- /hexagon.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from math import * 3 | import os 4 | import subprocess 5 | import numpy as np 6 | 7 | three69 = sqrt(3); 8 | thickness = 28; 9 | unit_size = 25; 10 | proximity = 13; 11 | 12 | box_length = unit_size*3; 13 | box_height = unit_size*2*sin(radians(60)); 14 | 15 | def processImage(filename): 16 | #gerb = Image.open(filename); 17 | #gerbpix = gerb.getdata(); 18 | 19 | # PIL accesses images in Cartesian co-ordinates, so it is Image[columns, rows] 20 | #img = Image.new( 'L', (gerb_width,gerb_height), "white").tobytes() # create a new white image 21 | newimdata = [] 22 | #pixels = Image.open(filename).convert('LA') # create the pixel map, convert to greyscale 23 | img = Image.open( filename ).convert('L') 24 | gerb_width, gerb_height = img.size; 25 | img.load() 26 | pixels = np.rot90(np.asarray( img, dtype="int8" )) 27 | 28 | print(gerb_width) 29 | print(gerb_height) 30 | # one unit box looks like 31 | # _ _ 32 | # \__/ 33 | # / \ 34 | 35 | # if the quadrant is normalized (flip symmetry), each section looks like 36 | # _ 37 | # \_ 38 | # 39 | # where both horizontal lines are unitsize/2 long 40 | 41 | for y in range(gerb_height): # For every row 42 | print( float(y)/gerb_height ); 43 | for x in range(gerb_width): # for every col: 44 | #print(pixels[x, y]) 45 | #print(gerb_width) 46 | # find relative location within box 47 | ypos = y % box_height; 48 | xpos = x % box_length; 49 | # normalize quadrant 50 | if (ypos > box_height/2): 51 | ypos = box_height - ypos; 52 | if (xpos > box_length/2): 53 | xpos = box_length - xpos; 54 | 55 | 56 | # warning track, or standard hexagon 57 | if ( 58 | (not square_detect(x,y, proximity, gerb_width, gerb_height, pixels)) 59 | and 60 | (square_detect(x,y, proximity + thickness, gerb_width, gerb_height, pixels) 61 | or 62 | (xpos < unit_size/2 and ypos < thickness/2) # top line 63 | or 64 | (xpos > (box_length - unit_size)/2 and ypos > (box_height - thickness)/2) # bottom line 65 | or 66 | (abs(ypos - three69*(xpos - unit_size/2)) < thickness) # diagonal defined by y = .5sqrt(3)(x - unit) 67 | )): 68 | newimdata.append(0) # set the colour accordingly 69 | else: 70 | newimdata.append(1) # set the colour accordingly 71 | 72 | 73 | #img.show() 74 | img = Image.new(pixels.mode,pixels.size) 75 | img.putdata(newimdata) 76 | img.save(filename + '.bmp') 77 | 78 | 79 | 80 | # square detection for simplicity 81 | def square_detect(x, y, width, gerb_width, gerb_height, gerbpix): 82 | #keep min values on image 83 | xmin = x - width; 84 | if xmin < 0: 85 | xmin = 0; 86 | ymin = y - width; 87 | if ymin < 0: 88 | ymin = 0; 89 | 90 | #keep max values on image 91 | xmax = x + width; 92 | if xmax >= gerb_width: 93 | xmax = gerb_width-1; 94 | ymax = y + width; 95 | if ymax >= gerb_height: 96 | ymax = gerb_height-1 97 | 98 | # allow to skip 5 pixels. assumes 8 pix width 99 | # horizontals 100 | for thisx in range(xmin, xmax, 5): 101 | if (gerbpix[thisx, ymin] != 0 or 102 | gerbpix[thisx, ymax] != 0): 103 | return True; 104 | 105 | # verticals 106 | for thisy in range(ymin, ymax, 5): 107 | if (gerbpix[xmin, thisy] != 0 or 108 | gerbpix[xmax, thisy] != 0): 109 | return True; 110 | 111 | return False; 112 | 113 | # for all files in this dir 114 | for file in os.listdir("."): 115 | if file.endswith(".svg"): #if they are svg 116 | #convert to png 117 | sysString = "inkscape -z " + file + " -d 1600 -e " + file + ".bmp" 118 | print(sysString) 119 | print(subprocess.call(sysString)) 120 | #process the new png 121 | processImage(file + ".bmp") 122 | -------------------------------------------------------------------------------- /padTeardrop.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | SCALE = 1000000.0 4 | import pcbnew 5 | import math 6 | from trackUtils import * 7 | 8 | def generate(): 9 | # generate a LUT with shape integers to a string 10 | #padshapes = { 11 | # pcbnew.PAD_SHAPE_CIRCLE: "PAD_SHAPE_CIRCLE", 12 | # pcbnew.PAD_SHAPE_OVAL: "PAD_SHAPE_OVAL", 13 | # pcbnew.PAD_SHAPE_RECT: "PAD_SHAPE_RECT", 14 | # pcbnew.PAD_SHAPE_TRAPEZOID: "PAD_SHAPE_TRAPEZOID" 15 | #} 16 | ## new in the most recent kicad code 17 | #if hasattr(pcbnew, 'PAD_SHAPE_ROUNDRECT'): 18 | # padshapes[pcbnew.PAD_SHAPE_ROUNDRECT] = "PAD_SHAPE_ROUNDRECT" 19 | 20 | board = pcbnew.GetBoard() 21 | tracksToAdd = set() 22 | tracksToDelete = set() 23 | 24 | # get all "modules", seemingly identical to footprints 25 | for mod in board.GetModules(): 26 | #print(mod.GetReference()) 27 | #get all pads on this module 28 | for pad in mod.Pads(): 29 | #print("pad {}({}) on {}({}) at {},{} shape {} size {},{}" 30 | # .format(pad.GetPadName(), 31 | # pad.GetNet().GetNetname(), 32 | # mod.GetReference(), 33 | # mod.GetValue(), 34 | # pad.GetPosition().x, pad.GetPosition().y, 35 | # padshapes[pad.GetShape()], 36 | # pad.GetSize().x, pad.GetSize().y 37 | #)) 38 | 39 | # for simplicity, only consider the bounding box of the pad 40 | # future improved versions of the code should account for rotated pads and strange geometries 41 | boundingBox = pad.GetBoundingBox(); 42 | # get all the tracks in this net 43 | tracks = board.TracksInNet(pad.GetNet().GetNet()) 44 | for track in tracks: 45 | # it seems vias are treated as tracks ???? this should take care of that 46 | if(track.GetLength() > 0): 47 | #angle = abs(normalizeAngleHalf(getTrackAngle(track) - pad.GetOrientationRadians())); 48 | # keep angle [-pi/2,pi/2] because start and end are placed arbitrarily 49 | # because we are using "bounding box", the orientation of the pad is already accounted for 50 | angle = abs(normalizeAngleHalf(getTrackAngle(track))); 51 | # if this track is the same layer, and has an endpoint within the bounding box of the pad, create 3 new traces, one to either side of the bounding box and one in the center 52 | 53 | # reverse this track if the endpoint is on the pad 54 | if(pad.HitTest(track.GetEnd())): 55 | reverseTrack(track) 56 | 57 | # if track and pad are on the same layer, intersect, and are not over stiffener material, make 3 new traces 58 | if(track.GetLayer() == pad.GetLayer() and pad.HitTest(track.GetStart()) and not stiffened(board, track.GetStart())): 59 | #print("intersect at start ", track.GetStart()) 60 | if (angle > math.pi/4): 61 | # shorten this track by the X-dimension of the pad 62 | sp = shortenTrack(track, boundingBox.GetHeight()*.7) 63 | #if the new track is length 0, slate it for deletion 64 | if(track.GetLength() == 0): 65 | tracksToDelete.add(track); 66 | tracksToAdd.add((cloneWxPoint(sp), pcbnew.wxPoint(boundingBox.GetRight() - track.GetWidth()/2, boundingBox.GetCenter().y), track.GetWidth(), pad.GetLayer(), pad.GetNet())) 67 | tracksToAdd.add((cloneWxPoint(sp), pcbnew.wxPoint(boundingBox.GetLeft() + track.GetWidth()/2, boundingBox.GetCenter().y), track.GetWidth(), pad.GetLayer(), pad.GetNet())) 68 | tracksToAdd.add((cloneWxPoint(sp), cloneWxPoint(boundingBox.GetCenter()), track.GetWidth(), pad.GetLayer(), pad.GetNet())) 69 | else: 70 | # shorten this track by the Y-dimension of the pad 71 | sp = shortenTrack(track, boundingBox.GetWidth()*.7) 72 | #if the new track is length 0, slate it for deletion 73 | if(track.GetLength() == 0): 74 | tracksToDelete.add(track); 75 | tracksToAdd.add((cloneWxPoint(sp), pcbnew.wxPoint(boundingBox.GetCenter().x, boundingBox.GetTop() + track.GetWidth()/2), track.GetWidth(), pad.GetLayer(), pad.GetNet())) 76 | tracksToAdd.add((cloneWxPoint(sp), pcbnew.wxPoint(boundingBox.GetCenter().x, boundingBox.GetBottom() - track.GetWidth()/2), track.GetWidth(), pad.GetLayer(), pad.GetNet())) 77 | tracksToAdd.add((cloneWxPoint(sp), cloneWxPoint(boundingBox.GetCenter()), track.GetWidth(), pad.GetLayer(), pad.GetNet())) 78 | 79 | 80 | 81 | #add all the tracks in post, so as not to cause problems with set iteration 82 | for trackpoints in tracksToAdd: 83 | (sp, ep, width, layer, net) = trackpoints 84 | track = pcbnew.TRACK(board) 85 | track.SetStart(sp) 86 | track.SetEnd(ep) 87 | track.SetWidth(width) 88 | track.SetLayer(layer) 89 | board.Add(track) 90 | track.SetNet(net) 91 | 92 | for track in tracksToDelete: 93 | board.Remove(track) 94 | -------------------------------------------------------------------------------- /roundTracks.py: -------------------------------------------------------------------------------- 1 | # Original example Copyright [2017] [Miles McCoo] 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | # extensively modified by Julian Loiacono, Oct 2018 15 | 16 | 17 | # This script will round PCBNEW traces by shortening traces at intersections, 18 | # and filling new traces between. This process is repeated thrice. 19 | 20 | # usage: 21 | # import round 22 | # round.rounder() 23 | 24 | SCALE = 1000000.0 25 | import pcbnew 26 | import math 27 | from trackUtils import * 28 | 29 | # maximum path length is set to .2mm, and progressive shortening is 25% 30 | MAXLENGTH = 0.20 31 | SCALING = 0.25 32 | 33 | #the main function, adds intermediate tracks 3 times 34 | def rounder(): 35 | for i in range(3): 36 | addIntermediateTracks() 37 | 38 | 39 | def addIntermediateTracks(): 40 | 41 | # most queries start with a board 42 | board = pcbnew.GetBoard() 43 | 44 | # returns a dictionary netcode:netinfo_item 45 | netcodes = board.GetNetsByNetcode() 46 | 47 | # list off all of the nets in the board. 48 | for netcode, net in netcodes.items(): 49 | #print("netcode {}, name {}".format(netcode, net.GetNetname())) 50 | tracks = board.TracksInNet(net.GetNet()) # get all the tracks in this net 51 | 52 | 53 | #add all the possible intersections to a unique set, for iterating over later 54 | intersections = set(); 55 | for t1 in range(len(tracks)): 56 | for t2 in range(t1+1, len(tracks)): 57 | #check if these two tracks share an endpoint 58 | # reduce it to a 2-part tuple so there are not multiple objects of the same point in the set 59 | if(tracks[t1].IsPointOnEnds(tracks[t2].GetStart())): 60 | intersections.add((tracks[t2].GetStart().x, tracks[t2].GetStart().y)) 61 | if(tracks[t1].IsPointOnEnds(tracks[t2].GetEnd())): 62 | intersections.add((tracks[t2].GetEnd().x, tracks[t2].GetEnd().y)) 63 | 64 | ipsToRemove = set() 65 | 66 | # remove all of the intersections which occur over an ECO1 (stiffener) zone 67 | # we are not worried about rounding these traces. 68 | for ip in intersections: 69 | (newX, newY) = ip; 70 | if stiffened(board, pcbnew.wxPoint(newX, newY)): 71 | ipsToRemove.add(ip) 72 | for ip in ipsToRemove: 73 | #print("removing", ip) 74 | intersections.remove(ip) 75 | 76 | 77 | 78 | #for each remaining intersection, shorten each track by the same amount, and place a track between. 79 | tracksToAdd = set() 80 | tracksToShorten = set() 81 | for ip in intersections: 82 | (newX, newY) = ip; 83 | intersection = pcbnew.wxPoint(newX, newY) 84 | tracksHere = []; 85 | for t1 in tracks: 86 | # it seems vias are treated as tracks ???? this should take care of that 87 | if(t1.GetLength() > 0): 88 | if similarPoints(t1.GetStart(), intersection): 89 | tracksHere.append(t1) 90 | if similarPoints(t1.GetEnd(), intersection): 91 | #flip track such that all tracks start at the IP 92 | reverseTrack(t1) 93 | tracksHere.append(t1) 94 | 95 | 96 | # sometimes tracksHere is empty ??? 97 | if len(tracksHere) == 0: 98 | continue 99 | 100 | #sort tracks by length, just to find the shortest 101 | tracksHere.sort(key = GetTrackLength) 102 | #shorten all tracks by the same length, which is a function of existing shortest path length 103 | shortenLength = min(tracksHere[0].GetLength() * MAXLENGTH, SCALING*SCALE) 104 | 105 | #sort these tracks by angle, so new tracks can be drawn between them 106 | tracksHere.sort(key = getTrackAngle) 107 | #shorten all these tracks 108 | for t1 in range(len(tracksHere)): 109 | shortenTrack(tracksHere[t1], shortenLength) 110 | #connect the new startpoints in a circle around the old center point 111 | for t1 in range(len(tracksHere)): 112 | #dont add 2 new tracks in the 2 track case 113 | if not (len(tracksHere) == 2 and t1 == 1): 114 | newPoint1 = cloneWxPoint(tracksHere[t1].GetStart()) 115 | newPoint2 = cloneWxPoint(tracksHere[(t1+1)%len(tracksHere)].GetStart()) 116 | tracksToAdd.add((newPoint1, newPoint2, tracksHere[t1].GetWidth(), tracksHere[t1].GetLayer(), tracksHere[t1].GetNet())) 117 | 118 | #add all the new tracks in post, so as not to cause problems with set iteration 119 | for trackpoints in tracksToAdd: 120 | (sp, ep, width, layer, net) = trackpoints 121 | 122 | track = pcbnew.TRACK(board) 123 | track.SetStart(sp) 124 | track.SetEnd(ep) 125 | track.SetWidth(width) 126 | track.SetLayer(layer) 127 | board.Add(track) 128 | track.SetNet(net) 129 | 130 | # the following really fucks with the vias: 131 | # remove all tracks of length 0 132 | #for track in tracks: 133 | # if track.GetLength() == 0: 134 | # board.Remove(track) 135 | -------------------------------------------------------------------------------- /runall.py: -------------------------------------------------------------------------------- 1 | import teardrop 2 | teardrop.generate() 3 | 4 | import padTeardrop 5 | padTeardrop.generate() 6 | 7 | import roundTracks 8 | roundTracks.rounder() 9 | -------------------------------------------------------------------------------- /teardrop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Teardrops for PCBNEW by svofski, 2014 4 | # http://sensi.org/~svo 5 | 6 | # modified slightly by Julian Loiacono 2018 7 | 8 | from pcbnew import * 9 | from math import * 10 | from trackUtils import * 11 | 12 | ToUnits=ToMM 13 | FromUnits=FromMM 14 | 15 | 16 | def generate(): 17 | board = GetBoard() 18 | vias = set() 19 | 20 | count = 0; 21 | 22 | # this dumbass way to get all the vias 23 | # wish I could get them by net like tracks 24 | for item in board.GetTracks(): 25 | if type(item) == VIA: 26 | vias.add(item) 27 | 28 | # returns a dictionary netcode:netinfo_item 29 | netcodes = board.GetNetsByNetcode() 30 | 31 | tracksToAdd = set() 32 | tracksToDelete = set() 33 | # list off all of the nets in the board. 34 | for netcode, net in netcodes.items(): 35 | #print("netcode {}, name {}".format(netcode, net.GetNetname())) 36 | tracks = board.TracksInNet(net.GetNet()) # get all the tracks in this net 37 | for track in tracks: 38 | # it seems vias are treated as tracks ???? this should take care of that 39 | if(track.GetLength() > 0): 40 | for via in vias: 41 | if track.IsPointOnEnds(via.GetPosition()) and not stiffened(board, via.GetPosition()): 42 | #print("track here") 43 | # ensure that start is at the via/pad end 44 | if similarPoints(track.GetEnd(), via.GetPosition()): 45 | reverseTrack(track) 46 | 47 | # get angle of track 48 | angle = getTrackAngle(track) 49 | 50 | # 2 pependicular angles to find the end points on via side 51 | angleB = angle + pi/2; 52 | angleC = angle - pi/2; 53 | 54 | #shorten this track from start by via width*2 55 | sp = shortenTrack(track, via.GetWidth()) 56 | 57 | #if the new track is length 0, slate it for deletion 58 | if(track.GetLength() == 0): 59 | tracksToDelete.add(track); 60 | 61 | #determine the radius on which the 2 new endpoints reside 62 | radius = via.GetWidth() / 2 - track.GetWidth() / 2 63 | 64 | # via side points 65 | pointB = via.GetPosition() + wxPoint(int(math.cos(angleB) * radius), int(math.sin(angleB) * radius)) 66 | pointC = via.GetPosition() + wxPoint(int(math.cos(angleC) * radius), int(math.sin(angleC) * radius)) 67 | 68 | # create the teardrop 69 | tracksToAdd.add((cloneWxPoint(sp), pointB, track.GetWidth(), track.GetLayer(), track.GetNet())) 70 | tracksToAdd.add((cloneWxPoint(sp), pointC, track.GetWidth(), track.GetLayer(), track.GetNet())) 71 | tracksToAdd.add((cloneWxPoint(sp), via.GetPosition(), track.GetWidth(), track.GetLayer(), track.GetNet())) 72 | 73 | # add extra lines if via/track ratio is high 74 | if via.GetWidth()/track.GetWidth() > 5.5: 75 | radius /= 2 76 | # via side points 77 | pointB = via.GetPosition() + wxPoint(int(math.cos(angleB) * radius), int(math.sin(angleB) * radius)) 78 | pointC = via.GetPosition() + wxPoint(int(math.cos(angleC) * radius), int(math.sin(angleC) * radius)) 79 | tracksToAdd.add((cloneWxPoint(sp), pointB, track.GetWidth(), track.GetLayer(), track.GetNet())) 80 | tracksToAdd.add((cloneWxPoint(sp), pointC, track.GetWidth(), track.GetLayer(), track.GetNet())) 81 | 82 | 83 | count = count + 1 84 | 85 | 86 | #add all the tracks in post, so as not to cause an infinite loop 87 | for trackpoints in tracksToAdd: 88 | (sp, ep, width, layer, net) = trackpoints 89 | 90 | track = pcbnew.TRACK(board) 91 | track.SetStart(sp) 92 | track.SetEnd(ep) 93 | track.SetWidth(width) 94 | track.SetLayer(layer) 95 | board.Add(track) 96 | track.SetNet(net) 97 | 98 | for track in tracksToDelete: 99 | board.Remove(track) 100 | 101 | print "Teardrops generated for %d vias. To undo, select all drawings in copper layers and delete block" % count 102 | -------------------------------------------------------------------------------- /trackUtils.py: -------------------------------------------------------------------------------- 1 | # some utility functions for dealing with tracks 2 | # Julian Loiacono 2018 3 | 4 | import pcbnew 5 | import math 6 | 7 | def reverseTrack(t1): 8 | #flip the track 9 | sp = cloneWxPoint(t1.GetStart()) 10 | ep = cloneWxPoint(t1.GetEnd()) 11 | t1.SetStart(ep) 12 | t1.SetEnd(sp) 13 | 14 | #determine if there is a stiffener zone on Eco1 which this point lies above 15 | def stiffened(board, point): 16 | # remove all of the intersections which occur over an ECO1 (stiffener) zone 17 | # we are not worried about rounding these traces. 18 | for zone in board.Zones(): 19 | if "Eco1" in board.GetLayerName(zone.GetLayer()): 20 | if zone.HitTestInsideZone(point): 21 | return True 22 | return False; 23 | 24 | 25 | # normalizes any angle to between pi/2 and -pi/2 26 | def normalizeAngleHalf(inputAngle): 27 | #normalize the angle to [-pi, pi) 28 | while(inputAngle >= math.pi/2): 29 | inputAngle -= math.pi; 30 | while(inputAngle < -math.pi/2): 31 | inputAngle += math.pi; 32 | 33 | return inputAngle; 34 | 35 | #determines whether 2 points are close enough to be considered identical 36 | def similarPoints(p1, p2): 37 | return (((p1.x > p2.x - 10) and (p1.x < p2.x + 10)) and ((p1.y > p2.y - 10) and (p1.y < p2.y + 10))); 38 | #return ((p1.x == p2.x) and (p1.y == p2.y)); 39 | 40 | # the math for shorten track 41 | def projectInward(t1, amountToShorten): 42 | 43 | #reduce to 0 length if amount to shorten exceeds length 44 | 45 | # find the angle of this track 46 | angle = normalizeAngle(getTrackAngle(t1)); 47 | #print("angle of track: ", angle*180/math.pi) 48 | #print("Shortening from Start") 49 | if amountToShorten >= t1.GetLength(): 50 | newX = t1.GetEnd().x; 51 | newY = t1.GetEnd().y; 52 | 53 | else: 54 | #project amountToShorten as the new startpoint 55 | newX = t1.GetStart().x + math.cos(angle)*amountToShorten; 56 | newY = t1.GetStart().y + math.sin(angle)*amountToShorten; 57 | 58 | return pcbnew.wxPoint(newX, newY) 59 | 60 | # shortens a track by an arbitrary amount, maintaning the angle and the endpoint 61 | def shortenTrack(t1, amountToShorten): 62 | newPoint = projectInward(t1, amountToShorten); 63 | 64 | #always shorten from start 65 | t1.SetStart(newPoint) 66 | 67 | return newPoint 68 | 69 | # normalizes any angle to between pi and -pi 70 | def normalizeAngle(inputAngle): 71 | #normalize the angle to [-pi, pi) 72 | while(inputAngle >= math.pi): 73 | inputAngle -= 2*math.pi; 74 | while(inputAngle < -math.pi): 75 | inputAngle += 2*math.pi; 76 | 77 | return inputAngle; 78 | 79 | #gets the angle of a track (unnormalized) 80 | #startReference of True means take angle from startpoint 81 | #otherwise take angle from endpoint (difference of pi) 82 | def getTrackAngle(t1): 83 | #use atan2 so the correct quadrant is returned 84 | return math.atan2((t1.GetEnd().y - t1.GetStart().y), (t1.GetEnd().x - t1.GetStart().x)); 85 | 86 | 87 | #finds the angle difference between t1 and t2 at intersection ip 88 | def angleBetween(t1, t2, ip): 89 | 90 | #find the angle betwenn 2 tracks 91 | #where start and end point are arranged arbitrarily 92 | angle1 = getTrackAngle(t1) 93 | angle2 = getTrackAngle(t2) 94 | if(similarPoints(t1.GetEnd(), ip)): 95 | angle1 += math.pi; 96 | if(similarPoints(t2.GetEnd(), ip)): 97 | angle2 += math.pi; 98 | 99 | return abs(normalizeAngle(angle1-angle2)) 100 | 101 | #utility function for sorting tracks by length 102 | def GetTrackLength(t1): 103 | return t1.GetLength() 104 | 105 | #utility function to return a new point object with the same coordinates 106 | def cloneWxPoint(wxp): 107 | return pcbnew.wxPoint(wxp.x, wxp.y) --------------------------------------------------------------------------------