Solder cone around protruding THT leads (tent structure)
Build PCM package / build (push) Successful in 5s
Build PCM package / build (push) Successful in 5s
The clipped lead of a soldered THT contact protrudes THT_LEAD_PROTRUSION_MM (1.5 mm default, 0 disables) out of the hole on the side opposite the component, and a solder cone wraps it: full protrusion height at the drill wall, tapering linearly to zero at the pad edge. Painted as per-cell extra conduction-equivalent copper via stack.thick_scale - the tall solder column at the wall pulls the joint vicinity to lead potential (equivalent to extending the barrel wall vertically), the taper carries the radial spreading. DC-exact additive conductance; at f > 0 the factor multiplies the skin-corrected sheet conductance like the via mouths (documented approximation). The protrusion side is looked up from the owning footprint (pads store absolute positions; component on F.Cu -> lead tents on B.Cu), with a logged B.Cu fallback. Dump schema gains protrusion_side and tht_protrusion_nm (defaults keep older v6 dumps loading). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -125,9 +125,17 @@ SWIG API. Requires KiCad **10.0.1+**.
|
|||||||
soldered **THT joint** additionally assumes the **hole is filled with
|
soldered **THT joint** additionally assumes the **hole is filled with
|
||||||
solder** (core in parallel with the plating) and the **pad face
|
solder** (core in parallel with the plating) and the **pad face
|
||||||
carries an average-thickness solder coat** (`SOLDER_THICKNESS_UM`,
|
carries an average-thickness solder coat** (`SOLDER_THICKNESS_UM`,
|
||||||
50 µm) over the modeled copper under the pad shape. To model a probe
|
50 µm) over the modeled copper under the pad shape. The **clipped
|
||||||
pressed onto the pad face instead, draw a marker rectangle over the
|
lead protrudes** `THT_LEAD_PROTRUSION_MM` (1.5 mm, 0 = off) on the
|
||||||
pad.
|
side opposite the component (taken from the owning footprint;
|
||||||
|
assumed `B.Cu` if it cannot be found) and a **solder cone** wraps
|
||||||
|
it: full protrusion height at the drill wall, tapering linearly to
|
||||||
|
zero at the pad edge, applied as extra conduction-equivalent copper
|
||||||
|
per cell. The tall solder column at the wall pulls the joint
|
||||||
|
vicinity to lead potential — equivalent to extending the barrel wall
|
||||||
|
vertically — while the taper carries the radial spreading. To model
|
||||||
|
a probe pressed onto the pad face instead, draw a marker rectangle
|
||||||
|
over the pad.
|
||||||
- **Contact models** (dialog / `CONTACT_MODEL`): default **uniform
|
- **Contact models** (dialog / `CONTACT_MODEL`): default **uniform
|
||||||
injection** — a conductor pressed on top feeds the current orthogonally
|
injection** — a conductor pressed on top feeds the current orthogonally
|
||||||
with uniform surface density, so |J| ramps across the contact area
|
with uniform surface density, so |J| ramps across the contact area
|
||||||
|
|||||||
@@ -180,8 +180,29 @@ def _pad_polygons(board: Board, pad: Pad, contact: str) -> list[Polygon] | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _to_electrode(board: Board, item,
|
def _tht_protrusion_side(pad: Pad, footprints) -> str:
|
||||||
stackup: StackupInfo | None = None) -> Electrode:
|
"""Outer layer where the clipped THT lead protrudes (tent + solder
|
||||||
|
cone): the side OPPOSITE the component. Footprint pads are stored
|
||||||
|
with absolute positions, so the owning footprint is matched by pad
|
||||||
|
number + position. Unknown owner -> assume the component sits on
|
||||||
|
F.Cu (lead tents on B.Cu)."""
|
||||||
|
try:
|
||||||
|
for fp in footprints or []:
|
||||||
|
for fpad in fp.definition.pads:
|
||||||
|
if fpad.number == pad.number \
|
||||||
|
and fpad.position.x == pad.position.x \
|
||||||
|
and fpad.position.y == pad.position.y:
|
||||||
|
side = canonical_name(fp.layer)
|
||||||
|
return "F.Cu" if side == "B.Cu" else "B.Cu"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
print(f"note: no footprint found for pad {pad.number} - assuming its "
|
||||||
|
f"lead protrudes on B.Cu")
|
||||||
|
return "B.Cu"
|
||||||
|
|
||||||
|
|
||||||
|
def _to_electrode(board: Board, item, stackup: StackupInfo | None = None,
|
||||||
|
footprints=None) -> Electrode:
|
||||||
if isinstance(item, BoardRectangle):
|
if isinstance(item, BoardRectangle):
|
||||||
tl, br = item.top_left, item.bottom_right
|
tl, br = item.top_left, item.bottom_right
|
||||||
rect = Rect.normalized(tl.x, tl.y, br.x, br.y,
|
rect = Rect.normalized(tl.x, tl.y, br.x, br.y,
|
||||||
@@ -219,10 +240,13 @@ def _to_electrode(board: Board, item,
|
|||||||
return Electrode(rect=rect, contact=contact,
|
return Electrode(rect=rect, contact=contact,
|
||||||
polygons=_pad_polygons(board, pad, contact), label=label,
|
polygons=_pad_polygons(board, pad, contact), label=label,
|
||||||
# through-hole pad: current enters at the soldered
|
# through-hole pad: current enters at the soldered
|
||||||
# barrel; the joint is solder-filled + pad-coated
|
# barrel; the joint is solder-filled + pad-coated,
|
||||||
|
# with a solder cone around the protruding lead
|
||||||
drill_nm=drill, pad_nm=_padstack_pad_nm(pad),
|
drill_nm=drill, pad_nm=_padstack_pad_nm(pad),
|
||||||
center=(pad.position.x, pad.position.y),
|
center=(pad.position.x, pad.position.y),
|
||||||
solder=drill > 0)
|
solder=drill > 0,
|
||||||
|
protrusion_side=(_tht_protrusion_side(pad, footprints)
|
||||||
|
if drill > 0 else None))
|
||||||
|
|
||||||
|
|
||||||
def _net_hint_of(items: list) -> str | None:
|
def _net_hint_of(items: list) -> str | None:
|
||||||
@@ -257,6 +281,10 @@ def get_electrodes(board: Board, stackup: StackupInfo | None = None
|
|||||||
selection = list(board.get_selection())
|
selection = list(board.get_selection())
|
||||||
rects = [s for s in selection if isinstance(s, BoardRectangle)]
|
rects = [s for s in selection if isinstance(s, BoardRectangle)]
|
||||||
pads = [s for s in selection if isinstance(s, (Pad, Via))]
|
pads = [s for s in selection if isinstance(s, (Pad, Via))]
|
||||||
|
# protrusion-side lookup needs the owning footprints (THT pads only)
|
||||||
|
footprints = (board.get_footprints()
|
||||||
|
if any(isinstance(s, Pad) and _pad_drill_nm(s) > 0
|
||||||
|
for s in pads) else None)
|
||||||
|
|
||||||
if not selection:
|
if not selection:
|
||||||
allr = [s for s in board.get_shapes() if isinstance(s, BoardRectangle)]
|
allr = [s for s in board.get_shapes() if isinstance(s, BoardRectangle)]
|
||||||
@@ -292,7 +320,8 @@ def get_electrodes(board: Board, stackup: StackupInfo | None = None
|
|||||||
f"only for a side that has none."
|
f"only for a side that has none."
|
||||||
)
|
)
|
||||||
if pads:
|
if pads:
|
||||||
pad_parts = [_to_electrode(board, p, stackup) for p in pads]
|
pad_parts = [_to_electrode(board, p, stackup, footprints)
|
||||||
|
for p in pads]
|
||||||
if not es1:
|
if not es1:
|
||||||
es1 = pad_parts
|
es1 = pad_parts
|
||||||
else:
|
else:
|
||||||
@@ -306,8 +335,9 @@ def get_electrodes(board: Board, stackup: StackupInfo | None = None
|
|||||||
|
|
||||||
items = rects + pads
|
items = rects + pads
|
||||||
if len(items) == 2:
|
if len(items) == 2:
|
||||||
return ([_to_electrode(board, items[0], stackup)],
|
return ([_to_electrode(board, items[0], stackup, footprints)],
|
||||||
[_to_electrode(board, items[1], stackup)], _net_hint_of(pads))
|
[_to_electrode(board, items[1], stackup, footprints)],
|
||||||
|
_net_hint_of(pads))
|
||||||
raise SelectionError(
|
raise SelectionError(
|
||||||
f"The selection has {len(rects)} rectangle(s) (none on the marker "
|
f"The selection has {len(rects)} rectangle(s) (none on the marker "
|
||||||
f"layers) and {len(pads)} pad(s)/via(s); without marker layers "
|
f"layers) and {len(pads)} pad(s)/via(s); without marker layers "
|
||||||
@@ -554,12 +584,19 @@ def build_problem(board: Board, net: str, layer_names: list[str],
|
|||||||
cap_plating_nm=int(config.CAP_PLATING_UM * 1000),
|
cap_plating_nm=int(config.CAP_PLATING_UM * 1000),
|
||||||
cap_max_drill_nm=int((cap_max_drill_mm if cap_max_drill_mm is not None
|
cap_max_drill_nm=int((cap_max_drill_mm if cap_max_drill_mm is not None
|
||||||
else config.CAP_MAX_DRILL_MM) * 1e6),
|
else config.CAP_MAX_DRILL_MM) * 1e6),
|
||||||
|
tht_protrusion_nm=int(config.THT_LEAD_PROTRUSION_MM * 1e6),
|
||||||
)
|
)
|
||||||
solder_layers = contact_solder_buildups(problem)
|
solder_layers = contact_solder_buildups(problem)
|
||||||
if solder_layers:
|
if solder_layers:
|
||||||
|
sides = sorted({e.protrusion_side
|
||||||
|
for e in problem.electrodes1 + problem.electrodes2
|
||||||
|
if e.solder and e.protrusion_side})
|
||||||
|
cone = (f", {config.THT_LEAD_PROTRUSION_MM:g} mm lead + solder cone "
|
||||||
|
f"on {', '.join(sides)}"
|
||||||
|
if sides and problem.tht_protrusion_nm > 0 else "")
|
||||||
print(f"THT contact(s): solder-filled hole + "
|
print(f"THT contact(s): solder-filled hole + "
|
||||||
f"{config.SOLDER_THICKNESS_UM:g} um average solder coat on the "
|
f"{config.SOLDER_THICKNESS_UM:g} um average solder coat on the "
|
||||||
f"pad face ({', '.join(solder_layers)})")
|
f"pad face ({', '.join(solder_layers)}){cone}")
|
||||||
return problem
|
return problem
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ INCLUDE_TH_PADS = True # plated through-hole pads stitch layers too;
|
|||||||
# their holes are modeled solder-filled (a
|
# their holes are modeled solder-filled (a
|
||||||
# soldered component lead), so the solder core
|
# soldered component lead), so the solder core
|
||||||
# conducts in parallel with the plating
|
# conducts in parallel with the plating
|
||||||
|
THT_LEAD_PROTRUSION_MM = 1.5 # clipped THT lead protrusion on the side
|
||||||
|
# opposite the component: a solder cone of
|
||||||
|
# this height at the drill wall (tapering to
|
||||||
|
# zero at the pad edge) wraps the lead of
|
||||||
|
# every soldered THT CONTACT. 0 = no cones
|
||||||
SKIN_SIDES = 1 # skin-effect field config: 1 = plane facing a
|
SKIN_SIDES = 1 # skin-effect field config: 1 = plane facing a
|
||||||
# return plane (conservative), 2 = isolated foil
|
# return plane (conservative), 2 = isolated foil
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,11 @@ class Electrode:
|
|||||||
center: tuple[int, int] | None = None # drill center; None = rect center
|
center: tuple[int, int] | None = None # drill center; None = rect center
|
||||||
barrel_z: tuple[int, int] | None = None # (z_top, z_bot); None = full stack
|
barrel_z: tuple[int, int] | None = None # (z_top, z_bot); None = full stack
|
||||||
solder: bool = False # soldered THT joint (see above)
|
solder: bool = False # soldered THT joint (see above)
|
||||||
|
protrusion_side: str | None = None # outer layer where the clipped
|
||||||
|
# lead protrudes (opposite the
|
||||||
|
# component): a solder cone
|
||||||
|
# wraps it there, see
|
||||||
|
# Problem.tht_protrusion_nm
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -170,6 +175,13 @@ class Problem:
|
|||||||
cap_max_drill_nm: int = 500_000 # fab caps only small vias:
|
cap_max_drill_nm: int = 500_000 # fab caps only small vias:
|
||||||
# drills above this stay open
|
# drills above this stay open
|
||||||
# even with vias_capped
|
# even with vias_capped
|
||||||
|
tht_protrusion_nm: int = 1_500_000 # clipped THT lead protrusion:
|
||||||
|
# a solder cone of this height
|
||||||
|
# at the drill wall (tapering
|
||||||
|
# to zero at the pad edge)
|
||||||
|
# wraps the lead on each solder
|
||||||
|
# contact's protrusion_side;
|
||||||
|
# 0 disables the cones
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def layer_names(self) -> list[str]:
|
def layer_names(self) -> list[str]:
|
||||||
@@ -368,6 +380,7 @@ def _electrode_to_json(e: Electrode) -> dict:
|
|||||||
"center": (None if e.center is None else list(e.center)),
|
"center": (None if e.center is None else list(e.center)),
|
||||||
"barrel_z": (None if e.barrel_z is None else list(e.barrel_z)),
|
"barrel_z": (None if e.barrel_z is None else list(e.barrel_z)),
|
||||||
"solder": e.solder,
|
"solder": e.solder,
|
||||||
|
"protrusion_side": e.protrusion_side,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -385,6 +398,7 @@ def _electrode_from_json(d: dict) -> Electrode:
|
|||||||
barrel_z=(None if d.get("barrel_z") is None
|
barrel_z=(None if d.get("barrel_z") is None
|
||||||
else (int(d["barrel_z"][0]), int(d["barrel_z"][1]))),
|
else (int(d["barrel_z"][0]), int(d["barrel_z"][1]))),
|
||||||
solder=bool(d.get("solder", False)),
|
solder=bool(d.get("solder", False)),
|
||||||
|
protrusion_side=d.get("protrusion_side"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -424,6 +438,7 @@ def problem_to_json(p: Problem) -> dict:
|
|||||||
"vias_capped": p.vias_capped,
|
"vias_capped": p.vias_capped,
|
||||||
"cap_plating_nm": p.cap_plating_nm,
|
"cap_plating_nm": p.cap_plating_nm,
|
||||||
"cap_max_drill_nm": p.cap_max_drill_nm,
|
"cap_max_drill_nm": p.cap_max_drill_nm,
|
||||||
|
"tht_protrusion_nm": p.tht_protrusion_nm,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -498,6 +513,7 @@ def problem_from_json(d: dict) -> Problem:
|
|||||||
vias_capped=bool(d.get("vias_capped", True)),
|
vias_capped=bool(d.get("vias_capped", True)),
|
||||||
cap_plating_nm=int(d.get("cap_plating_nm", 15_000)),
|
cap_plating_nm=int(d.get("cap_plating_nm", 15_000)),
|
||||||
cap_max_drill_nm=int(d.get("cap_max_drill_nm", 500_000)),
|
cap_max_drill_nm=int(d.get("cap_max_drill_nm", 500_000)),
|
||||||
|
tht_protrusion_nm=int(d.get("tht_protrusion_nm", 1_500_000)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -232,9 +232,58 @@ def rasterize_stack(problem: Problem, h_nm: float) -> RasterStack:
|
|||||||
_paint_ring(stack, hole, False, pmask)
|
_paint_ring(stack, hole, False, pmask)
|
||||||
stack.buildup[li] |= pmask
|
stack.buildup[li] |= pmask
|
||||||
stack.buildup &= stack.masks # solder wets exposed copper only
|
stack.buildup &= stack.masks # solder wets exposed copper only
|
||||||
|
|
||||||
|
_paint_lead_fillets(stack, problem)
|
||||||
return stack
|
return stack
|
||||||
|
|
||||||
|
|
||||||
|
def _paint_lead_fillets(stack: RasterStack, problem: Problem) -> None:
|
||||||
|
"""Protruding THT leads of soldered barrel contacts: the clipped
|
||||||
|
lead sticks tht_protrusion_nm out of the hole on the side opposite
|
||||||
|
the component, wrapped by a solder cone - full protrusion height at
|
||||||
|
the drill wall, tapering linearly to zero at the pad edge. Modeled
|
||||||
|
as extra conduction-equivalent copper via stack.thick_scale: the
|
||||||
|
tall solder column next to the wall pulls those cells to lead
|
||||||
|
potential (equivalent to extending the barrel wall vertically), the
|
||||||
|
taper carries the radial spreading. At f > 0 the factor multiplies
|
||||||
|
the skin-corrected sheet conductance, like the via mouths
|
||||||
|
(approximation)."""
|
||||||
|
H = problem.tht_protrusion_nm
|
||||||
|
if H <= 0:
|
||||||
|
return
|
||||||
|
ny, nx = stack.shape2d
|
||||||
|
h = stack.h_nm
|
||||||
|
index = {name: li for li, name in enumerate(stack.layer_names)}
|
||||||
|
for e in problem.electrodes1 + problem.electrodes2:
|
||||||
|
if not (e.solder and e.drill_nm > 0 and e.protrusion_side):
|
||||||
|
continue
|
||||||
|
li = index.get(e.protrusion_side)
|
||||||
|
if li is None or e.pad_nm <= e.drill_nm:
|
||||||
|
continue
|
||||||
|
if e.center is not None:
|
||||||
|
x, y = e.center
|
||||||
|
else:
|
||||||
|
x = (e.rect.x0 + e.rect.x1) / 2.0
|
||||||
|
y = (e.rect.y0 + e.rect.y1) / 2.0
|
||||||
|
ra, rb = e.drill_nm / 2.0, e.pad_nm / 2.0
|
||||||
|
j0 = max(0, math.floor((x - rb - stack.x0_nm) / h))
|
||||||
|
j1 = min(nx, math.floor((x + rb - stack.x0_nm) / h) + 1)
|
||||||
|
i0 = max(0, math.floor((y - rb - stack.y0_nm) / h))
|
||||||
|
i1 = min(ny, math.floor((y + rb - stack.y0_nm) / h) + 1)
|
||||||
|
if i0 >= i1 or j0 >= j1:
|
||||||
|
continue
|
||||||
|
xs = stack.x0_nm + (np.arange(j0, j1) + 0.5) * h - x
|
||||||
|
ys = stack.y0_nm + (np.arange(i0, i1) + 0.5) * h - y
|
||||||
|
r = np.sqrt(ys[:, None] ** 2 + xs[None, :] ** 2)
|
||||||
|
t_sn = H * np.clip((rb - r) / (rb - ra), 0.0, 1.0)
|
||||||
|
t_eq = t_sn * (problem.rho_ohm_m / problem.solder_rho_ohm_m)
|
||||||
|
factor = 1.0 + t_eq / problem.layers[li].thickness_nm
|
||||||
|
if stack.thick_scale is None:
|
||||||
|
stack.thick_scale = np.ones(stack.masks.shape)
|
||||||
|
m = stack.masks[li, i0:i1, j0:j1]
|
||||||
|
stack.thick_scale[li, i0:i1, j0:j1] *= np.where(m, factor, 1.0)
|
||||||
|
|
||||||
|
|
||||||
def _via_span(problem: Problem, via) -> list[int]:
|
def _via_span(problem: Problem, via) -> list[int]:
|
||||||
return [li for li, layer in enumerate(problem.layers)
|
return [li for li, layer in enumerate(problem.layers)
|
||||||
if via.spans(layer.z_nm)]
|
if via.spans(layer.z_nm)]
|
||||||
|
|||||||
@@ -143,16 +143,73 @@ def test_contact_solder_coat():
|
|||||||
assert r_coat.R_ohm < r_bare.R_ohm
|
assert r_coat.R_ohm < r_bare.R_ohm
|
||||||
|
|
||||||
|
|
||||||
|
def test_lead_fillet_profile():
|
||||||
|
"""The protruding-lead solder cone paints thick_scale with the exact
|
||||||
|
per-cell formula: 1 + H*clip((rb-r)/(rb-ra), 0, 1)*(rho_cu/rho_sn)/t
|
||||||
|
on copper of the protrusion side; nothing elsewhere."""
|
||||||
|
p = make_problem([(PLATE20, [])],
|
||||||
|
rect1_mm=(0, 0, 1, 20), rect2_mm=(19, 0, 20, 20))
|
||||||
|
p.electrodes1 = [_barrel(10, 10, drill_mm=1.0, pad_mm=2.4, solder=True)]
|
||||||
|
p.electrodes1[0].protrusion_side = "F.Cu"
|
||||||
|
stack = raster.rasterize_stack(p, 0.1 * NM)
|
||||||
|
assert stack.thick_scale is not None
|
||||||
|
ny, nx = stack.shape2d
|
||||||
|
jj, ii = np.meshgrid(np.arange(nx), np.arange(ny))
|
||||||
|
r = np.hypot(stack.x0_nm + (jj + 0.5) * stack.h_nm - 10 * NM,
|
||||||
|
stack.y0_nm + (ii + 0.5) * stack.h_nm - 10 * NM)
|
||||||
|
ra, rb, H = 0.5 * NM, 1.2 * NM, p.tht_protrusion_nm
|
||||||
|
t_eq = H * np.clip((rb - r) / (rb - ra), 0, 1) \
|
||||||
|
* (p.rho_ohm_m / p.solder_rho_ohm_m)
|
||||||
|
expect = np.where(stack.masks[0],
|
||||||
|
1.0 + t_eq / p.layers[0].thickness_nm, 1.0)
|
||||||
|
assert np.allclose(stack.thick_scale[0], expect, rtol=1e-12)
|
||||||
|
# 1.5 mm of solder at the wall ~ 191 um copper: factor ~ 3.7 on 70 um
|
||||||
|
assert stack.thick_scale[0].max() > 3.0
|
||||||
|
|
||||||
|
p.electrodes1[0].protrusion_side = None # e.g. via contact: no cone
|
||||||
|
s2 = raster.rasterize_stack(p, 0.1 * NM)
|
||||||
|
assert s2.thick_scale is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_lead_fillet_lowers_resistance(monkeypatch):
|
||||||
|
"""The cone shorts the joint vicinity: R(with cone) < R(coat-less
|
||||||
|
bare barrel); the adaptive grid pins the cone cells fine and
|
||||||
|
matches the uniform grid."""
|
||||||
|
def prob(protrude=True):
|
||||||
|
p = make_problem([(PLATE20, [])],
|
||||||
|
rect1_mm=(0, 0, 1, 20), rect2_mm=(19, 0, 20, 20))
|
||||||
|
p.electrodes1 = [_barrel(10, 10, drill_mm=1.0, pad_mm=2.4,
|
||||||
|
solder=True)]
|
||||||
|
p.electrodes1[0].protrusion_side = "F.Cu"
|
||||||
|
if not protrude:
|
||||||
|
p.tht_protrusion_nm = 0
|
||||||
|
return p
|
||||||
|
|
||||||
|
r_cone, _ = _solve(prob(), 0.1)
|
||||||
|
r_bare, _ = _solve(prob(protrude=False), 0.1)
|
||||||
|
assert r_cone.R_ohm < r_bare.R_ohm
|
||||||
|
|
||||||
|
from fill_resistance import config
|
||||||
|
monkeypatch.setattr(config, "ADAPTIVE_CELLS", True)
|
||||||
|
r_ada, _ = _solve(prob(), 0.1)
|
||||||
|
assert r_ada.R_ohm == pytest.approx(r_cone.R_ohm, rel=2e-3)
|
||||||
|
|
||||||
|
|
||||||
def test_barrel_electrode_json_roundtrip(tmp_path):
|
def test_barrel_electrode_json_roundtrip(tmp_path):
|
||||||
p = make_problem([(PLATE20, [])],
|
p = make_problem([(PLATE20, [])],
|
||||||
rect1_mm=(0, 0, 1, 20), rect2_mm=(19, 0, 20, 20))
|
rect1_mm=(0, 0, 1, 20), rect2_mm=(19, 0, 20, 20))
|
||||||
p.electrodes1 = [_barrel(10, 10, drill_mm=0.6, pad_mm=1.2, solder=True,
|
p.electrodes1 = [_barrel(10, 10, drill_mm=0.6, pad_mm=1.2, solder=True,
|
||||||
polygons=[_disc(10, 10, 0.6)])]
|
polygons=[_disc(10, 10, 0.6)])]
|
||||||
p.electrodes1[0].barrel_z = (-1, 1_600_001)
|
p.electrodes1[0].barrel_z = (-1, 1_600_001)
|
||||||
|
p.electrodes1[0].protrusion_side = "B.Cu"
|
||||||
|
p.tht_protrusion_nm = 1_200_000
|
||||||
f = tmp_path / "d.json"
|
f = tmp_path / "d.json"
|
||||||
save_problem(p, f)
|
save_problem(p, f)
|
||||||
e = load_problem(f).electrodes1[0]
|
q = load_problem(f)
|
||||||
|
e = q.electrodes1[0]
|
||||||
assert e.drill_nm == 600_000 and e.pad_nm == 1_200_000
|
assert e.drill_nm == 600_000 and e.pad_nm == 1_200_000
|
||||||
assert e.center == (10 * NM, 10 * NM)
|
assert e.center == (10 * NM, 10 * NM)
|
||||||
assert e.barrel_z == (-1, 1_600_001)
|
assert e.barrel_z == (-1, 1_600_001)
|
||||||
assert e.solder is True and len(e.polygons) == 1
|
assert e.solder is True and len(e.polygons) == 1
|
||||||
|
assert e.protrusion_side == "B.Cu"
|
||||||
|
assert q.tht_protrusion_nm == 1_200_000
|
||||||
|
|||||||
Reference in New Issue
Block a user