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
  1. 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.
  2. 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).
  3. 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:

  1. Outer Wall Rings: Generated from $z = 0$ to $z = 50\text{ mm}$.
  2. Inner Wall Rings: Offset inwards by uniform normal distance $t = 1.0\text{ mm}$.
  3. 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.