Source code for KicadModTree.nodes.specialized.PadArray

#!/usr/bin/env python

# KicadModTree is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# KicadModTree is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with kicad-footprint-generator. If not, see < http://www.gnu.org/licenses/ >.
#
# (C) 2017 by @SchrodingersGat
# (C) 2017 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>

from types import GeneratorType
from KicadModTree.nodes.base.Pad import *
from KicadModTree.nodes.specialized.ChamferedPad import *
from KicadModTree.nodes.Node import Node

from KicadModTree.util.paramUtil import *


[docs]class PadArray(Node): r"""Add a row of Pads Simplifies the handling of pads which are rendered in a specific form :param \**kwargs: See below :Keyword Arguments: * *start* (``Vector2D``) -- start edge of the pad array * *center* (``Vector2D``) -- center pad array around specific point * *pincount* (``int``) -- number of pads to render * *spacing* (``Vector2D``, ``float``) -- offset between rendered pads * *x_spacing* (``float``) -- x offset between rendered pads * *y_spacing* (``float``) -- y offset between rendered pads * *initial* (``int``) -- name of the first pad * *increment* (``int, function(previous_number)``) -- declare how the name of the follow up is calculated * *type* (``Pad.TYPE_THT``, ``Pad.TYPE_SMT``, ``Pad.TYPE_CONNECT``, ``Pad.TYPE_NPTH``) -- type of the pad * *shape* (``Pad.SHAPE_CIRCLE``, ``Pad.SHAPE_OVAL``, ``Pad.SHAPE_RECT``, ``Pad.SHAPE_TRAPEZE``, ...) -- shape of the pad * *rotation* (``float``) -- rotation of the pad * *size* (``float``, ``Vector2D``) -- size of the pad * *offset* (``Vector2D``) -- offset of the pad * *drill* (``float``, ``Vector2D``) -- drill-size of the pad * *solder_paste_margin_ratio* (``float``) -- solder paste margin ratio of the pad * *layers* (``Pad.LAYERS_SMT``, ``Pad.LAYERS_THT``, ``Pad.LAYERS_NPTH``) -- layers on which are used for the pad * *chamfer_corner_selection_first* (``[bool, bool, bool, bool]``) Select which corner should be chamfered for the first pad. (default: None) * *chamfer_corner_selection_last* (``[bool, bool, bool, bool]``) Select which corner should be chamfered for the last pad. (default: None) * *chamfer_size* (``float``, ``Vector2D``) -- size for the chamfer used for the end pads. (default: None) * *end_pads_size_reduction* (``dict with keys x-,x+,y-,y+``) -- size is reduced on the given side. (size reduced plus center moved.) * *tht_pad1_shape* (``Pad.SHAPE_RECT``, ``Pad.SHAPE_ROUNDRECT``, ...) -- shape for marking pad 1 for through hole components. (deafult: ``Pad.SHAPE_ROUNDRECT``) * *tht_pad1_id* (``int, string``) -- pad number used for "pin 1" (default: 1) * *hidden_pins* (``int, Vector1D``) -- pin number(s) to be skipped; a footprint with hidden pins has missing pads and matching pin numbers * *deleted_pins* (``int, Vector1D``) -- pin locations(s) to be skipped; a footprint with deleted pins has pads missing but no missing pin numbers" :Example: >>> from KicadModTree import * >>> PadArray(pincount=10, spacing=[1,-1], center=[0,0], initial=5, increment=2, ... type=Pad.TYPE_SMT, shape=Pad.SHAPE_RECT, size=[1,2], layers=Pad.LAYERS_SMT) """ def __init__(self, **kwargs): Node.__init__(self) self._initPincount(**kwargs) self._initIncrement(**kwargs) self._initInitialNumber(**kwargs) self._initSpacing(**kwargs) self._initStartingPosition(**kwargs) self.virtual_childs = self._createPads(**kwargs) # How many pads in the array def _initPincount(self, **kwargs): if not kwargs.get('pincount'): raise KeyError('pincount not declared (like "pincount=10")') self.pincount = kwargs.get('pincount') if type(self.pincount) is not int or self.pincount <= 0: raise ValueError('{pc} is an invalid value for pincount'.format(pc=self.pincount)) if kwargs.get('hidden_pins') and kwargs.get('deleted_pins'): raise KeyError('hidden pins and deleted pins cannot be used together') self.exclude_pin_list = [] if kwargs.get('hidden_pins'): # exclude_pin_list is for pads being removed based on pad number # deleted pins are filtered out later by pad location (not number) self.exclude_pin_list = kwargs.get('hidden_pins') if type(self.exclude_pin_list) not in [list, tuple]: raise TypeError('exclude pin list must be specified like "exclude_pin_list=[0,1]"') elif any([type(i) not in [int] for i in self.exclude_pin_list]): raise ValueError('exclude pin list must be integer value') # Where to start the aray def _initStartingPosition(self, **kwargs): """ can use the 'start' argument to start a pad array at a given position OR can use the 'center' argument to center the array around the given position """ self.startingPosition = [0, 0] # Start takes priority if kwargs.get('start'): self.startingPosition = kwargs.get('start') if type(self.startingPosition) not in [list, tuple] or not len(self.startingPosition) == 2: raise ValueError('array starting position "start" must be given as an list of length two') if any([type(i) not in [int, float] for i in self.startingPosition]): raise ValueError('array starting coordinates must be numerical') elif kwargs.get('center'): center = kwargs.get('center') if type(center) not in [list, tuple] or not len(center) == 2: raise ValueError('array center position "center" must be given as a list of length two') if any([type(i) not in [int, float] for i in center]): raise ValueError('array center coordinates must be numerical') # Now calculate the desired starting position of the array self.startingPosition[0] = center[0] - (self.pincount - 1) * self.spacing[0] / 2. self.startingPosition[1] = center[1] - (self.pincount - 1) * self.spacing[1] / 2. # What number to start with? def _initInitialNumber(self, **kwargs): self.initialPin = kwargs.get('initial', 1) if self.initialPin == "": self.increment = 0 elif type(self.initialPin) is not int or self.initialPin < 1: if not callable(self.increment): raise ValueError('{pn} is not a valid starting pin number if increment is not a function' .format(pn=self.initialPin)) # Pin incrementing def _initIncrement(self, **kwargs): self.increment = kwargs.get('increment', 1) # Pad spacing def _initSpacing(self, **kwargs): """ spacing can be given as: spacing = [1,2] # high priority x_spacing = 1 y_spacing = 2 """ self.spacing = [0, 0] # [x, y] if kwargs.get('spacing'): self.spacing = kwargs.get('spacing') if type(self.spacing) not in [list, tuple]: raise TypeError('spacing must be specified like "spacing=[0,1]"') elif len(self.spacing) is not 2: raise ValueError('spacing must be supplied as x,y pair') elif any([type(i) not in [int, float] for i in self.spacing]): raise ValueError('spacing must be numerical value') # if 'spacing' is specified, ignore x_spacing and y_spacing return if kwargs.get('x_spacing'): self.spacing[0] = kwargs.get('x_spacing') if type(self.spacing[0]) not in [int, float]: raise ValueError('x_spacing must be supplied as numerical value') if kwargs.get('y_spacing'): self.spacing[1] = kwargs.get('y_spacing') if type(self.spacing[1]) not in [int, float]: raise ValueError('y_spacing must be supplied as numerical value') if all([i == 0 for i in self.spacing]): raise ValueError('pad spacing ({sp}) must be non-zero'.format(sp=self.spacing)) def _createPads(self, **kwargs): pads = [] x_start, y_start = self.startingPosition x_spacing, y_spacing = self.spacing padShape = kwargs.get('shape') # Special case, increment = 0 # this can be used for creating an array with all the same pad number if self.increment == 0: pad_numbers = [self.initialPin] * self.pincount elif type(self.increment) == int: pad_numbers = range(self.initialPin, self.initialPin + (self.pincount * self.increment), self.increment) elif callable(self.increment): pad_numbers = [self.initialPin] for idx in range(1, self.pincount): pad_numbers.append(self.increment(pad_numbers[-1])) elif type(self.increment) == GeneratorType: pad_numbers = [next(self.increment) for i in range(self.pincount)] else: raise TypeError("Wrong type for increment. It must be either a int, callable or generator.") end_pad_params = copy(kwargs) if kwargs.get('end_pads_size_reduction'): size_reduction = kwargs['end_pads_size_reduction'] end_pad_params['size'] = toVectorUseCopyIfNumber(kwargs.get('size'), low_limit=0) delta_size = Vector2D( size_reduction.get('x+', 0) + size_reduction.get('x-', 0), size_reduction.get('y+', 0) + size_reduction.get('y-', 0) ) end_pad_params['size'] -= delta_size delta_pos = Vector2D( -size_reduction.get('x+', 0) + size_reduction.get('x-', 0), -size_reduction.get('y+', 0) + size_reduction.get('y-', 0) )/2 else: delta_pos = Vector2D(0, 0) for i, number in enumerate(pad_numbers): includePad = True # deleted pins are filtered by pad/pin position (they are 'None' in pad_numbers list) if type(number) not in [int, str]: includePad = False # hidden pins are filtered out by pad number (index of pad_numbers list) if not kwargs.get('deleted_pins'): if type(self.initialPin) == 'int': includePad = (self.initialPin + i) not in self.exclude_pin_list else: includePad = number not in self.exclude_pin_list if includePad: current_pad_pos = Vector2D( x_start + i * x_spacing, y_start + i * y_spacing ) current_pad_params = copy(kwargs) if i == 0 or i == len(pad_numbers)-1: current_pad_pos += delta_pos current_pad_params = end_pad_params if kwargs.get('type') == Pad.TYPE_THT and number == kwargs.get('tht_pad1_id', 1): current_pad_params['shape'] = kwargs.get('tht_pad1_shape', Pad.SHAPE_ROUNDRECT) if 'radius_ratio' not in current_pad_params: current_pad_params['radius_ratio'] = 0.25 if 'maximum_radius' not in current_pad_params: current_pad_params['maximum_radius'] = 0.25 else: current_pad_params['shape'] = padShape if kwargs.get('chamfer_size'): if i == 0 and 'chamfer_corner_selection_first' in kwargs: pads.append( ChamferedPad( number=number, at=current_pad_pos, corner_selection=kwargs.get('chamfer_corner_selection_first'), **current_pad_params )) continue if i == len(pad_numbers)-1 and 'chamfer_corner_selection_last' in kwargs: pads.append( ChamferedPad( number=number, at=current_pad_pos, corner_selection=kwargs.get('chamfer_corner_selection_last'), **current_pad_params )) continue pads.append(Pad(number=number, at=current_pad_pos, **current_pad_params)) return pads
[docs] def getVirtualChilds(self): return self.virtual_childs