
Simulating Physics in Python: Vacuum Nozzle Validation for SMT Assembly
Every component on a PCB has a physical body width. Every nozzle has an outer diameter. If the nozzle is too large, it drops the part. Here is the 1,600-line async pipeline that validates this at scale.
- Published
- April 27, 2026
- Author
- Hrushiekesh Kanjula Reddy
- Read time
- ~7 min
- Category
- engineering

There is a physical law embedded in every SMT assembly job: the outer diameter of the vacuum nozzle picking up a component must not exceed 50% of that component's body width. Exceed this ratio and the nozzle overhangs the component edge. The vacuum seal breaks. The component falls onto the factory floor, or worse, onto an adjacent already-placed component.
This constraint applies to every component on every board, checked against every nozzle in the machine's current tool roster. A typical PCB has 200–800 components. A machine might have 12 nozzle types in rotation. Manually validating 200 × 12 = 2,400 checks is not a realistic pre-production step. The assembly hub does it automatically in under 10 seconds using an async pipeline.
The Data Requirements
Two datasets feed the validation:
Component geometries — pulled from the canonical SQLite library. For each component reference designator on the board's placement list, we need body_width_mm. This data comes from manufacturer datasheets, imported during component normalization.
Machine nozzle roster — the current set of nozzles loaded in the machine, with each nozzle's outer_diameter_mm. This is queried from the machine's CGI API at runtime — it reflects the actual tools loaded today, not what was loaded last week.
The validation logic itself is trivial:
def nozzle_valid_for_component(
nozzle_od_mm: float,
component_body_width_mm: float
) -> bool:
return nozzle_od_mm <= 0.5 * component_body_width_mmThe complexity is in gathering and correlating the two datasets efficiently across hundreds of components and a dozen nozzle types.
Why Async?
The bottleneck is I/O: querying the machine CGI API (network), querying the SQLite database (disk), and potentially fetching missing component geometry data from the Mouser API (network). Doing these sequentially means each component waits for all the I/O of the previous component to complete before its own queries start.
With asyncio, all component geometry queries can be dispatched concurrently — the event loop fires all database read coroutines, then processes results as they complete. For 500 components, this reduces wall-clock time from ~25 seconds (sequential) to ~3 seconds (concurrent).
import asyncio
import aiosqlite
async def get_component_geometry(db_path: str, ref_des: str) -> dict:
async with aiosqlite.connect(db_path) as db:
async with db.execute(
"SELECT body_width_mm FROM component_library WHERE ref_des = ?",
(ref_des,)
) as cursor:
row = await cursor.fetchone()
return {"ref_des": ref_des, "body_width_mm": row[0] if row else None}
async def validate_all_components(placement_list: list, nozzle_roster: list, db_path: str):
# Dispatch all geometry queries concurrently
geometry_tasks = [
get_component_geometry(db_path, comp["ref_des"])
for comp in placement_list
]
geometries = await asyncio.gather(*geometry_tasks)
# Now validate each component against all nozzles
violations = []
for geom in geometries:
if geom["body_width_mm"] is None:
violations.append({"ref": geom["ref_des"], "issue": "MISSING_GEOMETRY"})
continue
for nozzle in nozzle_roster:
if not nozzle_valid_for_component(nozzle["od_mm"], geom["body_width_mm"]):
violations.append({
"ref": geom["ref_des"],
"nozzle": nozzle["id"],
"issue": "OVERHANG_VIOLATION",
"ratio": nozzle["od_mm"] / geom["body_width_mm"]
})
return violations
Handling Missing Geometry Data
Not every component in the library has a documented body_width_mm. Older components from legacy suppliers often have incomplete datasheets. The pipeline handles this in two ways:
Package-based fallback. Most standardized package sizes have known nominal body widths. An 0402 resistor is 1.0mm × 0.5mm. An 0603 is 1.6mm × 0.8mm. If the library entry has no explicit body width but has a recognized package code, the pipeline substitutes the package nominal width with a safety margin applied.
Mouser API enrichment. For components with unknown package codes and no body width, the pipeline queries the Mouser API using the manufacturer part number to retrieve dimensional data. These lookups are also asynchronous and cached — the first query populates the library, subsequent runs use the cached value.
The pipeline flags components it cannot resolve through either fallback. These are surfaced in the UI as "geometry unknown — manual review required" rather than silently skipped.
The Violation Report
The output of the validation pipeline is a structured violation report grouped by severity:
CRITICAL — Nozzle-to-component ratio exceeds 0.6 (20% beyond the constraint). Likely causes a drop. Assembly must not proceed without changing the nozzle assignment.
WARNING — Ratio between 0.5 and 0.6. Marginal case — may work on a well-maintained nozzle, will fail on a worn one. Flagged for engineer review.
INFO — Ratio between 0.45 and 0.5. Within spec but close to the limit. Noted for monitoring.

The report also suggests nozzle substitutions: for each violation, it identifies nozzles in the roster that would pass the constraint for that component, ordered by how close they are to the optimal pick ratio (0.3–0.4 is the sweet spot for reliable vacuum seal).
The Broader Principle
This validation pipeline encodes a physical law into software. The 50% constraint isn't a business rule that might change next quarter — it's a consequence of fluid dynamics and contact geometry that doesn't negotiate. Encoding it into an automated pre-production check means the constraint is enforced consistently, regardless of operator experience or shift pressure.
There are many such physical laws in SMT assembly: thermal pad sizing constraints, solder paste volume requirements, minimum clearance rules between adjacent components. The assembly hub's validation framework is designed to encode them all using the same async pipeline pattern — a new physical constraint becomes a new async coroutine and a new violation type in the report.
Software that models physics is some of the most reliable software you can write, because the physics doesn't have bugs.