pointhi/kicad-footprint-generator

View on GitHub
KicadModTree/nodes/specialized/ChamferedPadGrid.py

Summary

Maintainability
F
1 wk
Test Coverage
# 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) 2016 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
# (C) 2018 by Rene Poeschl, github @poeschlr

from __future__ import division

from KicadModTree.util.paramUtil import *
from KicadModTree.Vector import *
from KicadModTree.nodes.base.Polygon import *
from KicadModTree.nodes.specialized.ChamferedPad import *


class ChamferSelPadGrid(CornerSelection):
    r"""Class for handling chamfer selection
        :param chamfer_select:
            * A list of bools do directly set the corners
              (top left, top right, bottom right, bottom left)
            * A dict with keys (Constands see below)
            * The integer 1 means all corners and edges
            * The integer 0 means no corners, no edges

        :constants:
            * ChamferSelPadGrid.TOP_EDGE
            * ChamferSelPadGrid.RIGHT_EDGE
            * ChamferSelPadGrid.BOTTOM_EDGE
            * ChamferSelPadGrid.LEFT_EDGE
            * Plus all constands inherited from CornerSelection

    """

    TOP_EDGE = "t"
    RIGHT_EDGE = "r"
    BOTTOM_EDGE = "b"
    LEFT_EDGE = "l"

    def __init__(self, chamfer_select):
        self.top_edge = False
        self.right_edge = False
        self.bottom_edge = False
        self.left_edge = False

        if chamfer_select == 1:
            self.selectAll()
            CornerSelection.__init__(self, 1)
            return

        if chamfer_select == 0:
            CornerSelection.__init__(self, 0)
            return

        if type(chamfer_select) is dict:
            CornerSelection.__init__(self, chamfer_select)
            for key in chamfer_select:
                self[key] = bool(chamfer_select[key])
        else:
            for i, value in enumerate(chamfer_select):
                self[i] = bool(value)

    def setLeft(self, value=1):
        CornerSelection.setLeft(self, value)
        self.left_edge = bool(value)

    def setTop(self, value=1):
        CornerSelection.setTop(self, value)
        self.top_edge = bool(value)

    def setRight(self, value=1):
        CornerSelection.setRight(self, value)
        self.right_edge = bool(value)

    def setBottom(self, value=1):
        CornerSelection.setBottom(self, value)
        self.bottom_edge = bool(value)

    def setCorners(self, value=1):
        self.top_left = bool(value)
        self.top_right = bool(value)
        self.bottom_right = bool(value)
        self.bottom_left = bool(value)

    def setEdges(self, value=1):
        self.top_edge = bool(value)
        self.right_edge = bool(value)
        self.bottom_edge = bool(value)
        self.left_edge = bool(value)

    def __len__(self):
        return 8

    def __iter__(self):
        for v in CornerSelection.__iter__(self):
            yield v
        yield self.top_edge
        yield self.right_edge
        yield self.bottom_edge
        yield self.left_edge

    def __getitem__(self, item):
        if item in [4, ChamferSelPadGrid.TOP_EDGE]:
            return self.top_edge
        if item in [5, ChamferSelPadGrid.RIGHT_EDGE]:
            return self.right_edge
        if item in [6, ChamferSelPadGrid.BOTTOM_EDGE]:
            return self.bottom_edge
        if item in [7, ChamferSelPadGrid.LEFT_EDGE]:
            return self.left_edge
        return CornerSelection.__getitem__(self, item)

    def __setitem__(self, item, value):
        if item in [4, ChamferSelPadGrid.TOP_EDGE]:
            self.top_edge = bool(value)
        elif item in [5, ChamferSelPadGrid.RIGHT_EDGE]:
            self.right_edge = bool(value)
        elif item in [6, ChamferSelPadGrid.BOTTOM_EDGE]:
            self.bottom_edge = bool(value)
        elif item in [7, ChamferSelPadGrid.LEFT_EDGE]:
            self.left_edge = bool(value)
        else:
            CornerSelection.__setitem__(self, item, value)

    def to_dict(self):
        result = CornerSelection.to_dict(self)
        result.update({
            ChamferSelPadGrid.TOP_EDGE: self.top_edge,
            ChamferSelPadGrid.RIGHT_EDGE: self.right_edge,
            ChamferSelPadGrid.BOTTOM_EDGE: self.bottom_edge,
            ChamferSelPadGrid.LEFT_EDGE: self.left_edge
            })
        return result


class ChamferedPadGrid(Node):
    r"""Add a ChamferedPad to the render tree

    :param \**kwargs:
        See below

    :Keyword Arguments:
        * *number* (``int``, ``str``) --
          number/name of the pad (default: \"\")
        * *center* (``Vector2D``) --
          center position of the pad grid
        * *size* (``float``, ``Vector2D``) --
          size of the pads
        * *pincount* (``int``, ``[int, int]``)
          Pad count in x and y direction.
          If only one integer is given, it will be used for both directions.
        * *grid* (``float``, ``Vector2D``)
          Pad grid in x and y direction.
          If only one float is given, it will be used for both directions.

        * *solder_paste_margin_ratio* (``float``) --
          solder paste margin ratio of the pad (default: 0)
        * *solder_paste_margin* (``float``) --
          solder paste margin of the pad (default: 0)
        * *solder_mask_margin* (``float``) --
          solder mask margin of the pad (default: 0)

        * *layers* (``Pad.LAYERS_SMT``, ``Pad.LAYERS_THT``, ``Pad.LAYERS_NPTH``) --
          layers on which are used for the pad
        * *chamfer_selection* (``ChamferSelPadGrid``) --
          Select which corner and edge pads to chamfer.
        * *chamfer_size* (``float``, ``Vector2D``) --
          Size of the chamfer.
        * *x_mirror* (``[int, float](mirror offset)``) --
          mirror x direction around offset "point"
        * *y_mirror* (``[int, float](mirror offset)``) --
          mirror y direction around offset "point"

        * *radius_ratio* (``float``) --
          The radius ratio of the rounded rectangle.
          (default 0 for backwards compatibility)
        * *maximum_radius* (``float``) --
          The maximum radius for the rounded rectangle.
          If the radius produced by the radius_ratio parameter for the pad would
          exceed the maximum radius, the ratio is reduced to limit the radius.
          (This is useful for IPC-7351C compliance as it suggests 25% ratio with limit 0.25mm)
        * *round_radius_exact* (``float``) --
          Set an exact round radius for a pad.
        * *round_radius_handler* (``RoundRadiusHandler``) --
          An instance of the RoundRadiusHandler class
          If this is given then all other round radius specifiers are ignored
    """

    def __init__(self, **kwargs):
        Node.__init__(self)
        if len(kwargs) == 0:
            return

        self.number = kwargs.get('number', "")
        self.center = Vector2D(kwargs.get('center', [0, 0]))

        self._initSize(**kwargs)
        self._initCount(**kwargs)
        self._initGrid(**kwargs)
        self._initPadSettings(**kwargs)

    def _initCount(self, **kwargs):
        if 'pincount' not in kwargs:
            raise KeyError('pincount not declared (like "pincount=10")')

        self.pincount = toIntArray(kwargs['pincount'])

    def _initSize(self, **kwargs):
        if 'size' not in kwargs:
            raise KeyError('size not declared (like "size=[1, 2]")')

        self.size = toVectorUseCopyIfNumber(kwargs['size'], low_limit=0)

    def _initGrid(self, **kwargs):
        if 'grid' not in kwargs:
            raise KeyError('grid not declared (like "grid=[1, 2]")')

        self.grid = toVectorUseCopyIfNumber(kwargs['grid'], low_limit=self.size)

    def _initPadSettings(self, **kwargs):
        if 'chamfer_selection' not in kwargs:
            raise KeyError('chamfer selection is required for chamfered pads (like "chamfer_selection=[1,0,0,0]")')

        self.chamfer_selection = ChamferSelPadGrid(kwargs.get('chamfer_selection'))

        if 'chamfer_size' not in kwargs:
            self.chamfer_size = Vector2D(0, 0)
        else:
            self.chamfer_size = toVectorUseCopyIfNumber(
                kwargs.get('chamfer_size'), low_limit=0, must_be_larger=False)

        if('round_radius_handler' in kwargs):
            self.round_radius_handler = kwargs['round_radius_handler']
        else:
            # default radius ration 0 for backwards compatibility
            self.round_radius_handler = RoundRadiusHandler(default_radius_ratio=0, **kwargs)

        self.padargs = copy(kwargs)
        self.padargs.pop('size', None)
        self.padargs.pop('number', None)
        self.padargs.pop('at', None)
        self.padargs.pop('chamfer_size', None)
        self.padargs.pop('round_radius_handler', None)

    def chamferAvoidCircle(self, center, diameter, clearance=0):
        r""" set the chamfer such that the pad avoids a cricle located at near corner.

        :param center: (``Vector2D``) --
           The center of the circle ot avoid
        :param diameter: (``float``, ``Vector2D``) --
           The diameter of the circle. If Vector2D given only x direction is used.
        :param clearance: (``float``) --
           Additional clearance around circle. default:0
        """
        relative_center = Vector2D(center) - self.center

        left = -self.grid['x']*(self.pincount[0]-1)/2
        top = -self.grid['y']*(self.pincount[1]-1)/2

        nearest_x = left
        nearest_y = top

        min_dist_x = abs(relative_center['x']-nearest_x)
        min_dist_y = abs(relative_center['y']-nearest_y)

        for i in range(self.pincount[0]):
            x = left+i*self.grid['x']
            dx = abs(x-relative_center['x'])
            if dx < min_dist_x:
                min_dist_x = dx
                nearest_x = x

        for i in range(self.pincount[1]):
            y = top+i*self.grid['y']
            dy = abs(y-relative_center['y'])
            if dy < min_dist_y:
                min_dist_y = dy
                nearest_y = y

        temp_pad = ChamferedPad(
            at=[nearest_x, nearest_y], size=self.size,
            type=Pad.TYPE_SMT, layers=['F.Cu'], corner_selection=1
        )
        self.chamfer_size = temp_pad.chamferAvoidCircle(
            center=relative_center, diameter=diameter, clearance=clearance)
        return self.chamfer_size

    def __padCornerSelection(self, idx_x, idx_y):
        corner = CornerSelection(0)
        if idx_x == 0:
            if idx_y == 0:
                if self.chamfer_selection[ChamferSelPadGrid.TOP_LEFT]:
                    corner[CornerSelection.TOP_LEFT] = True
                if self.chamfer_selection[ChamferSelPadGrid.LEFT_EDGE]:
                    corner[CornerSelection.BOTTOM_LEFT] = True
            if idx_y == self.pincount[1]-1:
                if self.chamfer_selection[ChamferSelPadGrid.BOTTOM_LEFT]:
                    corner[CornerSelection.BOTTOM_LEFT] = True
                if self.chamfer_selection[ChamferSelPadGrid.LEFT_EDGE]:
                    corner[CornerSelection.TOP_LEFT] = True
            if idx_y != 0 and idx_y != self.pincount[1]-1:
                if self.chamfer_selection[ChamferSelPadGrid.LEFT_EDGE]:
                    corner.setLeft()
        if idx_x == self.pincount[0]-1:
            if idx_y == 0:
                if self.chamfer_selection[ChamferSelPadGrid.TOP_RIGHT]:
                    corner[CornerSelection.TOP_RIGHT] = True
                if self.chamfer_selection[ChamferSelPadGrid.RIGHT_EDGE]:
                    corner[CornerSelection.BOTTOM_RIGHT] = True
            if idx_y == self.pincount[1]-1:
                if self.chamfer_selection[ChamferSelPadGrid.BOTTOM_RIGHT]:
                    corner[CornerSelection.BOTTOM_RIGHT] = True
                if self.chamfer_selection[ChamferSelPadGrid.RIGHT_EDGE]:
                    corner[CornerSelection.TOP_RIGHT] = True
            if idx_y != 0 and idx_y != self.pincount[1]-1:
                if self.chamfer_selection[ChamferSelPadGrid.RIGHT_EDGE]:
                    corner.setRight()
        if idx_x != 0 and idx_x != self.pincount[0]-1:
            if idx_y == 0:
                if self.chamfer_selection[ChamferSelPadGrid.TOP_EDGE]:
                    corner.setTop()
            if idx_y == self.pincount[1]-1:
                if self.chamfer_selection[ChamferSelPadGrid.BOTTOM_EDGE]:
                    corner.setBottom()
        return corner

    def _generatePads(self):
        left = -self.grid['x']*(self.pincount[0]-1)/2+self.center['x']
        top = -self.grid['y']*(self.pincount[1]-1)/2+self.center['y']

        pads = []
        for idx_x in range(self.pincount[0]):
            x = left+idx_x*self.grid['x']
            for idx_y in range(self.pincount[1]):
                y = top+idx_y*self.grid['y']
                corner = self.__padCornerSelection(idx_x, idx_y)
                pads.append(ChamferedPad(
                    at=[x, y], number=self.number, size=self.size,
                    chamfer_size=self.chamfer_size,
                    corner_selection=corner,
                    round_radius_handler=self.round_radius_handler,
                    **self.padargs
                    ))
        return pads

    def getVirtualChilds(self):
        return self._generatePads()

    def __copy__(self):
        newone = type(self)()
        newone.__dict__.update(self.__dict__)
        return newone