I haven’t found any examples of a working and useful Python script. I will Change That!

When I got the idea of building a Studio Clock I new I had to write a script. Placing 72 LED’s in a circle with rotation and position values that have to be calculated with trigonometry and will be therefore long floats numbers, would be a job for an intern who has to be kept busy ;-). The Board on picture has currently

-144 Pads

-84 Vias

-1130 Track Segments

all set in less than a second. When I decide to change the Radius, I just change a variable in the script and its done.

The schematic looks time-consuming too, but it isn’t, its just strategic copy-pasting of component groups and using the Auto Annotation function. That’s the reason D61 to D72 are aligned like that, this way they got the right pattern for the PCB design.

I using the operators Division “/” and Modulus “%” a lot. They are so useful to sort or accesses data, that I show you a small example (thanks to c4757p it is now correct):

```
list = ['a1','a2','a3','b1','b2','b3','c1','c2','c3']
sublists = [[None] * 3 for _ in range(3)]
print sublists
for i in range(len(list)):
sublists[i/3][i%3] = list[i]
print sublists
```

will print out:

```
[[None, None, None], [None, None, None], [None, None, None]]
[['a1', 'a2', 'a3'], ['b1', 'b2', 'b3'], ['c1', 'c2', 'c3']]
```

I decided not to use this technique to maintain it readable and created two Lists, one for the “SecondsRing” with 60 LED’s and one for the “HourRing” with 12 LED’s (Naming stuff is the must difficult thing in programming ;-)). The Rings are actually Polygons with 60 Sides, you will get a spider web when you extrapolate the connection Lines to the middle. The Lists are saving all important objects and values, that I need to assess, this way I don’t need to use all these Getters functions. I going to expand the script with rooting to a connector and 7 segment displays. So I don’t have to do anything, but creating an even more complex script).

Have fun trying to understand the net building logic, it took me 2 days to find the best solutions (and getting to learn the KiCad scripting).

```
#Cad Studio Clock (2016-01-30)
# execfile("/home/doug/svn/Elektronik/KiCad/StudioClockFull/makeClock.py")
#This script places leds in Clock Formation by using Patterns
#Kathode and Anode is used with the asumption you got Common Anode System. You can change it to Common Cathode System by Placing the diosds the turned by 180 deg.
import codecs
import pcbnew
import math
import sys
import collections
pcb = pcbnew.GetBoard()
Radius = 40
originOffsetXY = [0, 0]
MatSRing = [None]*(60) #Led Ring of Seconds
MatHRing = [None]*(12) #Led Ring of Hourse or 5 Minute Dividers
NetsA = [] #OuterSecondCommonAnodes
NetsB = [] #InnerCathodes
NetC = None #OuterHoursCommonAnode
#calc rotation angle (rad) with Position: float -> float
def calcRad(pos):
return math.pi/30*(pos%60)
#calc ratation angle (deg) with Position: float -> float
def calcDeg(pos):
return math.degrees(calcRad(pos))
#calc the Position(s) with the Radius and an Angle: float, float -> float/float/wxPoint
def calcX(radius, pos):
return math.sin(calcRad(pos))*(radius)+originOffsetXY[0]
def calcY(radius, pos):
return -math.cos(calcRad(pos))*radius+originOffsetXY[1]
def calcXY(radius, pos):
return pcbnew.wxPointMM(calcX(radius,pos),calcY(radius,pos))
#add a track and add it: wxPoint, wxPoint, int, int -> Track
def addTrack(startPos, stopPos, net, layer):
t = pcbnew.TRACK(pcb)
pcb.Add(t)
t.SetStart(startPos)
t.SetEnd(stopPos)
t.SetNetCode(net)
t.SetLayer(layer)
return t
#add an arc of tracks with the Position Idices of the SecondsLeds: float, int, int, int, int -> Track
def addTrackArc(radius, startPos, stopPos, net, layer):
t = None
for i in range(startPos,stopPos-1):
t = addTrack(calcXY(radius, i), calcXY(radius, i+1), net, layer)
return t
#add a full Track ring with the desired Radius: float, int, int
def addTrackRing(radius, net, layer):
addTrackArc(radius, 0, 61, net, layer)
#add a via at the Position: wxPoint -> Via
def addVia(position, net):
v = pcbnew.VIA(pcb)
pcb.Add(v)
v.SetPosition(position)
#v.SetWidth(600000)
v.SetViaType(pcbnew.VIA_THROUGH)
v.SetLayerPair(LayerFCu, LayerBCu)
v.SetNetCode(net)
return v
#Some Constants for better Readability
LayerBCu = 31
LayerFCu = 0
LayerEdgeCuts = 44
#Deleting all Nets and Drawings is esier than finding exisitng ones
print '---------------------------------------------------------------'
print '---Delete-Exisitng-Nets-and Drawings---------------------------'
print '---------------------------------------------------------------'
for t in pcb.GetTracks():
pcb.Delete(t)
for d in pcb.GetDrawings():
pcb.Remove(d)
#Find all Diods and save often needed Information or Objects in Array
#Sort them into HourRing and Secods Ring
#Find all Nets add them to their Netgroup list.
print '---------------------------------------------------------------'
print '---Gathering-Diods-and-Info------------------------------------'
print '---------------------------------------------------------------'
for modu in pcb.GetModules():
ref = modu.GetReference().encode('utf-8')
if(ref.startswith('D')):
pos = int(ref.split('D')[-1])-1
pad1 = None #kathode
net1 = None
pad2 = None #anode
net2 = None
for pad in modu.Pads():
if int(pad.GetPadName()) == 1:
pad1 = pad
net1 = pad.GetNetCode()
if pos <= 59:
if net1 not in NetsB:
NetsB.append(net1)
else:
pad2 = pad
net2 = pad.GetNetCode()
if pos <= 59:
if net2 not in NetsA:
NetsA.append(net2)
elif pos <= 71 and NetC == None:
NetC = net2
if pos <= 59:
MatSRing[pos] = [pos, modu, pad1, net1, pad2, net2, ref]
print 'Read: Second %s, Position %d, Net1 %d, Net2 %d' % (MatSRing[pos][6], MatSRing[pos][0], net1, net2)
elif pos <= 71:
pos = pos%60
MatHRing[pos] = [pos, modu, pad1, net1, pad2, net2, ref]
print 'Read: Hour %s, Poition %d, Net1 %d, Net2 %d' % (ref, MatHRing[pos][0], net1, net2)
print '-----------------------------------------------------'
print 'NetsA:', NetsA
print 'NetsB:', NetsB
print 'NetC :', NetC
#This is just moving modules with a pattern
print '---------------------------------------------------------------'
print '---Calculating-and-Setting-Module-Positions--------------------'
print '---------------------------------------------------------------'
for i in range(len(MatSRing)): #60
MatSRing[i][1].SetOrientation(-(calcDeg(i)-90)*10)
MatSRing[i][1].SetPosition(calcXY(Radius, i))
print 'Placed: Second %s at %s with rot %s' % (MatSRing[i][6], str(MatSRing[i][1].GetPosition()), str(MatSRing[i][1].GetOrientation()))
for i in range(len(MatHRing)): #12
MatHRing[i][1].SetOrientation(-(calcDeg(i*5)-90)*10)
MatHRing[i][1].SetPosition(calcXY(Radius+4, i*5))
print 'Placed: Hour %s at %s with rot %s' % (MatSRing[i][6], str(MatHRing[i][1].GetPosition()), str(MatHRing[i][1].GetOrientation()))
print '---------------------------------------------------------------'
print '---Build-Net---------------------------------------------------'
print '---------------------------------------------------------------'
#Inner Kathode Rings and Connections to Pads
for i in range(len(NetsB)):
print "Adding NetB", NetsB[i]
r = Radius-3-i #this can be replaced by a more advanced equation
addTrackRing(r, NetsB[i], LayerBCu)
filtered = filter(lambda x: x[3] == NetsB[i], MatSRing)
for f in filtered:
addTrack(calcXY(r, f[0]), f[2].GetPosition(), NetsB[i], LayerFCu)
print 'Added: Connection to %s with Net %d' % (f[6], f[3])
addVia(calcXY(r,f[0]), NetsB[i])
#Outer Anode Rings of Seconds
for i in range(len(NetsA)):
var = filter(lambda x: x[5] == NetsA[i], MatSRing)
sor = sorted(var, key = lambda x: x[3])
print "Adding NetA", NetsA[i]
for j in range(0,len(sor)-1):
addTrack(sor[j][4].GetPosition(),sor[j+1][4].GetPosition(), NetsA[i], LayerFCu)
#Outer Anode Ring of Hours
print "Adding NetC", NetC
RadiusPadH2 = (float(MatHRing[6][4].GetPosition().y))/1000000
addTrackRing(RadiusPadH2,NetC,LayerFCu)
#Hours kathode sonnections to inner Rings
RadiusPadH1 = (float(MatHRing[6][2].GetPosition().y)/1000000)
RadiusPadS1 = (float(MatSRing[30][2].GetPosition().y)/1000000)
for i in range(len(MatHRing)):
positionOffset = {0:0.5, 1:1.5, 2:2.5, 3:3.5}[i/3]
positionIndex = MatHRing[i][0]*5
tempPosXY1 = calcXY(RadiusPadH1, positionIndex+positionOffset)
tempPosXY2 = calcXY(RadiusPadS1, positionIndex+positionOffset)
if i/3 == 0:
addTrack(MatHRing[i][2].GetPosition(), tempPosXY1, MatHRing[i][3], LayerFCu)
else:
tempTrack = addTrackArc(RadiusPadH1, positionIndex, positionIndex+int(positionOffset+0.5), MatHRing[i][3], LayerFCu)
tempPosXY3 = tempTrack.GetEnd()
addTrack(tempPosXY3, tempPosXY1, MatHRing[i][3], LayerFCu)
addVia(tempPosXY1, MatHRing[i][3])
addTrack(tempPosXY1, tempPosXY2, MatHRing[i][3], LayerBCu)
addVia(tempPosXY2, MatHRing[i][3])
sPadsNet = filter(lambda x: x[3] == MatHRing[i][3], MatSRing)
addTrack(tempPosXY2, sPadsNet[i/3][2].GetPosition(), MatHRing[i][3], LayerFCu)
print '---------------------------------------------------------------'
print '---Set Edge Cut------------------------------------------------'
print '---------------------------------------------------------------'
print "Setting Board Dimensions too:"
corners = [[-1,-1],[-1,1],[1,1],[1,-1]]
l = Radius*1.2
for i in range(4):
seg = pcbnew.DRAWSEGMENT(pcb)
pcb.Add(seg)
seg.SetStart(pcbnew.wxPointMM(corners[i][0]*l, corners[i][1]*l))
seg.SetEnd(pcbnew.wxPointMM(corners[(i+1)%4][0]*l, corners[(i+1)%4][1]*l))
seg.SetLayer(LayerEdgeCuts)
print "Corner:", seg.GetStart()
print '---------------------------------------------------------------'
print '---The-End-----------------------------------------------------'
print '---------------------------------------------------------------'
```