Skip to content

Instantly share code, notes, and snippets.

@alg0trader
Created June 7, 2019 03:54
Show Gist options
  • Save alg0trader/0ce8960e0809a9d6e834850e42cc65b8 to your computer and use it in GitHub Desktop.
Save alg0trader/0ce8960e0809a9d6e834850e42cc65b8 to your computer and use it in GitHub Desktop.
# This python script wizard creates a mitered bend for microwave applications
from __future__ import division
from pcbnew import *
import pcbnew
import math
class UWMiterFootprintWizard(pcbnew.FootprintWizardPlugin):
# Copy units from pcbnew
uMM = pcbnew.uMM
uMils = pcbnew.uMils
uFloat = pcbnew.uFloat
uInteger = pcbnew.uInteger
uBool = pcbnew.uBool
uRadians = pcbnew.uRadians
uDegrees = pcbnew.uDegrees
uPercent = pcbnew.uPercent
uString = pcbnew.uString
def __init__(self):
pcbnew.FootprintWizardPlugin.__init__(self)
self.name = "uW Mitered Bend"
self.description = "Mitered Bend Footprint Wizard"
self.GenerateParameterList()
# self.parameters = {
# "Corner":{
# "width": FromMM(0.34),
# "height": FromMM(0.17),
# "angle": 90,
# }
# }
# self.ClearErrors()
def GenerateParameterList(self):
"""!
Footprint parameter specification is done here
"""
self.AddParam("Corner", "width", self.uMM, 0.34)
self.AddParam("Corner", "height", self.uMM, 0.17)
self.AddParam("Corner", "angle", self.uDegrees, 90)
# Build a rectangular pad
def smdRectPad(self, module, size, pos, name, angle):
pad = D_PAD(module)
pad.SetSize(size)
pad.SetShape(PAD_SHAPE_RECT)
pad.SetAttribute(PAD_ATTRIB_SMD)
# Set only the copper layer without mask
# since nothing is mounted on these pads
pad.SetLayerSet( LSET(F_Cu) )
pad.SetPos0(pos)
pad.SetPosition(pos)
pad.SetPadName(name)
pad.Rotate(pos, angle)
# Set clearance to small value, because
# pads can be very close together.
# If distance is smaller than clearance
# DRC doesn't allow routing the pads
pad.SetLocalClearance(1)
return pad
def Polygon(self, points, layer):
"""
Draw a polygon through specified points
"""
polygon = pcbnew.EDGE_MODULE(self.module)
polygon.SetWidth(0) # Disables outline
polygon.SetLayer(layer)
polygon.SetShape(pcbnew.S_POLYGON)
polygon.SetPolyPoints(points)
self.module.Add(polygon)
# This method checks the parameters provided to wizard and set errors
def CheckParameters(self):
p = self.parameters
width = p["Corner"]["width"]
height = p["Corner"]["height"]
angle = p["Corner"]["angle"]
errors = []
if (width<0):
errors.append("Width has invalid value")
if width/height < 0.25:
errors.append("Too small width to height ratio")
if angle > 90:
errors.append("Too large angle")
if angle < 0:
errors.append("Angle must be positive")
errors = ', '.join(errors)
print errors
return errors == ""
def bilinear_interpolation(self, x, y, points):
'''http://stackoverflow.com/questions/8661537/how-to-perform-bilinear-interpolation-in-python
Interpolate (x,y) from values associated with four points.
The four points are a list of four triplets: (x, y, value).
The four points can be in any order. They should form a rectangle.
>>> bilinear_interpolation(12, 5.5,
... [(10, 4, 100),
... (20, 4, 200),
... (10, 6, 150),
... (20, 6, 300)])
165.0
'''
# See formula at: http://en.wikipedia.org/wiki/Bilinear_interpolation
points = sorted(points) # Order points by x, then by y
(x1, y1, q11), (_x1, y2, q12), (x2, _y1, q21), (_x2, _y2, q22) = points
return (q11 * (x2 - x) * (y2 - y) +
q21 * (x - x1) * (y2 - y) +
q12 * (x2 - x) * (y - y1) +
q22 * (x - x1) * (y - y1)
) / ((x2 - x1) * (y2 - y1) + 0.0)
def OptimalMiter(self, w, h, angle):
"""Calculate optimal miter by interpolating from table.
https://awrcorp.com/download/faq/english/docs/Elements/MBENDA.htm
"""
wh = w/h
whs = [0.5, 1.0, 2.0]
angles = [0, 30, 60, 90, 120]
table = [
[0, 12, 45, 75, 98],
[0, 19, 41, 63, 92],
[0, 7, 31, 56, 79]
]
for i, x in enumerate(whs):
if x > wh:
break
for j, y in enumerate(angles):
if y > angle:
break
i = min(i-1,1)
j = min(j-1,3)
px = lambda ii,jj: (whs[ii],angles[jj],table[ii][jj])
x1 = px(i,j)
x2 = px(i+1,j)
y1 = px(i,j+1)
y2 = px(i+1,j+1)
return self.bilinear_interpolation(wh, angle, [x1,x2,y1,y2])/100.0
# Build the footprint from parameters
def BuildFootprint(self):
self.module = pcbnew.MODULE(None) # Create new module
module = self.module
if not self.CheckParameters():
return
p = self.parameters
width = p["Corner"]["width"]
height = p["Corner"]["height"]
angle_deg = float(p["Corner"]["angle"])
angle = angle_deg*0.0174532925 # To radians
# textposy = width + 1
# size_text = pcbnew.wxSize( 0.8, 0.7 )
# module.SetReference("uwmiter_{0:.2f}_{1:0.2f}_{2:.0f}".format(width,height,angle_deg))
# # module.Reference().SetPos0(wxPoint(0, textposy))
# # module.Reference().SetTextPosition(module.Reference().GetPos0())
# # module.Reference().SetSize( size_text )
# # module.Reference().SetVisible(0)
# # textposy = textposy + 1
# # module.SetValue("Val***") # give it a default value
# # module.Value().SetPos0( wxPoint(0, textposy) )
# # module.Value().SetTextPosition(module.Value().GetPos0())
# # module.Value().SetSize( size_text )
# # module.Value().SetVisible(0)
# # fpid = FPID(self.module.GetReference()) #the name in library
# module.SetFPID(self.module.GetReference())
# Calculate the miter
w = width
# Width of the corner from edge of the corner to inside corner
corner_width = ToMM(w)/math.cos(angle/2)
# Get proportion of width to cut
cut = self.OptimalMiter(width, height, angle_deg)
print "Cut: {0:.2f}%".format(cut*100)
#Distance from uncut outside corner point to point 7
cut = FromMM(cut*corner_width/math.cos((math.pi-angle)/2))
# Distance between points 2 and 3 and points 3 and 4
# Minimum of w/2 to satisfy DRC, otherwise pads are too close
# and track connected to other pad overlaps the other one.
# Rounded trace end can also stick out of the cut area
# if a is too small.
a = max(cut-width*math.tan(angle/2),w/2)
# Distance between points 3 and 4
x34 = a*math.sin(angle)
y34 = a*math.cos(angle)
# Distance between points 4 and 5
x45 = width*math.cos(angle)
y45 = width*math.sin(angle)
# 1 2
#8 +--+
# | |3
#7 \ --+ 4
# \ |
# \--+ 5
# 6
points = [
(0,0),
(w,0),
(w,a),
(w+x34,a+y34),
(w+x34-x45,a+y34+y45),
(cut*math.sin(angle),a+width*math.tan(angle/2)+cut*math.cos(angle)),
(0,a+width*math.tan(angle/2)-cut),
(0,0)]
# Last two points can be equal
if points[-2] == points[-1]:
points = points[:-1]
points = [wxPoint(*point) for point in points]
self.Polygon(points, F_Cu)
# Create pads
pad_l = width/10
size_pad = wxSize(width,pad_l)
module.Add(self.smdRectPad(module, size_pad, wxPoint(width/2,-pad_l/2), "1", 0))
size_pad = wxSize(pad_l,width)
# Halfway between points 4 and 5
posx = ((w+x34) + (w+x34-x45))/2
posy = ((a+y34) + (a+y34+y45))/2
# Position pad so that pad edge touches polygon edge
posx += (pad_l/2)*math.sin(angle)
posy += (pad_l/2)*math.cos(angle)
size_pad = wxSize(pad_l, width)
module.Add(self.smdRectPad(module, size_pad, wxPoint(posx,posy), "1", (angle_deg-90)*10))
# Create our footprint wizard
uwmiter_wizard = UWMiterFootprintWizard()
# Register it into pcbnew
uwmiter_wizard.register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment