For a very geometric/repetitive design, it may be easier to describe it with code than to place parts manually. To arrange LEDs at regular spacing around a circle, I used the KiCad python API.
Update: Check out my more recent component layout plugin for a bettery way
KiCad has a python API that you can use to place and position footprints into a PCB design by script. The API isn’t super well documented, but it turned out to support what I needed it to after a bit of research and reading the code. The biggest hurdle I had to get over was realizing that you should not modify a PCB from the python console while the design is open! The very existence of the scripting console dialog led me to believe this would be OK, but actually all of the examples are saw using this console were performing read-only operations, and making changes caused a lot of unexpected behavior.
The script below loads an existing .kicad_pcb file, adds a bunch of LED footprints, connects their pads to the appropriate nets, and then saves out to a different PCB file.
The easiest way I found to run this was to open the scripting console in pcbnew and source the script (e.g.
exec(open('generate.py').read())), because getting all of the pcbnew libraries setup in a python env on OS X looked like a major pain.
I think you can also add tracks, and I was intending to do even some of the interconnect on these LEDs via python, but then I ended up going a different direction with this project and dropped it.
import math import pcbnew # All linear dimensions in mm R1 = 1.0*25.4 # Inner diameter R2 = 2.00*25.4 # Outer diameter RADIAL_PITCH = 10 # Center-to-center distances radially SEGMENTS = 52 # Number of angular segments, or "radials" to lay out STAGGER_PERIOD = 4.0 # Number of different staggered starting radii board = pcbnew.LoadBoard('copy.kicad_pcb') # Converts mm to PCB internal units SCALE = 1000000 LIBPATH = 'ColorWheel.pretty' FOOTPRINT = 'CREE_RGBLED_1x1mm' CENTER = (140, 95) io = pcbnew.PCB_IO() staggerIdx = 0.0 totalLeds = 0 def find_layer(board, layer_name): for i in range(128): if board.GetLayerName(i) == layer_name: return i return -1 def find_pad(module, padName): for pad in module.Pads(): if pad.GetPadName() == padName: return pad return None anodeNet = pcbnew.NETINFO_ITEM(board, "VLED") board.Add(anodeNet) # Add LEDs for seg in range(0, SEGMENTS): ledCount = 0 theta = 2*math.pi / SEGMENTS * seg r = R1 + RADIAL_PITCH / 2.0 + (staggerIdx / STAGGER_PERIOD)*RADIAL_PITCH staggerIdx = (staggerIdx + 1) % STAGGER_PERIOD rNet = pcbnew.NETINFO_ITEM(board, "R%02d" % seg) gNet = pcbnew.NETINFO_ITEM(board, "G%02d" % seg) bNet = pcbnew.NETINFO_ITEM(board, "B%02d" % seg) board.Add(rNet) board.Add(gNet) board.Add(bNet) while r < R2 - RADIAL_PITCH/2.0: newMod = io.FootprintLoad(LIBPATH, FOOTPRINT) newMod.SetReference("D%d-%d" % (seg, ledCount)) x = CENTER + r * math.cos(theta) y = CENTER + r * math.sin(theta) newMod.SetPosition(pcbnew.wxPoint(x*SCALE, y*SCALE)) board.Add(newMod) find_pad(newMod, '1').SetNetCode(rNet.GetNet()) find_pad(newMod, '2').SetNetCode(gNet.GetNet()) find_pad(newMod, '3').SetNetCode(bNet.GetNet()) find_pad(newMod, '4').SetNetCode(anodeNet.GetNet()) ledCount += 1 totalLeds += 1 r += RADIAL_PITCH print("Added %d LEDs" % totalLeds) # Add silkscreen border silkLayerId = find_layer(board, "F.SilkS") if(silkLayerId == -1): print("Couldn't find silk screen layer") for radius in [R1, R2]: circle1 = pcbnew.DRAWSEGMENT() circle1.SetShape(pcbnew.S_CIRCLE) circle1.SetCenter(pcbnew.wxPoint(CENTER*SCALE, CENTER*SCALE)) start_coord = pcbnew.wxPoint(CENTER*SCALE, (CENTER + radius)*SCALE) circle1.SetArcStart(start_coord) circle1.SetLayer(silkLayerId) circle1.SetWidth(int(0.5*SCALE)) board.Add(circle1) board.Save('autogen.kicad_pcb')