Skip to content
This repository has been archived by the owner on Oct 7, 2020. It is now read-only.

Ep roundradius handling #422

Merged
merged 22 commits into from
Jan 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bf83ec7
Introduce more powerful round radius handler
poeschlr Sep 8, 2019
9241a3d
EP round radius update
poeschlr Sep 8, 2019
688d3ba
fix duplicate old style dimension
poeschlr Sep 8, 2019
08a4cf3
fix rounding of chamfered pad if handler is given
poeschlr Sep 8, 2019
343f887
update docstring
poeschlr Sep 8, 2019
24f55e8
better int/float param checking
poeschlr Sep 12, 2019
bb831c4
docstring
poeschlr Sep 15, 2019
6709c48
fix minor grammer spelling and formating mistakes
poeschlr Sep 26, 2019
7d48698
Better formating of format string
poeschlr Sep 27, 2019
baaddfa
correct exclude pin list for SOIC-4_4.55x3.7mm_P2.54mm
poeschlr Oct 9, 2019
44ff14f
Merge branch 'ep_roundradius_handling' of github.com:poeschlr/kicad-f…
poeschlr Oct 9, 2019
65dc6e5
Merge branch 'master' into ep_roundradius_handling
poeschlr Oct 10, 2019
c8e7299
VSON-10 has no place in the qfn/dfn lib
poeschlr Oct 10, 2019
13969f5
fix spelling mistakes
poeschlr Oct 10, 2019
53693b8
more options for ep rounding and default is now 0
poeschlr Nov 8, 2019
a2f9bb5
update readme with new ep rounded corner handling
poeschlr Nov 9, 2019
6dcbef1
Remove definition for footprint that required a very specialized manu…
poeschlr Jan 5, 2020
17ce20f
fix documentation links
poeschlr Jan 5, 2020
7b76c11
add page to documentation link
poeschlr Jan 13, 2020
2e50827
Merge branch 'master' into ep_roundradius_handling
poeschlr Jan 13, 2020
7ab2304
Backwardscompatibility: use default round radius 0 for chamfered and ep
poeschlr Jan 14, 2020
06d7d27
coding style
poeschlr Jan 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 126 additions & 20 deletions KicadModTree/nodes/base/Pad.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,111 @@
from KicadModTree.nodes.base.Polygon import Polygon


class RoundRadiusHandler(object):
r"""Handles round radius setting of a pad

:param \**kwargs:
See below

:Keyword Arguments:
* *radius_ratio* (``float [0 <= r <= 0.5]``) --
The radius ratio of the rounded rectangle. (default set by default_radius_ratio)
* *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.
* *default_radius_ratio* (``float [0 <= r <= 0.5]``) --
This parameter allows to set the default radius ratio
(backwards compatibility option for chamfered pads)
"""
def __init__(self, **kwargs):
default_radius_ratio = getOptionalNumberTypeParam(
kwargs, 'default_radius_ratio', default_value=0.25,
low_limit=0, high_limit=0.5)
self.radius_ratio = getOptionalNumberTypeParam(
kwargs, 'radius_ratio', default_value=default_radius_ratio,
low_limit=0, high_limit=0.5)

self.maximum_radius = getOptionalNumberTypeParam(kwargs, 'maximum_radius')
self.round_radius_exact = getOptionalNumberTypeParam(kwargs, 'round_radius_exact')

self.kicad4_compatible = kwargs.get('kicad4_compatible', False)

def getRadiusRatio(self, shortest_sidelength):
r"""get the resulting round radius ratio

:param shortest_sidelength: shortest sidelength of a pad
:return: the resulting round radius ratio to be used for the pad
"""
if self.kicad4_compatible:
return 0

if self.round_radius_exact is not None:
if self.round_radius_exact > shortest_sidelength/2:
raise ValueError(
"requested round radius of {} is too large for pad size of {}"
.format(self.round_radius_exact, pad_size)
)
if self.maximum_radius is not None:
return min(self.round_radius_exact, self.maximum_radius)/shortest_sidelength
else:
return self.round_radius_exact/shortest_sidelength
if self.maximum_radius is not None:
if self.radius_ratio*shortest_sidelength > self.maximum_radius:
return self.maximum_radius/shortest_sidelength

return self.radius_ratio

def getRoundRadius(self, shortest_sidelength):
r"""get the resulting round radius

:param shortest_sidelength: shortest sidelength of a pad
:return: the resulting round radius to be used for the pad
"""
return self.getRadiusRatio(shortest_sidelength)*shortest_sidelength

def roundingRequested(self):
r"""Check if the pad has a rounded corner

:return: True if rounded corners are required
"""
if self.kicad4_compatible:
return False

if self.maximum_radius == 0:
return False

if self.round_radius_exact == 0:
return False

if self.radius_ratio == 0:
return False

return True

def limitMaxRadius(self, limit):
r"""Set a new maximum limit

:param limit: the new limit.
"""

if not self.roundingRequested():
return
if self.maximum_radius is not None:
self.maximum_radius = min(self.maximum_radius, limit)
else:
self.maximum_radius = limit

def __str__(self):
return "ratio {}, max {}, exact {}, v4 compatible {}".format(
self.radius_ratio, self.maximum_radius,
self.round_radius_exact, self.kicad4_compatible
)


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

Expand All @@ -38,6 +143,9 @@ class Pad(Node):
* *shape* (``Pad.SHAPE_CIRCLE``, ``Pad.SHAPE_OVAL``, ``Pad.SHAPE_RECT``, ``SHAPE_ROUNDRECT``,
``Pad.SHAPE_TRAPEZE``, ``SHAPE_CUSTOM``) --
shape of the pad
* *layers* (``Pad.LAYERS_SMT``, ``Pad.LAYERS_THT``, ``Pad.LAYERS_NPTH``) --
layers on which are used for the pad

* *at* (``Vector2D``) --
center position of the pad
* *rotation* (``float``) --
Expand All @@ -48,23 +156,31 @@ class Pad(Node):
offset of the pad
* *drill* (``float``, ``Vector2D``) --
drill-size of the pad

* *radius_ratio* (``float``) --
The radius ratio of the rounded rectangle.
Ignored for every other shape.
Ignored for every shape except round rect.
* *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 ratio.
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)
Ignored for every other shape.
Ignored for every shape except round rect.
* *round_radius_exact* (``float``) --
Set an exact round radius for a pad.
Ignored for every shape except round rect
* *round_radius_handler* (``RoundRadiusHandler``) --
An instance of the RoundRadiusHandler class
If this is given then all other round radius specifiers are ignored
Ignored for every shape except round rect

* *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

* *x_mirror* (``[int, float](mirror offset)``) --
mirror x direction around offset "point"
* *y_mirror* (``[int, float](mirror offset)``) --
Expand Down Expand Up @@ -210,22 +326,12 @@ def _initLayers(self, **kwargs):
self.layers = kwargs.get('layers')

def _initRadiusRatio(self, **kwargs):
radius_ratio = kwargs.get('radius_ratio', 0)
if type(radius_ratio) not in [int, float]:
raise TypeError('radius ratio needs to be of type int or float')
if radius_ratio >= 0 and radius_ratio <= 0.5:
self.radius_ratio = radius_ratio
if('round_radius_handler' in kwargs):
self.round_radius_handler = kwargs['round_radius_handler']
else:
raise ValueError('radius ratio out of allowed range (0 <= rr <= 0.5)')

if kwargs.get('maximum_radius') is not None:
maximum_radius = kwargs.get('maximum_radius')
if type(maximum_radius) not in [int, float]:
raise TypeError('maximum radius needs to be of type int or float')
self.round_radius_handler = RoundRadiusHandler(**kwargs)

shortest_sidelength = min(self.size)
if self.radius_ratio*shortest_sidelength > maximum_radius:
self.radius_ratio = maximum_radius/shortest_sidelength
self.radius_ratio = self.round_radius_handler.getRadiusRatio(min(self.size))
poeschlr marked this conversation as resolved.
Show resolved Hide resolved

if self.radius_ratio == 0:
self.shape = Pad.SHAPE_RECT
Expand Down Expand Up @@ -305,4 +411,4 @@ def getRoundRadius(self):
if r > r_max:
r_max = r
return r_max
return self.radius_ratio*min(self.size)
return self.round_radius_handler.getRoundRadius(min(self.size))
41 changes: 27 additions & 14 deletions KicadModTree/nodes/specialized/ChamferedPad.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from KicadModTree.util.paramUtil import *
from KicadModTree.Vector import *
from KicadModTree.nodes.base.Polygon import *
from KicadModTree.nodes.base.Pad import Pad
from KicadModTree.nodes.base.Pad import Pad, RoundRadiusHandler
from math import sqrt


Expand Down Expand Up @@ -212,11 +212,18 @@ class ChamferedPad(Node):
mirror y direction around offset "point"

* *radius_ratio* (``float``) --
The radius ratio used if the pad has no chamfer
Default: 0 means pads do not included rounded corners (normal rectangles are used)
The radius ratio of the rounded rectangle.
(default 0 for backwards compatibility)
* *maximum_radius* (``float``) --
Only used if a radius ratio is given.
Limits the radius.
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):
Expand All @@ -225,8 +232,7 @@ def __init__(self, **kwargs):
self._initSize(**kwargs)
self._initMirror(**kwargs)
self._initPadSettings(**kwargs)
self.radius_ratio = kwargs.get('radius_ratio', 0)
self.maximum_radius = kwargs.get('maximum_radius')

self.pad = self._generatePad()

def _initSize(self, **kwargs):
Expand Down Expand Up @@ -258,10 +264,17 @@ def _initPadSettings(self, **kwargs):
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('shape', None)
self.padargs.pop('at', None)
self.padargs.pop('round_radius_handler', None)

def _generatePad(self):
if self.chamfer_size[0] >= self.size[0] or self.chamfer_size[1] >= self.size[1]:
Expand All @@ -271,10 +284,7 @@ def _generatePad(self):
if self.corner_selection.isAnySelected() and self.chamfer_size[0] > 0 and self.chamfer_size[1] > 0:
is_chamfered = True

shortest_sidlength = min(self.size)
radius = shortest_sidlength*self.radius_ratio
if self.maximum_radius and radius > self.maximum_radius:
radius = self.maximum_radius
radius = self.round_radius_handler.getRoundRadius(min(self.size))

if is_chamfered:
outside = Vector2D(self.size.x/2, self.size.y/2)
Expand All @@ -284,9 +294,12 @@ def _generatePad(self):
]

polygon_width = 0
if self.radius_ratio > 0:
if self.round_radius_handler.roundingRequested():
if self.chamfer_size[0] != self.chamfer_size[1]:
raise NotImplementedError('rounded chamfered pads are only supported for 45 degree chamfers')
raise NotImplementedError(
'Rounded chamfered pads are only supported for 45 degree chamfers.'
' Chamfer {}'.format(self.chamfer_size)
)
# We prefer the use of rounded rectangle over chamfered pads.
r_chamfer = self.chamfer_size[0] + sqrt(2)*self.chamfer_size[0]/2
if radius >= r_chamfer:
Expand Down Expand Up @@ -326,7 +339,7 @@ def _generatePad(self):
else:
return Pad(
at=self.at, shape=Pad.SHAPE_ROUNDRECT, size=self.size,
**self.padargs
round_radius_handler=self.round_radius_handler, **self.padargs
)

def chamferAvoidCircle(self, center, diameter, clearance=0):
Expand Down
23 changes: 19 additions & 4 deletions KicadModTree/nodes/specialized/ChamferedPadGrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,18 @@ class ChamferedPadGrid(Node):
mirror y direction around offset "point"

* *radius_ratio* (``float``) --
The radius ratio used if the pad has no chamfer
Default: 0 means pads do not included rounded corners (normal rectangles are used)
The radius ratio of the rounded rectangle.
(default 0 for backwards compatibility)
* *maximum_radius* (``float``) --
Only used if a radius ratio is given.
Limits the radius.
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):
Expand Down Expand Up @@ -230,11 +237,18 @@ def _initPadSettings(self, **kwargs):
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.
Expand Down Expand Up @@ -332,6 +346,7 @@ def _generatePads(self):
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
Expand Down
Loading