Download this example
Download this example as a Jupyter Notebook or as a Python script.
Applied: Solder ball modeling for morphing#
Solder balls are the small spherical joints used in ball grid array (BGA) packages to electrically and mechanically connect a chip to a printed circuit board (PCB). Accurately representing their geometry is critical for thermal, structural, and drop-test simulations.
A common meshing challenge is that cylindrical solder columns are easy to mesh but physically inaccurate, while spherical solder balls are realistic but harder to mesh. Morphing workflows solve this by:
Meshing a source model that uses idealised cylindrical solder columns (easy to mesh).
Defining a target model with realistic spherical solder balls.
Morphing the source mesh so that the solder nodes move from the cylinder shape to the spherical shape, producing a high-quality, physically accurate mesh.
This example builds both models using PyAnsys Geometry:
Source model: A six-layer PCB laminate stack with a 3 × 3 array of cylindrical solder columns and copper pads. A
solder_cylnamed selection marks the solder surfaces that is to be morphed.Target model: The same 3 × 3 footprint with spherical solder balls created by revolving a 2D profile. A
solder_sphnamed selection marks the spherical surfaces that define the target shape.
Required imports#
Import the PyAnsys Geometry modules needed throughout the example.
[1]:
from ansys.geometry.core import launch_modeler
from ansys.geometry.core.designer import SurfaceType
from ansys.geometry.core.math import (
UNITVECTOR3D_X,
UNITVECTOR3D_Y,
UNITVECTOR3D_Z,
Plane,
Point2D,
Point3D,
)
from ansys.geometry.core.misc import DEFAULT_UNITS, UNITS, Angle, Distance
from ansys.geometry.core.plotting import GeometryPlotter
from ansys.geometry.core.sketch import Sketch
Source model: PCB stack with cylindrical solder columns#
Launch the modeler and set units#
All dimensions in the source model are in millimetres.
[2]:
modeler = launch_modeler()
DEFAULT_UNITS.LENGTH = UNITS.mm
Sketch the PCB laminate layers#
A realistic PCB stack has alternating copper and dielectric (core/prepreg) layers. The following table lists each layer, its start elevation (Z), and its thickness.
Layer |
Z start (mm) |
Thickness (mm) |
Role |
|---|---|---|---|
Layer 1 |
0.00 |
0.24 |
Bottom copper / dielectric |
Layer 2 |
0.24 |
0.26 |
Prepreg |
Layer 3 |
0.50 |
0.47 |
Core |
Infill |
0.97 |
0.67 |
PCB infill / underfill region |
Layer 4 |
1.64 |
0.24 |
Core |
Layer 5 |
1.88 |
0.26 |
Prepreg |
Layer 6 |
2.14 |
0.47 |
Top copper / dielectric |
All layers share a 4.0035 × 4.0035 mm² footprint centred at the origin.
[3]:
BOARD_SIZE = 4.0035 # mm
layer_1_sketch = Sketch()
layer_1_sketch.box(center=Point2D([0, 0]), width=BOARD_SIZE, height=BOARD_SIZE)
layer_2_sketch = Sketch(Plane(origin=Point3D([0, 0, 0.24])))
layer_2_sketch.box(center=Point2D([0, 0]), width=BOARD_SIZE, height=BOARD_SIZE)
layer_3_sketch = Sketch(Plane(origin=Point3D([0, 0, 0.50])))
layer_3_sketch.box(center=Point2D([0, 0]), width=BOARD_SIZE, height=BOARD_SIZE)
infill_sketch = Sketch(Plane(origin=Point3D([0, 0, 0.97])))
infill_sketch.box(center=Point2D([0, 0]), width=BOARD_SIZE, height=BOARD_SIZE)
layer_4_sketch = Sketch(Plane(origin=Point3D([0, 0, 1.64])))
layer_4_sketch.box(center=Point2D([0, 0]), width=BOARD_SIZE, height=BOARD_SIZE)
layer_5_sketch = Sketch(Plane(origin=Point3D([0, 0, 1.88])))
layer_5_sketch.box(center=Point2D([0, 0]), width=BOARD_SIZE, height=BOARD_SIZE)
layer_6_sketch = Sketch(Plane(origin=Point3D([0, 0, 2.14])))
layer_6_sketch.box(center=Point2D([0, 0]), width=BOARD_SIZE, height=BOARD_SIZE)
[3]:
<ansys.geometry.core.sketch.sketch.Sketch at 0x7f223e3bef10>
Sketch the solder features#
Each solder joint consists of three features sharing a 0.25-millimeter radius:
Bottom copper pad — at Z = 0.97 mm, thickness 0.0382 mm
Solder cylinder — at Z = 1.0082 mm, height 0.5835 mm
Top copper pad — at Z = 1.5918 mm, thickness 0.0482 mm
[4]:
SOLDER_RADIUS = 0.25 # mm
# Bottom copper pad
copper_pad_bottom_sketch = Sketch(Plane(origin=Point3D([0, 0, 0.97])))
copper_pad_bottom_sketch.circle(center=Point2D([0, 0]), radius=Distance(SOLDER_RADIUS))
# Solder cylinder
solder_cylinder_sketch = Sketch(Plane(origin=Point3D([0, 0, 1.0082])))
solder_cylinder_sketch.circle(center=Point2D([0, 0]), radius=Distance(SOLDER_RADIUS))
# Top copper pad
copper_pad_top_sketch = Sketch(Plane(origin=Point3D([0, 0, 1.5918])))
copper_pad_top_sketch.circle(center=Point2D([0, 0]), radius=Distance(SOLDER_RADIUS))
[4]:
<ansys.geometry.core.sketch.sketch.Sketch at 0x7f223e424e50>
Build the PCB design#
Create the design and extrude all sketched profiles into solid bodies.
[5]:
design = modeler.create_design("pcb_solder_cylinders")
component = design.add_component("pcb_model")
# PCB laminate layers
layer_1 = component.extrude_sketch("layer_1", layer_1_sketch, distance=Distance(0.24))
layer_2 = component.extrude_sketch("layer_2", layer_2_sketch, distance=Distance(0.26))
layer_3 = component.extrude_sketch("layer_3", layer_3_sketch, distance=Distance(0.47))
layer_4 = component.extrude_sketch("layer_4", layer_4_sketch, distance=Distance(0.24))
layer_5 = component.extrude_sketch("layer_5", layer_5_sketch, distance=Distance(0.26))
layer_6 = component.extrude_sketch("layer_6", layer_6_sketch, distance=Distance(0.47))
infill = component.extrude_sketch("infill", infill_sketch, distance=Distance(0.67))
# Solder features for the centre position (i=0, j=0)
copper_pad_bottom = component.extrude_sketch(
"copper_pad", copper_pad_bottom_sketch, distance=Distance(0.0382)
)
solder_cylinder = component.extrude_sketch(
"solder", solder_cylinder_sketch, distance=Distance(0.5835)
)
copper_pad_top = component.extrude_sketch(
"copper_pad", copper_pad_top_sketch, distance=Distance(0.0482)
)
Replicate solder joints in a 3 × 3 array#
The solder balls are arranged on a 1-millimeter pitch in a 3 × 3 grid centered at the origin (offsets: −1, 0, +1 mm in X and Y). The infill body is Boolean-subtracted so that the solder columns protrude through the PCB as expected.
[6]:
for i in range(-1, 2):
for j in range(-1, 2):
if i == 0 and j == 0:
# Center position — subtract the already-created solder bodies
# from the infill, keeping the solder bodies themselves.
infill.subtract(
[copper_pad_bottom, copper_pad_top, solder_cylinder],
keep_other=True,
)
else:
# Off-center positions — copy and translate the solder bodies,
# then subtract each copy from the infill.
for body, owner in [
(copper_pad_bottom, component),
(copper_pad_top, component),
(solder_cylinder, component),
]:
new_body = body.copy(owner)
new_body.translate(direction=UNITVECTOR3D_X, distance=i)
new_body.translate(direction=UNITVECTOR3D_Y, distance=j)
infill.subtract(new_body, keep_other=True)
Create the solder_cyl named selection#
Collect all solder bodies and their lateral (non-end-cap) cylindrical faces. This named selection identifies the solder surfaces to be morphed.
[7]:
solder_bodies = []
solder_source_faces = []
for body in component.bodies:
if body.name == "solder":
solder_bodies.append(body)
for face in body.faces:
# Exclude top and bottom end-cap faces (unit normal purely in Z)
if face.normal().z not in (1, -1):
solder_source_faces.append(face)
design.create_named_selection(
"solder_cyl", bodies=solder_bodies, faces=solder_source_faces
)
print(
f"'solder_cyl' named selection: "
f"{len(solder_bodies)} bodies, {len(solder_source_faces)} lateral faces"
)
'solder_cyl' named selection: 9 bodies, 9 lateral faces
Visualize the source model#
The GeometryPlotter, which is the visualization object leveraged throughout PyAnsys Geometry, displays each body type in a distinct color and opacity, making it easy to see the internal PCB layers through the semi-transparent laminate stack:
PCB layers: Green, semi-transparent (opacity 0.3)
Infill / underfill: Khaki, semi-transparent (opacity 0.2)
Copper pads: Orange, opaque
Solder cylinders: Silver, opaque
[8]:
pcb_layers = [layer_1, layer_2, layer_3, layer_4, layer_5, layer_6]
copper_pads = [b for b in component.bodies if b.name == "copper_pad"]
plotter = GeometryPlotter()
for layer in pcb_layers:
plotter.plot(layer, color="green", opacity=0.3, merge_bodies=True)
plotter.plot(infill, color="tan", opacity=0.2, merge_bodies=True)
for pad in copper_pads:
plotter.plot(pad, color="orange", merge_bodies=True)
for solder in solder_bodies:
plotter.plot(solder, color="silver", merge_bodies=True)
plotter.show()
Export the source model#
[9]:
source_file = design.export_to_scdocx()
print(f"Source model exported to: {source_file}")
Source model exported to: /home/runner/work/pyansys-geometry/pyansys-geometry/doc/source/examples/04_applied/pcb_solder_cylinders.scdocx
[10]:
modeler.close()
Target model: Spherical solder balls#
The target model replaces the cylinders with physically accurate solder ball shapes. Each ball is generated by revolving a 2D profile (an arc that approximates the meniscus of a reflowed solder joint) 360° around the Z-axis.
Solder ball profile#
The profile is a circular arc with a 0.3821-millimeter radius that spans 98.82°, connecting the bottom pad face to the top pad face. The arc is closed with a straight segment to form a closed 2D region suitable for revolving.
[11]:
modeler = launch_modeler()
DEFAULT_UNITS.LENGTH = UNITS.mm
# The sketch plane is offset from the Z-axis so the arc profile sits at the
# correct radial distance from the rotation axis.
solder_ball_plane = Plane(
origin=Point3D([SOLDER_RADIUS, 0, 1.0082]),
direction_x=UNITVECTOR3D_X,
direction_y=UNITVECTOR3D_Z,
)
solder_ball_sketch = Sketch(solder_ball_plane)
# Draw the arc profile and close it with a line segment back to the start.
solder_ball_sketch.arc_from_start_center_and_angle(
start=Point2D([0, 0]),
center=Point2D([-SOLDER_RADIUS, 0.2918]),
angle=Angle(98.82, unit=UNITS.degrees),
clockwise=False,
).segment_to_point(Point2D([0, 0]))
solder_ball_sketch.plot()
Revolve the profile to create a single solder ball#
[12]:
design_target = modeler.create_design("pcb_solder_spheres")
component_target = design_target.add_component("spherical_balls")
# Revolve 360° around the global Z-axis through the origin.
solder_ball = component_target.revolve_sketch(
"solder_ball",
solder_ball_sketch,
UNITVECTOR3D_Z,
Angle(360, unit=UNITS.degrees),
Point3D([0, 0, 1.0082]),
)
Replicate in a 3 × 3 array#
[13]:
for i in range(-1, 2):
for j in range(-1, 2):
if i == 0 and j == 0:
continue # Original body is already at the centre
new_ball = solder_ball.copy(component_target)
new_ball.translate(direction=UNITVECTOR3D_X, distance=i)
new_ball.translate(direction=UNITVECTOR3D_Y, distance=j)
Create the solder_sph named selection#
Collect all solder_ball bodies and their spherical faces. These define the target shape for the morphing step.
[14]:
solder_ball_bodies = []
solder_target_faces = []
for body in component_target.bodies:
if body.name == "solder_ball":
solder_ball_bodies.append(body)
for face in body.faces:
if face.surface_type == SurfaceType.SURFACETYPE_SPHERE:
solder_target_faces.append(face)
design_target.create_named_selection(
"solder_sph", bodies=solder_ball_bodies, faces=solder_target_faces
)
print(
f"'solder_sph' named selection: "
f"{len(solder_ball_bodies)} bodies, {len(solder_target_faces)} spherical faces"
)
'solder_sph' named selection: 9 bodies, 9 spherical faces
Visualize the target model#
The solder balls are plotted in silver to convey a metallic solder appearance.
[15]:
plotter = GeometryPlotter()
for ball in solder_ball_bodies:
plotter.plot(ball, color="silver", merge_bodies=True)
plotter.show()
Export the target model#
[16]:
target_file = design_target.export_to_scdocx()
print(f"Target model exported to: {target_file}")
Target model exported to: /home/runner/work/pyansys-geometry/pyansys-geometry/doc/source/examples/04_applied/pcb_solder_spheres.scdocx
[17]:
modeler.close()
Summary#
This example produced two geometry files ready for a mesh-morphing workflow:
File |
Named selection |
Purpose |
|---|---|---|
|
|
Source: Easy-to-mesh cylindrical solder columns |
|
|
Target: Realistic spherical solder balls |
In a downstream morphing tool (for example, Ansys Mechanical with morphing technology), the solder_cyl faces are mapped to the solder_sph faces so that the cylindrical mesh deforms smoothly into the spherical shape while the rest of the PCB mesh remains undisturbed.
References#
Download this example
Download this example as a Jupyter Notebook or as a Python script.