Procedural 3D Modeling with Blender MCP: Designing a 36mm Aerodynamic Blower Nozzle
How to generate 100% watertight, parametric blower nozzles in Blender using the Python BMesh API and Model Context Protocol (MCP) automation.
Radial centrifugal blowers (such as standard 5015 or portable electronics cooling fans) deliver high static pressure, but stock rectangular exhaust ports ($36 \times 36\text{ mm}$ with corner chamfers) are poorly suited for focused air blasting or tight chassis cooling without custom ductwork.
Manually modeling lofted transition adapters in CAD or GUI-based 3D packages often leads to non-manifold edges, uneven wall thickness, or pinched internal airflow profiles.
By integrating the Blender Model Context Protocol (MCP) with procedural Python scripting, we built an automated generator that outputs print-ready, 100% manifold STL files and EEVEE Next visual renders with zero manual polygon cleanup.
1. Design Constraints & Aerodynamic Profile
The nozzle geometry requires three distinct zones along its $50\text{ mm}$ vertical axis ($Z$):
Ø12mm OD / Ø10mm ID Tip
[====] Z = 50mm
/ \
/ \ Aerodynamic Smooth Loft (H = 38mm)
/ \
/ \
+------------------+ Collar Shoulder, Z = 12mm
| |
| | Two-Stage Slip Collar (H = 12mm)
+------------------+ Z = 0mm (Blower Outlet Interface)
36mm x 36mm Base
- Two-Stage Slip Collar ($Z = 0 \to 12\text{ mm}$):
- Outer profile: $36.0\text{ mm} \times 36.0\text{ mm}$ square with $3.0\text{ mm} \times 45^\circ$ corner chamfers.
- Socket depth: $12.0\text{ mm}$ friction collar.
- Dual-tolerance generation:
- Exact fit: Direct $36.0\text{ mm}$ nominal dimension.
- Slip fit: $+0.4\text{ mm}$ diametric clearance ($36.4\text{ mm}$) to accommodate FDM perimeter swelling.
- Aerodynamic Smooth Loft ($Z = 12 \to 50\text{ mm}$):
- Gradual cross-sectional transformation from a 4-sided chamfered polygon to a circular nozzle outlet.
- Continuous wall thickness ($t = 1.0\text{ mm}$, exactly two $0.5\text{ mm}$ perimeters on a $0.4\text{ mm}$ nozzle).
- Circular Tip Outlet ($Z = 50\text{ mm}$):
- Inner diameter: $\varnothing 10.0\text{ mm}$ ($78.5\text{ mm}^2$ orifice area).
- Outer diameter: $\varnothing 12.0\text{ mm}$.
2. Mathematical Cross-Section Interpolation
To ensure laminar flow without turbulence-inducing vortex steps, each intermediate contour ring at normalized height $u \in [0, 1]$ blends between the chamfered square and the circular ring using cosine-weighted easing:
$$u = \frac{z - z_{\text{collar}}}{H_{\text{loft}}}$$$$w_{\text{shape}}(u) = \frac{1 - \cos(\pi u)}{2}$$For every azimuthal sample angle $\theta_i \in [0, 2\pi)$ across $N = 64$ radial vertices:
$$\mathbf{P}(u, \theta_i) = (1 - w_{\text{shape}}) \cdot \mathbf{P}_{\text{chamfer}}(\theta_i) + w_{\text{shape}} \cdot \mathbf{P}_{\text{circle}}(\theta_i)$$def interpolate_ring(z_norm, num_points=64, base_radius=18.0, tip_radius=5.0):
"""Interpolates between chamfered square and circular cross section."""
t = 0.5 * (1.0 - math.cos(math.pi * z_norm)) # Cosine smoothing
ring_points = []
for i in range(num_points):
theta = (2.0 * math.pi * i) / num_points
# Point on chamfered rectangular base
p_base = evaluate_chamfered_rect(theta, half_w=18.0, half_h=18.0, chamfer=3.0)
# Point on target circular tip
p_tip = Vector((tip_radius * math.cos(theta), tip_radius * math.sin(theta), 0.0))
# Blended vertex position
blended = p_base.lerp(p_tip, t)
ring_points.append(blended)
return ring_points
3. Constructing Manifold Geometry via BMesh
Boolean union and difference operations on thin-walled meshes frequently produce non-manifold vertex clusters or inverted normal shells.
Instead of shelling via CSG Booleans, the script constructs both the inner airflow lumen and the outer perimeter wall simultaneously in raw BMesh:
- Outer Wall Rings: Generated from $z = 0$ to $z = 50\text{ mm}$.
- Inner Wall Rings: Offset inwards by uniform normal distance $t = 1.0\text{ mm}$.
- Bridge Quads: Faces are stitched ring-to-ring using
bmesh.ops.bridge_loops:
def stitch_wall_tube(bm, outer_rings, inner_rings):
"""Bridges adjacent rings into watertight quad ribbons."""
# Skin outer surface
for r1, r2 in zip(outer_rings[:-1], outer_rings[1:]):
bm.faces.new([r1[i], r1[(i+1)%N], r2[(i+1)%N], r2[i]] for i in range(N))
# Skin inner lumen (reversed normal orientation)
for r1, r2 in zip(inner_rings[:-1], inner_rings[1:]):
bm.faces.new([r2[i], r2[(i+1)%N], r1[(i+1)%N], r1[i]] for i in range(N))
# Cap tip and collar rims
bridge_rim(bm, outer_rings[0], inner_rings[0])
bridge_rim(bm, outer_rings[-1], inner_rings[-1])
The resulting mesh has exactly 0 non-manifold edges, 0 loose vertices, and consistent outwards-pointing surface normals.
4. Automation via Blender MCP
Using Blender MCP, the model generation, material assignment, EEVEE Next studio lighting setup, and dual STL exports are triggered automatically from the command line:
& "C:\Program Files\Blender Foundation\Blender 5.1\blender.exe" `
--background `
--python generate_blower_nozzle.py
The script automatically exports:
blower_nozzle_36mm_exact.stl: Tight fit for soft materials (PETG/TPU).blower_nozzle_36mm_slip_fit.stl: Standard fit for rigid PLA/ABS.blower_nozzle_preview.png: 1080p studio beauty render.