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

Updates to the non-plated hole and support for changes to mechanical/MountingHoles #209

Merged
merged 12 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
177 changes: 177 additions & 0 deletions src/geometry/basics.stanza
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#use-added-syntax(jitx)
defpackage jsl/geometry/basics:
import core
import collections
import math
import jitx
import jitx/commands
Expand Down Expand Up @@ -47,6 +48,42 @@ public defn GeneralChamferedRectangle (size:Dims, r:Tuple<Double> -- anchor:Anch
; public defn Circle (radius:Double -- anchor:Anchor = C):
; Circle(anchor, radius)

doc: \<DOC>
Break a `Line` into a sequence of `Segment`

A `Line` can have multiple points. A Segment has
only a start and end. This deconstructs a line into
multiple segments.
<DOC>
defn to-segments (l:Line) -> Seq<Segment>:
val pts = points(l)
for (pt in pts, i in 0 to false) seq?:
if i < (length(pts) - 1):
One $ Segment(pts[i], pts[i + 1])
else:
None()

defn to-capsule (s:Segment, width:Double, radius:Double) -> Capsule:
if radius < 0.0:
throw $ ArgumentError("Invalid Radius for new Capsule - radius=%_ > 0" % [radius])

val L = length(s)
val ang = angle-degrees(s)
val diam = (2.0 * radius)
; Create the raw capsule as if it starts at 0,0 and goes along
; the X axis.
val raw-cap = Capsule(Dims(L + diam, width + diam), anchor = W)
; The rotate it and translate it to place it where
; the originating segment was.
loc(source(s)) * loc(0.0, 0.0, ang) * raw-cap


defn expand-line (x:Line, amount:Double) -> Shape:
Union $ to-tuple $ for seg in to-segments(x) seq:
to-capsule(seg, width(x), amount)

defn expand-polyline (x:Polyline, amount:Double) -> Shape :
Polyline(width(x) + (2.0 * amount), elements(x))

doc: \<DOC>
Expand for Union shapes
Expand All @@ -59,9 +96,149 @@ public defn expand-union (x:Shape|Union, amount:Double) -> Shape:
(U:Union):
Union $ for obj in shapes(U) seq:
expand-union(obj, amount)
(l:Line): expand-line(l, amount)
(pl:Polyline): expand-polyline(pl, amount)
(non-handled:Polygon|PolygonWithArcs):
println("Polygons are currently not handled with `expand` - No Expansion on this will be provided")
non-handled
(y:Shape):
expand-or-shrink(y, amount)

public defn to-polygon (x:Shape -- top-level?:True|False, num-points:Int = 32) -> Polygon|Union :
match(x):
(U:Union):
; I have to use the hidden constructor here because
; the `Union` function that overrides the constructor returns `Shape` instead of Union
#Union $ to-tuple $ for obj in shapes(U) seq:
to-polygon(obj, top-level? = false, num-points = num-points)
(c:Circle|Arc|PolygonWithArcs|DShape|GeneralRoundedRectangle|RoundedRectangle|Capsule): to-polygon(c, num-points) as Polygon
(p:Polyline|GeneralChamferedRectangle|Rectangle): to-polygon(p) as Polygon
(p:Polygon): p

defn to-points (poly:Polygon|Union) -> Tuple<Point>:
match(poly):
(u:Union):
to-tuple $ for sh in shapes(u) seq-cat:
to-points(sh as Polygon|Union)
(p:Polygon): points(p)


doc: \<DOC>
Compute the convex hull of a non-trivial polygon case.

This function uses Jarvis's Gift wrapping algorithm. It is a
brute-force algorithm that is not optimized for performance.

@see https://en.wikipedia.org/wiki/Gift_wrapping_algorithm

@param sh Non-trivial shape like `Polyline`, `Arc`, `PolygonWithArcs`, `Polygon`,
and `Union` shape cases. Other cases are handled with `to-polygon`
@param num-points When Approximating shapes like Arc, PolygonWithArcs, etc, we need to
use a number of points to create a polygon. This sets that polygonization amount.

@throws ValueError if the shapes passed are degenerate - ie less than 3 points.
<DOC>
defn non-trivial-convex-hull (sh:Shape, num-points:Int = 32) -> Polygon:
val pt-set = to-vector<Point> $ to-points $ match(sh):
(p:Polyline): to-polygon(p)
(a:Arc|PolygonWithArcs): to-polygon(a, num-points)
(u:Union):
to-polygon(u, top-level? = true, num-points = num-points)
(p:Polygon): p

if length(pt-set) == 0:
throw $ ValueError("No Points in Polygons to create Convex Hull")

; TODO - we should handle the 3 colinear point case
; here.

qsort!(pt-set, {x(_0) < x(_1)})
; The farthest left point is guaranteed to be part of the
; convex hull.
val far-left = pt-set[0]

val hull-pts = to-vector<Point>([far-left])

; This function determines the state of q with respect to r
; in a polar coordinate frame, with an origin a P.
; This polar coordinate frame is "right-handed".
; If `q` is more counter-clockwise than `r` - this function returns 2
; If `q` is more clockwise than `r` - this function returns 1
; If `p`, `q` and `r are colinear - then this function returns 0
defn orientation (p:Point, q:Point, r:Point) -> Int:
val ret = (y(q) - y(p)) * (x(r) - x(q)) -
(x(q) - x(p)) * (y(r) - y(q))
if ret == 0.0:
0
else if ret > 0.0:
1 ; clock wise
else:
2 ; counterclockwise

var done = false
var i = 0
while not done:
; println("Iteration: %_" % [i])
; The inner loop compares all of the points to the point on the hull
; and finds the one to the farthest left. We keep going around until we
; come back to the beginning

val pointOnHull = peek(hull-pts)
; println("Origin: %_" % [pointOnHull])

defn inner-loop (endpoint:Point, s:Point) -> Point:
val is-clockwise = orientation(pointOnHull, s, endpoint) == 2
if endpoint == pointOnHull or is-clockwise:
; println("Updated Endpoint: %_" % [s])
s
else:
endpoint

val newPt = reduce(inner-loop, pt-set[0], pt-set)
add(hull-pts, newPt)
; println("New Pt: %_" % [newPt])
i = i + 1

done = newPt == hull-pts[0]
; This is a hedge in case the algorithm has a bug
; I want to prevent getting trapped in an infinite
; loop. The typical case should not encounter this.
if not (i < (2 * length(pt-set))):
; Break if we have been through all the points.
done = true

Polygon(to-tuple(hull-pts))




doc: \<DOC>
Compute the Convex Hull of the passed Shape

This function uses Jarvis's Gift wrapping algorithm for
non-trivial polygon shapes. It is a brute-force algorithm
that is not optimized for performance.

@see https://en.wikipedia.org/wiki/Gift_wrapping_algorithm


@param x Shape object to construct the convex hull of
@param num-points Some objects need to be polygonized to
be able to run the convex hull algorithm. This parameter determines
how many points will be used for each shape. Alternatively, the
user can pass in pre-polygonized shapes and this value will be ignored.
<DOC>
public defn convex-hull (x:Shape -- num-points:Int = 32) -> Polygon:
match(x):
; Some shapes are already their own convex hull - so we just convert them
; to a polygon
(c:Circle|DShape|GeneralRoundedRectangle|RoundedRectangle|Capsule): to-polygon(c, num-points)
(p:GeneralChamferedRectangle|Rectangle): to-polygon(p)
(x): non-trivial-convex-hull(x, num-points)




doc: \<DOC>
Increase the size of a `Dims` by `amount` on all sides

Expand Down
4 changes: 2 additions & 2 deletions src/landpatterns/SOIC.stanza
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public defn make-SOIC-narrow-body (
width:Toleranced = min-max(3.8, 4.0),
height:Toleranced = min-max(1.35, 1.75),
) -> PackageBody :
#PackageBody(width, length, height)
PackageBody(width = width, length = length, height = height)

doc: \<DOC>
Create a Wide SOIC PackageBody
Expand All @@ -108,7 +108,7 @@ public defn make-SOIC-wide-body (
width:Toleranced = min-max(7.4, 7.6),
height:Toleranced = min-max(2.35, 2.65)
) -> PackageBody :
#PackageBody(width, length, height)
PackageBody(width = width, length = length, height = height)

doc: \<DOC>
SOIC Standard Package Type
Expand Down
55 changes: 55 additions & 0 deletions src/landpatterns/VirtualLP.stanza
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,30 @@ public defn get-layers (vp:VirtualLP, offset:Pose = loc(0.0, 0.0)) -> Tuple<Laye
get-layers(child, offset * pose(child))
to-tuple $ cat(local-art, kid-art)

doc: \<DOC>
Retrieve Geometry for all including pads

This function recursively finds all of the shapes on all artwork and pads
in the scene graph. It then returns them as `LayerShape` objects.
@param vp Virtual LP scene graph
@param offset Offset position to apply to the created LayerShape objects.
This will cause all created objects to be in the same root frame of reference
for the VirtualLP `vp`.
@return Tuple of LayerShape objects. The child layer content will be transformed
to be in the parent's frame of reference.
<DOC>
public defn get-layers-all (vp:VirtualLP, offset:Pose = loc(0.0, 0.0)) -> Tuple<LayerShape> :
val local-art = for va in artwork(vp) seq:
to-layer-shape(va, offset)
val local-lands = for land in lands(vp) seq-cat:
get-layers(land)
val kid-shapes = for child in children(vp) seq-cat:
val shs = get-layers-all(child)
for sh in shs seq:
LayerShape(specifier(sh), pose(child) * shape(sh))
to-tuple $ for shList in [local-art, local-lands, kid-shapes] seq-cat:
shList

public defn get-copper (vp:VirtualLP, li:LayerIndex) -> Tuple<VirtualCopper> :
to-tuple $ for m in metal(vp) filter:
layer-index(m) == li
Expand Down Expand Up @@ -538,6 +562,37 @@ public defn add-thermal-pad (
val pd-def = smd-thermal-pad(tp)
append(vp, VirtualPad(pad-id, pd-def, pose, class = cls))

doc: \<DOC>
Add a non-plated hole

Non-plated holes can't be pads so we need special
handling.

@param vp Virtual Landpattern Scene Graph
@param hole Hole shape for the non-plated hole. Doesn't have to be
a Circle/Capsule - could be any rout-able shape.
@param mask Soldermask opening. This can be either a `Shape` or
a expansion amount. If a `Shape`, then we use it directly.
If this value is a `Double`, then we use this to expand around
the `hole` by this many mm. Default is based on the `SolderMaskRegistration` in
the current rule set.
@param pose Location for the cutout in the `vp` node. Default is `loc(0.0, 0.0)`
<DOC>
public defn add-non-plated-hole (
vp:VirtualLP
--
hole:Shape,
mask:Shape|Double = clearance(current-rules(), SolderMaskRegistration)
pose:Pose = loc(0.0, 0.0)
) :
val mask-sh = match(mask):
(d:Double): expand(hole, d)
(sh:Shape): sh

add-cutout(vp, pose * hole)
add-artwork(vp, SolderMask(Top), pose * mask-sh)
add-artwork(vp, SolderMask(Bottom), pose * mask-sh)


doc: \<DOC>
Create a reference designator in the current virtual landpattern
Expand Down
8 changes: 4 additions & 4 deletions src/landpatterns/courtyard.stanza
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ public val DEF_COURTYARD_LEN_RATIO = 0.5
; on a separate layer.
public val DEF_COURTYARD_ORIGIN_LAYER = CustomLayer("origin", Top)

defn compute-origin-size (outline:Rectangle) -> Double :
defn compute-origin-size (outline:Shape) -> Double :
; We look at the outline and use this to determine the
; origin marker's size. We don't want the marker going
; outside the boundary. Otherwise, this will throw off
; other calculations we make with the boundary.
val max-dim = min(width(outline), height(outline))
val max-2 = max-dim * DEF_COURTYARD_LEN_RATIO
val d = dims(outline)
val max-dim = min(x(d), y(d)) val max-2 = max-dim * DEF_COURTYARD_LEN_RATIO
min(max-2, DEF_COURTYARD_LINE_LEN)

public defn build-courtyard-origin (
vp:VirtualLP,
outline:Rectangle,
outline:Shape,
--
line-width:Double = DEF_COURTYARD_LINE_WIDTH
pose:Pose = loc(0.0, 0.0)
Expand Down
11 changes: 11 additions & 0 deletions src/landpatterns/numbering.stanza
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ Convert the row and column indices into a pad number or reference
<DOC>
public defmulti to-pad-id (x:Numbering, row:Int, column:Int) -> Int|Ref

doc: \<DOC>
Null Numbering Object

This is a default object for creating packages that don't require
any pad numbering. These landpatterns will likely not have any
pads to number.
<DOC>
public defstruct NullNumbering <: Numbering
defmethod to-pad-id (n:NullNumbering, row:Int, column:Int) -> Int|Ref:
throw $ ValueError("Null Numbering Cannot Create Pad IDs")

; Default for Std IC Numbering
val DUAL-ROW-COLS = 2

Expand Down
Loading
Loading