def add_pad(self, position, size, name='', pad_type='standard', shape='circle', drill=1.0, layers=None): """Create a pad on the module Args: position: pad position in mm size: pad size in mm, value if shape == 'circle', tuple otherwise name: pad name/number pad_type: One of 'standard', 'smd', 'conn', 'hole_not_plated' shape: One of 'circle', 'rect', 'oval', 'trapezoid' drill: drill size in mm, single value for round hole, or tuple for oblong hole. layers: None for default, or a list of layer definitions (for example: ['F.Cu', 'F.Mask']) """ pad = Pad(pcbnew.D_PAD(self._module)) pad.type = pad_type pad.shape = shape pad.size = size pad.name = name pad.position = position pad.layers = layers self._module.Add(pad._pad) return pad
def NPTHRoundPad(self, drill): pad = pcbnew.D_PAD(self.module) pad.SetSize(pcbnew.wxSize(drill, drill)) pad.SetShape(pcbnew.PAD_SHAPE_CIRCLE) pad.SetAttribute(pcbnew.PAD_ATTRIB_HOLE_NOT_PLATED) pad.SetLayerSet(pad.UnplatedHoleMask()) pad.SetDrillSize(pcbnew.wxSize(drill, drill)) return pad
def SMDPad(self, Vsize, Hsize, shape=pcbnew.PAD_SHAPE_RECT, rot_degree=0): pad = pcbnew.D_PAD(self.module) pad.SetSize(pcbnew.wxSize(Hsize, Vsize)) pad.SetShape(shape) pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD) pad.SetLayerSet(pad.SMDMask()) pad.SetOrientation(rot_degree*10) # rotation is in 0.1 degrees return pad
def THPad(self, Vsize, Hsize, drill, shape=pcbnew.PAD_SHAPE_OVAL, rot_degree = 0): pad = pcbnew.D_PAD(self.module) pad.SetSize(pcbnew.wxSize(Hsize, Vsize)) pad.SetShape(shape) pad.SetAttribute(pcbnew.PAD_ATTRIB_STANDARD) pad.SetLayerSet(pad.StandardMask()) pad.SetDrillSize(pcbnew.wxSize(drill, drill)) pad.SetOrientation(rot_degree*10) # rotation is in 0.1 degrees return pad
def smdRectPad(self, module, size, pos, name): pad = pcbnew.D_PAD(module) pad.SetSize(size) pad.SetShape(pcbnew.PAD_SHAPE_RECT) pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD) pad.SetLayerSet(pad.SMDMask()) pad.SetPos0(pos) pad.SetPosition(pos) pad.SetName(name) return pad
def SMDPad(self, w, l, shape=pcbnew.PAD_SHAPE_RECT): pad = pcbnew.D_PAD(self.module) pad.SetSize(pcbnew.wxSize(l, w)) pad.SetShape(shape) pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD) pad.SetLayerSet(pad.SMDMask()) return pad
def THPad(self, w, l, drill, shape=pcbnew.PAD_SHAPE_OVAL): pad = pcbnew.D_PAD(self.module) pad.SetSize(pcbnew.wxSize(l, w)) pad.SetShape(shape) pad.SetAttribute(pcbnew.PAD_ATTRIB_STANDARD) pad.SetLayerSet(pad.StandardMask()) pad.SetDrillSize(pcbnew.wxSize(drill, drill)) return pad
def NPTHRoundPad(self, drill): """! A round non-plated though hole (NPTH) @param drill: the drill diameter (equals the NPTH diameter) """ pad = pcbnew.D_PAD(self.module) pad.SetSize(pcbnew.wxSize(drill, drill)) pad.SetShape(pcbnew.PAD_SHAPE_CIRCLE) pad.SetAttribute(pcbnew.PAD_ATTRIB_HOLE_NOT_PLATED) pad.SetLayerSet(pad.UnplatedHoleMask()) pad.SetDrillSize(pcbnew.wxSize(drill, drill)) return pad
def _drawSolderMaskOpening(self, sizex, sizey, radius): # Draw the solder mask opening pad = pcbnew.D_PAD(self.module) pad.SetSize(pcbnew.wxSize(sizex, sizey)) #TODO: Add fancy radius, once I figure out where to set the radius... # pad.SetShape(pcbnew.PAD_SHAPE_ROUNDRECT) pad.SetShape(pcbnew.PAD_SHAPE_RECT) pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD) layerset = pcbnew.LSET() layerset.AddLayer(pcbnew.F_Mask) pad.SetLayerSet( layerset ) pad.SetLocalSolderMaskMargin( -1 ) pad.SetPosition(pcbnew.wxPoint(0, 0)) pad.SetPadName("") self.module.Add(pad)
def _drawPixel(self, xposition, yposition): # build a rectangular pad: as a dot pad = pcbnew.D_PAD(self.module) pad.SetSize(pcbnew.wxSize(self.X, self.X)) pad.SetShape(pcbnew.PAD_SHAPE_RECT) pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD) layerset = pcbnew.LSET() if self.UseCu: layerset.AddLayer(pcbnew.F_Cu) if self.UseSilkS: layerset.AddLayer(pcbnew.F_SilkS) pad.SetLayerSet( layerset ) pad.SetLocalSolderMaskMargin( -1 ) pad.SetPosition(pcbnew.wxPoint(xposition,yposition)) pad.SetPadName("") self.module.Add(pad)
def smd_rect_pad(self, module, size, pos, name): pad = pcbnew.D_PAD(module) pad.SetSize(size) if self.parameters['Pads'].get('*oval', "true").lower() == "true": pad.SetShape(pcbnew.PAD_OVAL) else: pad.SetShape(pcbnew.PAD_RECT) pad.SetAttribute(pcbnew.PAD_SMD) pad.SetLayerMask(pcbnew.PAD_SMD_DEFAULT_LAYERS) pad.SetPos0(pos) pad.SetPosition(pos) pad.SetPadName(name) return pad
def SMDPad(self, Vsize, Hsize, shape=pcbnew.PAD_SHAPE_RECT, rot_degree=0): """ Create a surface-mount pad of the given size and shape @param Vsize: the vertical size of the pad @param Hsize: the horizontal size of the pad @param drill: the drill diameter @param shape: the shape of the pad @param rot_degree: the pad rotation, in degrees """ pad = pcbnew.D_PAD(self.module) pad.SetSize(pcbnew.wxSize(Hsize, Vsize)) pad.SetShape(shape) pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD) pad.SetLayerSet(pad.SMDMask()) pad.SetOrientation(rot_degree*10) # rotation is in 0.1 degrees return pad
def _drawPixel(self, xposition, yposition): # build a rectangular pad as a dot on copper layer, # and a polygon (a square) on silkscreen if self.UseCu: pad = pcbnew.D_PAD(self.module) pad.SetSize(pcbnew.wxSize(self.X, self.X)) pad.SetPosition(pcbnew.wxPoint(xposition,yposition)) pad.SetShape(pcbnew.PAD_SHAPE_RECT) pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD) pad.SetName("") layerset = pcbnew.LSET() layerset.AddLayer(pcbnew.F_Cu) layerset.AddLayer(pcbnew.F_Mask) pad.SetLayerSet( layerset ) self.module.Add(pad) if self.UseSilkS: polygon=self.drawSquareArea(pcbnew.F_SilkS, self.X, xposition, yposition) self.module.Add(polygon)
def TaperFootprint(self, center=pcbnew.wxPoint(0,0), angle_deg=0): self.module = pcbnew.MODULE(None) # 6 # 5 +------------+ # | |\ # | | \ 7 # | | +---+ 0 # | | | | # | | +---+ 1 # | | / 2 # | |/ # 4 +------------+ # 3 points = [(self.w2/2, -self.w2/2), (self.w2/2, self.w2/2), (-self.w2/2, self.w2/2), \ (-self.w2/2 - self.tlen, self.w1/2), (-(self.w1/2 + self.tlen + self.w2/2), self.w1/2), \ (-(self.w1/2 + self.tlen + self.w2/2), -self.w1/2), (-self.w2/2 - self.tlen, -self.w1/2), \ (-self.w2/2, -self.w2/2)] points = [pcbnew.wxPoint(*point) for point in points] # Custom pad for smaller W2 x W2 pad cs_pad = pcbnew.D_PAD(self.module) cs_pad.SetSize(pcbnew.wxSize(self.w2, self.w2)) cs_pad.SetShape(pcbnew.PAD_SHAPE_CUSTOM) cs_pad.SetAnchorPadShape(pcbnew.PAD_SHAPE_RECT) cs_pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD) cs_pad.SetLayerSet(pcbnew.LSET(pcbnew.F_Cu)) cs_pad.AddPrimitive(points, 0) cs_pad.SetLocalClearance(1) cs_pad.SetNet(self.net) cs_pad.SetPadName("1") self.module.Add(cs_pad) self.module.Add(Layout.smdRectPad(self.module, pcbnew.wxSize(self.w1, self.w1), pcbnew.wxPoint(-(self.w1/2 + self.tlen + self.w2/2), 0), "1", angle_deg, self.net)) # TODO: Address other layers... self.module.SetPosition(center) # Add to pcbnew pcbnew.GetBoard().Add(self.module) pcbnew.Refresh()
def THPad(self, Vsize, Hsize, drill, shape=pcbnew.PAD_SHAPE_OVAL, rot_degree = 0): """! A basic through-hole pad of the given size and shape @param Vsize: the vertical size of the pad @param Hsize: the horizontal size of the pad @param drill: the drill diameter @param shape: the shape of the pad @param rot_degree: the pad rotation, in degrees """ pad = pcbnew.D_PAD(self.module) pad.SetSize(pcbnew.wxSize(Hsize, Vsize)) pad.SetShape(shape) pad.SetAttribute(pcbnew.PAD_ATTRIB_STANDARD) pad.SetLayerSet(pad.StandardMask()) pad.SetDrillSize(pcbnew.wxSize(drill, drill)) pad.SetOrientation(rot_degree*10) # rotation is in 0.1 degrees return pad
def get_via_module(board, drill, diameter, net): n = 'VIA-{}-{}'.format(fmt(drill), fmt(diameter)) # instantiate new module m = pcbnew.MODULE(board) m.SetReference('REF**') m.SetValue(n) m.SetLastEditTime(pcbnew.GetNewTimeStamp()) # adjust reference and value display settings r = m.Reference() r.SetVisible(False) r.SetTextSize(pcbnew.wxSize(mm2kicad(0.3), mm2kicad(0.3))) r.SetThickness(mm2kicad(0.075)) v = m.Value() v.SetVisible(False) v.SetTextSize(pcbnew.wxSize(mm2kicad(0.3), mm2kicad(0.3))) v.SetThickness(mm2kicad(0.075)) #v.SetLayer() # TODO: Move value to F.Fab layer (F.SilkS is default) lib_id = pcbnew.LIB_ID(n) m.SetFPID(lib_id) # create through-hole pad pad = pcbnew.D_PAD(m) pad.SetPadName('1') pad.SetNet(net) pad.SetPosition(pcbnew.wxPoint(0, 0)) pad.SetShape(pcbnew.PAD_SHAPE_CIRCLE) pad.SetSize(pcbnew.wxSize(diameter, diameter)) pad.SetDrillShape(pcbnew.PAD_DRILL_SHAPE_CIRCLE) pad.SetDrillSize(pcbnew.wxSize(drill, drill)) pad.SetLayerSet(pcbnew.LSET.AllCuMask()) pad.SetZoneConnection(pcbnew.PAD_ZONE_CONN_FULL) # add pad to module m.Add(pad) return m
def createPad(self, number, name): top = number % 2 == 1 if self.omitPin(number): return None padTotalHeight = topPadHeight if top else bottomPadHeight padHeight = padTotalHeight - padVerticalOffset padSize = pcbnew.wxSize(padWidth, padHeight) padOneCenterX = pcbnew.FromMM(18 * 0.5 + 0.25) padTwoCenterX = padOneCenterX + pcbnew.FromMM(0.25) pad = pcbnew.D_PAD(self.module) layerSet = pcbnew.LSET() if top: # On the top, 0.0 is centered between pads 35 and 37. padOffset = (number - 1) / 2 padCenterX = padOneCenterX - pcbnew.FromMM(padOffset * 0.5) layerSet.AddLayer(pcbnew.F_Cu) else: # On the bottom, 0.0 is the center of pad 36. padOffset = (number) / 2 padCenterX = padTwoCenterX - pcbnew.FromMM(padOffset * 0.5) layerSet.AddLayer(pcbnew.B_Cu) padCenterY = -(padVerticalOffset + padHeight / 2.0) padCenter = pcbnew.wxPoint(padCenterX, padCenterY) pad.SetSize(padSize) pad.SetPos0(padCenter) pad.SetPosition(padCenter) pad.SetShape(pcbnew.PAD_SHAPE_RECT) pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD) pad.SetLayerSet(layerSet) pad.SetPadName(name) return pad
def smdRectPad(module, size, pos, name, angle, net): '''Build a rectangular pad.''' pad = pcbnew.D_PAD(module) pad.SetSize(size) pad.SetShape(pcbnew.PAD_SHAPE_RECT) pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD) # Set only the copper layer without mask # since nothing is mounted on these pads pad.SetLayerSet(pcbnew.LSET(pcbnew.F_Cu)) # Get active layer 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) pad.SetNet(net) return pad
def Run(self): """ The entry function of the plugin that is executed on user action """ board = pcbnew.GetBoard() # TODO also aggregate and bin by optional attributes, like tolerances radii = set() circles = [] # TODO don't add an extra hole in same place -> overwrite # get circles drawn on board # TODO maybe include some means of filtering out possible circular board outline drawing_list = board.DrawingsList() for d in drawing_list: if d.GetLayerName() == 'Edge.Cuts': # to gaurd against random stuff being on edge cuts for some reason # causing weird errors assert type( d) == pcbnew.DRAWSEGMENT, 'non-DRAWSEGMENT on Edge.Cuts' if d.GetShapeStr() == 'Circle': # TODO check these are what i want, and don't need modification # w/ thickness or whatever (check using dxf import) r = d.GetRadius() radii.add(r) c = d.GetCenter() circles.append((c.x, c.y, r)) # TODO make optional # how to do? board.RemoveNative(d) # TODO option to use nearest hole in existing library option / next largest # (report error) # TODO easiest way to get available footprints (and their radii?) # TODO option to take a set of target hole sizes (for DFM purposes) # TODO include either global use next largest / next smallest # (maybe ignore locked, and say that, to complement this?) # or let people check for each hole whether they want to use next up or down # TODO option to make new hole components and put in project specific library # TODO include field for library to be saved to (maybe mandatory if this option?) # TODO fail gracefully if board filename is empty (board not saved yet) target_footprint_lib = '.'.join(str(board.GetFileName()).split('.')[:-1]) \ + '.pretty' if not os.path.exists(target_footprint_lib): print('creating project specific footprint library' + \ ' {}'.format(target_footprint_lib)) pcbnew.PCB_IO().FootprintLibCreate(target_footprint_lib) else: print('using footprint library {}'.format(target_footprint_lib)) # make a new footprint for each distinct hole radius we need # save to project specific library by default for r in radii: r_mm = nm_to_mm(r) print('generating footprint for hole of radius {}mm'.format(r_mm)) footprint = pcbnew.MODULE(None) # for non-electronic parts like these footprint.SetAttributes(pcbnew.MOD_VIRTUAL) footprint.SetLastEditTime() footprint.SetKeywords('non-plated through hole NPTH') # TODO it looks like the module itself needs to be specified to be on F.Cu? # how? (see github for example output) # TODO set tags to include non-plated through hole, npth? #footprint.SetDescription('{}mm diameter non-plated through hole') pad = pcbnew.D_PAD(footprint) # other library mounting hole pads seem to have this pad number pad.SetName('1') # TODO does this change the layers and stuff automatically? pad shape? pad.SetAttribute(pcbnew.PAD_ATTRIB_HOLE_NOT_PLATED) pad.SetShape(pcbnew.PAD_SHAPE_CIRCLE) # TODO do i need setsize / setstartend? # size seems to equal (r, r) for existing npth library parts # all holes in KiCad currently have to reside on one of the (external?) # copper layers (F.Cu should be one of these layers. mask the other?) pad.SetLayerSet(pcbnew.D_PAD_UnplatedHoleMask()) # TODO are holes alone actually rendered, or do you need the other stuff? # there seems to be a PAD_DRILL_SHAPE_CIRCLE and PAD_DRILL_SHAPE_OBLONG # but when are they ever not circular? oblong = limited routing? # would advanced circuits do oblong, for instance? #pad.SetDrillShape() # huh??? # TODO why isn't this a single number? what gens (drill X)? # TODO why is x in (size x x) slightly larger than 1.5? pad.SetDrillSize(pcbnew.wxSize(r, r)) footprint.Add(pad) footprint_id = 'R{}mm_NPTH'.format(r_mm) footprint.SetReference(footprint_id) ''' footprint.Reference().SetPosition(pcbnew.wxPoint()1) footprint.Value().SetPosition(pcbnew.wxPoint()1) ''' # aiming for a visible layer that does not translate into manufacture comment_layer_id = get_layer_id_by_name('Cmts.User') footprint.Reference().SetLayer(comment_layer_id) footprint.Value().SetLayer(comment_layer_id) # this is the filename, preceeding .kicad_mod fpid = pcbnew.LIB_ID(footprint_id) footprint.SetFPID(fpid) # seems to work to lock all imports by default footprint.SetLocked(True) print('saving footprint to {}'.format(os.path.join(\ target_footprint_lib, footprint_id + '.kicad_mod'))) pcbnew.PCB_IO().FootprintSave(target_footprint_lib, footprint) for x, y, r_curr in circles: if r == r_curr: f_new = pcbnew.PCB_IO().FootprintLoad( target_footprint_lib, footprint_id) f_new.SetPosition(pcbnew.wxPoint(x, y)) board.Add(f_new)
def ChamferFootprint(self, center=pcbnew.wxPoint(0, 0)): self.module = pcbnew.MODULE(None) # Create new module self.angle = m.radians(self.angle_deg) # Calculate the miter w = self.width # Width of the corner from edge of the corner to inside corner corner_width = pcbnew.ToMM(w) / m.cos(self.angle / 2) # Get proportion of width to cut cut = self.OptimalMiter() print("Cut: {0:.2f}%".format(cut * 100)) # Distance from uncut outside corner point to point 7 cut = pcbnew.FromMM(cut * corner_width / m.cos( (m.pi - self.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 - self.width * m.tan(self.angle / 2), w / 2) # Distance between points 3 and 4 x34 = a * m.sin(self.angle) y34 = a * m.cos(self.angle) # Distance between points 4 and 5 x45 = self.width * m.cos(self.angle) y45 = self.width * m.sin(self.angle) # Distance from point 7 to 1 (no x-component) d71 = a + self.width * m.tan(self.angle / 2) - cut # 1 2 # +--+ # | |3 # 7 \ --+ 4 # \ | # \--+ 5 # 6 dW = w / 55 # To prevent tracks from over-extending points = [ (-w / 2, -d71 / 2), (w / 2, -d71 / 2), (w / 2, a - d71 / 2 + dW), (w / 2 + x34 - (d71 / 2 - d71 / 1e5 - dW) * m.sin(self.angle), a + y34 - d71 / 2 - (d71 / 2 - d71 / 1e5 - dW) * m.cos(self.angle) + dW), (w / 2 + x34 - x45 - (d71 / 2 - d71 / 1e5 - dW) * m.sin(self.angle), a + y34 + y45 - d71 / 2 - (d71 / 2 - d71 / 1e5 - dW) * m.cos(self.angle) + dW), (cut * m.sin(self.angle) - w / 2, a + self.width * m.tan(self.angle / 2) + cut * m.cos(self.angle) - d71 / 2 + dW), (-w / 2, a + self.width * m.tan(self.angle / 2) - cut - d71 / 2 + dW) ] # Last two points can be equal if points[-2] == points[-1]: points = points[:-1] points = [pcbnew.wxPoint(*point) for point in points] # Custom pad cs_pad = pcbnew.D_PAD(self.module) cs_pad.SetSize(pcbnew.wxSize(w, d71)) cs_pad.SetShape(pcbnew.PAD_SHAPE_CUSTOM) cs_pad.SetAnchorPadShape(pcbnew.PAD_SHAPE_RECT) cs_pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD) cs_pad.SetLayerSet(pcbnew.LSET(pcbnew.F_Cu)) cs_pad.AddPrimitive(points, 0) cs_pad.SetLocalClearance(1) cs_pad.SetNet(self.net) cs_pad.SetPadName("1") self.module.Add(cs_pad) # Halfway between points 4 and 5 posx = ((w / 2 + x34) + (w / 2 + x34 - x45)) / 2 posy = ((a + y34 - d71 / 2) + (a + y34 + y45 - d71 / 2)) / 2 # Position pad so that pad edge touches polygon edge posx -= (d71 / 2 - dW) * m.sin(self.angle) posy -= (d71 / 2 - dW) * m.cos(self.angle) - dW size_pad = pcbnew.wxSize(d71, w) self.module.Add( Layout.smdRectPad(self.module, pcbnew.wxSize(w, d71), pcbnew.wxPoint(0, 0), "1", 0, self.net)) # Alignment pad self.module.Add( Layout.smdRectPad(self.module, size_pad, pcbnew.wxPoint(posx, posy), "1", (self.angle_deg - 90) * 10, self.net)) self.module.Rotate(self.module.GetPosition(), (90 + self.angle_deg) * 100) # self.module.MoveAnchorPosition(pcbnew.wxPoint((d71/2 - a - w/2)*m.sin(self.angle), \ # (d71/2 - a - w/2)*m.cos(self.angle))) self.module.SetPosition(center) if self.layer == pcbnew.B_Cu: self.module.Flip(self.module.GetCenter()) elif self.layer == pcbnew.F_Cu: self.module.Rotate(self.module.GetPosition(), (90 + self.angle_deg) * 100) # Find center of bounding box for placement # Add to Pcbnew pcbnew.GetBoard().Add(self.module) pcbnew.Refresh()
def hole_pad(board, module, size, drill_size, pos, net, pad_type="round"): # round|first_half|second_half pad = pcbnew.D_PAD(module) pad.SetSize(size) pad.SetDrillSize(drill_size) # pad.SetPos0(pos) # don't do this, pcbnew will freak out after save/open pad.SetPosition(pos) if net == ensure_net(board, "VCC") \ or net == ensure_net(board, "GND") \ or net == 0: # thru-holes pad.SetAttribute(pcbnew.PAD_ATTRIB_STANDARD) pad.SetLayerSet(pad.StandardMask()) if net == ensure_net(board, "VCC"): pad.SetShape(pcbnew.PAD_SHAPE_RECT) pad.SetOrientation(deg(45)) else: pad.SetShape(pcbnew.PAD_SHAPE_CIRCLE) else: # bridge pads pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD) if pad_type == "round": pad.SetShape(pcbnew.PAD_SHAPE_CIRCLE) else: pad.SetShape(pcbnew.PAD_SHAPE_CUSTOM) r = int(size.GetWidth() / 2) inverter = 1 if pad_type == "first_half": inverter = -1 hack_shift = int(r / 2) gap = int(GAP / 2) * inverter pad.SetPosition( pcbnew.wxPoint(pos.x, pos.y + hack_shift * inverter + gap)) module.SetPosition( pcbnew.wxPoint(module.GetPosition().x, module.GetPosition().y + gap)) # XXX yet another hack pad.SetSize(pcbnew.wxSize(int(r * 0.9), int( r * 0.9))) # base shape (a tad smaller) # ^ base shape size can't be 0 because 3d view asserts != 0 # ^ base shape size can't be 1 because somehow you'll end up with no copper # ^ base shape size can't be small because KiCad will find a way to break apart points = pcbnew.wxPoint_Vector() points.append(pcbnew.wxPoint(-r, -hack_shift * inverter)) for x in range(-r + int(COPPER_STEP_EVERY), +r, int(COPPER_STEP_EVERY)): # y = sqrt(r**2 - x**2) points.append( pcbnew.wxPoint(x, (int(math.sqrt(r**2 - x**2)) - hack_shift) * inverter)) points.append(pcbnew.wxPoint(+r, -hack_shift * inverter)) pad.AddPrimitive(points, 0) # 0 thickness = filled layer_set = pad.SMDMask().RemoveLayer(board.GetLayerID("F.Paste")) if "_B" in board.GetNetsByNetcode()[net].GetNetname(): # XXX we should be flipping the module, not the pad pad.SetLayerSet(pcbnew.FlipLayerMask(layer_set)) pad.Rotate(pos, 90 * 10) else: pad.SetLayerSet(layer_set) pad.Rotate(pos, 0) pad.SetPadName("0") pad.SetNet(board.GetNetsByNetcode()[net]) module.Add(pad)