Reverse-Engineering the Craighill Standing Bowl: Procedural CSG and Rolling Fillets in Blender MCP
How to programmatically model the iconic Craighill standing catchall bowl using Blender MCP, exact boolean unions, and continuous rolling-ball fillets.
The Craighill Standing Bowl is a celebrated piece of industrial metalware: a shallow, spherical catchall bowl suspended in a rigid cruciform base formed by two intersecting vertical planes. Its visual appeal comes from the interplay between the crisp planar fins and the smooth spherical bowl, with continuous, organic fillets running along the interior junctions.
Recreating this design via standard interactive CAD often leads to fin alignment errors, non-planar base rocking, or failed fillet topology at the 4-way intersection.
Using the Blender Model Context Protocol (MCP), we reverse-engineered the bowl procedurally in Python, automating exact Constructive Solid Geometry (CSG) unions and generating pristine rolling-ball fillets with 0 non-manifold elements.
1. Anatomy of the Design
.-------------------------. <-- Revolved Spherical Bowl Rim
/ \
+======|=============================|======+ <-- Planar Cruciform Fin Base
| | | |
| \ / |
| '-------------------------' |
| | |
+─────────────────────┴─────────────────────+ Z = 0.0 (Flat Ground Plane)
Wing 1 Wing 2
The model consists of two primary intersecting bodies:
- The Revolved Bowl Shell: A shallow spherical cap with a defined wall thickness, exterior curvature radius $R_{\text{ext}}$, and a soft rounded rim.
- The Cruciform Base: Two crossed vertical planar wings intersecting at a $90^\circ$ angle along the central $Z$-axis, extending radially past the bowl perimeter to form four stabilizing arms.
Flat-Bottom Ground Constraint
Early prototypes featured arched cutouts beneath the bowl. However, to ensure maximum stability on hard desk surfaces and eliminate tipping when heavier objects are dropped into the bowl’s rim, the base was updated so that all four cruciform fin wings sit completely flat on the ground plane at $Z = 0.0$.
2. Procedural CSG Boolean Pipeline
In standard polygon modeling, joining two complex curved meshes with bmesh.ops.boolean can leave degenerate sliver faces or non-manifold edge fans.
We solved this by enforcing a two-phase union with the EXACT arithmetic solver:
def build_craighill_bowl(bowl_radius=50.0, bowl_depth=22.0, fin_width=80.0, fin_thickness=4.0):
"""Constructs watertight standing bowl via exact CSG."""
# 1. Generate revolved spherical bowl shell
bowl_obj = create_spherical_cap(radius=bowl_radius, depth=bowl_depth, thickness=3.5)
# 2. Generate extruded crossed fins at 90 degrees
fin_x = create_planar_fin(length=fin_width, thickness=fin_thickness, height=bowl_depth + 4.0)
fin_y = create_planar_fin(length=fin_width, thickness=fin_thickness, height=bowl_depth + 4.0)
fin_y.rotation_euler.z = math.radians(90.0)
# Union crossed fins into single cruciform base
base_obj = boolean_union(fin_x, fin_y)
# Union bowl shell with cruciform base
final_bowl = boolean_union(bowl_obj, base_obj)
# Clear interior cavity
clear_internal_intersecting_geometry(final_bowl)
return final_bowl
3. Generating Rolling-Ball Fillets Along Complex Seams
The defining visual characteristic of the Craighill bowl is the heavy, fluid fillet running between the vertical fin walls and the convex underside of the spherical bowl.
Standard modifier-based bevels in Blender often fail at multi-surface saddle points. We implemented a dedicated rolling-ball fillet pass along the junction seams:
Vertical Fin Wall
│
│
│ ) ) <-- Rolling-Ball Fillet (w = 0.032, 4 segments)
│ /
───────────┴─────────── Convex Bowl Underside
- Junction Edge Tagging: Edges created by the boolean intersection between the fin and the bowl are tagged.
- 4-Segment Fillet Profile: The tagged seams are beveled with a rolling-ball radius ($w = 0.032\text{ BU}$, 4 segments).
- Smooth Top Termination: As the fillet reaches the top termination where the fin meets the bowl rim, the bevel width tapers gracefully to zero, preventing pinching at the perimeter.
4. Manifold Verification & EEVEE Beauty Renders
Before exporting the master .blend and .stl files, the procedural script runs an automated topological health check:
def verify_manifold_topology(obj):
"""Verifies that the mesh is 100% watertight and manifold."""
bm = bmesh.new()
bm.from_mesh(obj.data)
non_manifold_edges = [e for e in bm.edges if not e.is_manifold]
loose_verts = [v for v in bm.verts if not v.link_edges]
assert len(non_manifold_edges) == 0, f"Found {len(non_manifold_edges)} non-manifold edges!"
assert len(loose_verts) == 0, f"Found {len(loose_verts)} loose vertices!"
print("SUCCESS: 0 non-manifold edges, 0 loose verts. 100% Watertight.")
bm.free()
The resulting model was rendered in EEVEE with a satin brushed brass material, showcasing crisp highlights along the fin edges and soft specular reflections across the interior bowl basin.