Solder cone around protruding THT leads (tent structure)
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:
janik
2026-07-16 18:47:10 +07:00
parent 4ff729e192
commit 9e307f2818
6 changed files with 184 additions and 12 deletions
+49
View File
@@ -232,9 +232,58 @@ def rasterize_stack(problem: Problem, h_nm: float) -> RasterStack:
_paint_ring(stack, hole, False, pmask)
stack.buildup[li] |= pmask
stack.buildup &= stack.masks # solder wets exposed copper only
_paint_lead_fillets(stack, problem)
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]:
return [li for li, layer in enumerate(problem.layers)
if via.spans(layer.z_nm)]