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
+16
View File
@@ -116,6 +116,11 @@ class Electrode:
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
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
@@ -170,6 +175,13 @@ class Problem:
cap_max_drill_nm: int = 500_000 # fab caps only small vias:
# drills above this stay open
# 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
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)),
"barrel_z": (None if e.barrel_z is None else list(e.barrel_z)),
"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
else (int(d["barrel_z"][0]), int(d["barrel_z"][1]))),
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,
"cap_plating_nm": p.cap_plating_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)),
cap_plating_nm=int(d.get("cap_plating_nm", 15_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)),
)