Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Keepout Generators for SMT Components #177

Merged
merged 7 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
6 changes: 5 additions & 1 deletion src/ensure.stanza
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,8 @@ public defn ensure-in-set! (accepted:Tuple<Int>):
):
if not contains?(accepted, value):
throw $ ValueError("Value '%_' not in Accepted Value Set: [%,]" % [value, accepted])
ensure-func
ensure-func

public defn ensure-not-empty! (field:String, value:Collection) :
if count(value) == 0:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the argument field:String do here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the interface for the ensure => FUNC members of a defstruct field. I believe it gets used by the defstruct macro to tell the user which field of the defstruct had this fault case.

throw $ ValueError("Expected Non-Empty Collection - Found: %_" % [value])
62 changes: 62 additions & 0 deletions src/landpatterns/VirtualLP.stanza
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defpackage jsl/landpatterns/VirtualLP:
import jsl/design/Classable
import jsl/geometry/box
import jsl/landpatterns/courtyard
import jsl/landpatterns/leads
import jsl/landpatterns/thermal-pads

doc: \<DOC>
Expand Down Expand Up @@ -848,6 +849,67 @@ public defn get-first-pad (vp:VirtualLP) -> VirtualPad :
throw(Exception("Virtual landpattern has no pads"))
reduce(earlier-pad, pads)

doc: \<DOC>
Construct the interior bounds for the pads of a component

This function only makes sense for things like QFPs, SOICs,
SSOPs, or 2-pin components.

This function assumes that you have used the function
{@link build-vpad-classes} when constructing the rows and
columns of pads for your footprint.

This function attempts to extract out the pads by row
or column and then use the bounding box of the soldermask
to determine the interior bounds between the pads.

Note - there is another way to do this that I decided against
which was to try and find lines and intersections of those lines.
This seemed like it might be a bit more robust but at the cost
of being more complex code wise.

@throws ValueError if it encounter a number of columns that it can't
handle. Specifically, this function can handle [1, 2, 4] columns of pads.
This corresponds to 2-pin, dual-row, and quad land patterns, respectively.
<DOC>
public defn pad-interior-bounds (vp:VirtualLP, side:Side) -> Box:

val get-bounds = bounds{_, layer-spec = SolderMask(side)}
val cols = identify-pad-columns(vp)
val num-cols = length(cols)
if num-cols == 1:
; This is a 2-pin component - ie a SMT, Radial or Axial
val row0 = get-bounds $ get-pads-by-row(vp, 0)
val row1 = get-bounds $ get-pads-by-row(vp, 1)

Box(
Point(left(row1), up(row1))
Point(right(row0), down(row0))
)

else if num-cols == 2 :
val col0 = get-bounds $ get-pads-by-column(vp, 0)
val col1 = get-bounds $ get-pads-by-column(vp, 1)

Box(
Point(right(col0), down(col0))
Point(left(col1), up(col1))
)
else if num-cols == 4 :

val col-W = get-bounds $ get-pads-by-column(vp, 0)
val col-S = get-bounds $ get-pads-by-column(vp, 1)
val col-E = get-bounds $ get-pads-by-column(vp, 2)
val col-N = get-bounds $ get-pads-by-column(vp, 3)

Box(
Point(right(col-W), up(col-S)),
Point(left(col-E), down(col-N))
)
else:
throw $ ValueError("Unhandled Number of Columns '%_' - Expected 1, 2, or 4" % [num-cols])


; Converters
#for (vType in [VirtualPad, VirtualArtwork, VirtualCopper, VirtualLP]
funcName in [as-VirtualPad, as-VirtualArtwork, as-VirtualCopper ,as-VirtualLP]) :
Expand Down
1 change: 1 addition & 0 deletions src/landpatterns/framework.stanza
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ defpackage jsl/landpatterns/framework:
forward jsl/landpatterns/helpers
forward jsl/landpatterns/silkscreen
forward jsl/landpatterns/courtyard
forward jsl/landpatterns/keep-outs
forward jsl/landpatterns/introspection
forward jsl/landpatterns/pad-grid
forward jsl/landpatterns/thermal-pads
92 changes: 92 additions & 0 deletions src/landpatterns/keep-outs.stanza
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#use-added-syntax(jitx)
defpackage jsl/landpatterns/keep-outs:
import core
import jitx
import jitx/commands

import jsl/ensure
import jsl/geometry/box
import jsl/landpatterns/VirtualLP
import jsl/landpatterns/silkscreen

doc: \<DOC>
Interface Type for Keepout Creation

Custom keepout generators can be built by inheriting
from this type and then implementing the interface
<DOC>
public deftype KeepoutCreator

doc: \<DOC>
Build the keep-out layer geometry in the Virtual LP Scene Graph

This function will generate the necessary keep-out artwork
in the virtual LP scene graph.
<DOC>
public defmulti build-keep-out (kc:KeepoutCreator, vp:VirtualLP -- side:Side = Top) -> False

doc: \<DOC>
Keepout Creator for Intra-pad Keepouts

For SMT chip components like resistors and capacitors,
we often want to restrict copper underneath the component.
This can be for SI reasons, manufacturing reasons, etc.

Sometimes we want to restrict the ground plane on an internal
layer underneath these components.

This type is used to contruct these keepouts on any layer
of the board design.
<DOC>
public defstruct IntraPadKeepOut <: KeepoutCreator :
doc: \<DOC>
Set of copper layers where the keepout will be placed.
The most obvious layer would be the top layer underneath
the component. But for some applications, we might want
to add keepouts in the reference plane underneath the
component as well.

The default value is the top layer `LayerIndex(0)`
<DOC>
layer-set:Tuple<LayerIndex> with:
ensure => ensure-not-empty!
default => [LayerIndex(0)]

doc: \<DOC>
Shrink the created keepout
By default, this type will create a keepout that is
the same size as the interstitual region between the
pads of the SMT component.
This parameter can be used to make this keepout region
smaller (or larger if negative).
By default this value is 0.0.
<DOC>
shrink-by:Double|Percentage|Dims with:
default => 0.0
with:
printer => true
keyword-constructor => true


public defmethod build-keep-out (k:IntraPadKeepOut, vp:VirtualLP -- side:Side = Top) :

val b = pad-interior-bounds(vp, side)
val sh-by = match(shrink-by(k)):
(s:Double): Point(s, s)
(s:Dims): Point(x(s), y(s))
(p:Percentage):
val s = dims(b)
Point(x(s) * p, y(s) * p)

val b* = shrink(sh-by, b)
val sh = to-Rectangle(b*)

val cls = ["keepout"]
for ly in layer-set(k) do:
val fb = ForbidCopper(ly)
add-artwork(vp, fb, sh, class = cls)





65 changes: 4 additions & 61 deletions src/landpatterns/silkscreen.stanza
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ defpackage jsl/landpatterns/silkscreen:
import jsl/errors
import jsl/geometry/box
import jsl/geometry/LineRectangle
import jsl/landpatterns/framework
import jsl/landpatterns/VirtualLP
import jsl/landpatterns/helpers
import jsl/landpatterns/leads
import jsl/landpatterns/package-body

public defn default-silk-width () -> Double :
clearance(current-rules(), MinSilkscreenWidth)
Expand Down Expand Up @@ -211,66 +214,6 @@ with:
printer => true
keyword-constructor => true

doc: \<DOC>
Construct the interior bounds for the pads of a component

This function only makes sense for things like QFPs, SOICs,
SSOPs, or 2-pin components.

This function assumes that you have used the function
{@link build-vpad-classes} when constructing the rows and
columns of pads for your footprint.

This function attempts to extract out the pads by row
or column and then use the bounding box of the soldermask
to determine the interior bounds between the pads.

Note - there is another way to do this that I decided against
which was to try and find lines and intersections of those lines.
This seemed like it might be a bit more robust but at the cost
of being more complex code wise.

@throws ValueError if it encounter a number of columns that it can't
handle. Specifically, this function can handle [1, 2, 4] columns of pads.
This corresponds to 2-pin, dual-row, and quad land patterns, respectively.
<DOC>
defn pad-interior-bounds (vp:VirtualLP, side:Side) -> Box:

val get-bounds = bounds{_, layer-spec = SolderMask(side)}
val cols = identify-pad-columns(vp)
val num-cols = length(cols)
if num-cols == 1:
; This is a 2-pin component - ie a SMT, Radial or Axial
val row0 = get-bounds $ get-pads-by-row(vp, 0)
val row1 = get-bounds $ get-pads-by-row(vp, 1)

Box(
Point(left(row1), up(row1))
Point(right(row0), down(row0))
)

else if num-cols == 2 :
val col0 = get-bounds $ get-pads-by-column(vp, 0)
val col1 = get-bounds $ get-pads-by-column(vp, 1)

Box(
Point(right(col0), down(col0))
Point(left(col1), up(col1))
)
else if num-cols == 4 :

val col-W = get-bounds $ get-pads-by-column(vp, 0)
val col-S = get-bounds $ get-pads-by-column(vp, 1)
val col-E = get-bounds $ get-pads-by-column(vp, 2)
val col-N = get-bounds $ get-pads-by-column(vp, 3)

Box(
Point(right(col-W), up(col-S)),
Point(left(col-E), down(col-N))
)
else:
throw $ ValueError("Unhandled Number of Columns '%_' - Expected 1, 2, or 4" % [num-cols])


public defmethod build-shape (s:InterstitialOutline, vp:VirtualLP -- side:Side = Top) -> Shape :
; Package bounding box
Expand Down
Loading
Loading