A jack-o'-lantern is a good first exercise for signed distance functions (SDFs): one formula gives the ribbed body, a second gives the hollow, and a few polygons carve the face. This uses manifold3d and plain Python, with no CAD program.
1. A ribbed pumpkin from one formula
A squashed ellipsoid is (rho/R)^2 + (z/H)^2 = 1. To get ribs, let the radius change with the angle: R(t) = R * (1 + 0.09 * cos(10 t)). level_set samples a function on a grid and returns a mesh where it crosses zero. Positive means inside.
import math
from manifold3d import Manifold
R, H = 30.0, 25.0
def pumpkin(scale):
def f(x, y, z):
rho, t = math.hypot(x, y), math.atan2(y, x)
Rt = R * scale * (1 + 0.09 * math.cos(10 * t))
d = math.sqrt((rho / Rt) ** 2 + (z / (H * scale)) ** 2)
return (1 - d) * 20
return f
box = [-36, -36, -30, 36, 36, 30] # minx, miny, minz, maxx, maxy, maxz
outer = Manifold.level_set(pumpkin(1.0), box, 0.55) # 0.55 mm grid
inner = Manifold.level_set(pumpkin(0.9), box, 0.55)
shell = outer - inner # hollow, about 2.5 mm wall
shell = shell ^ Manifold.cube([200, 200, 200]).translate([-100, -100, -17]) # flat open base
The ^ keeps only the part above z = -17. That gives an open base for an LED tealight and a flat face to print on.
2. Carve the face
Draw each shape as a 2D polygon in the x/z plane, extrude it 50 mm and boolean-subtract it from the front wall.
from manifold3d import CrossSection
def poly(pts):
area = sum(pts[k][0] * pts[(k + 1) % len(pts)][1]
- pts[(k + 1) % len(pts)][0] * pts[k][1] for k in range(len(pts)))
pts = pts if area > 0 else pts[::-1] # must be counter-clockwise
return CrossSection([pts]).extrude(50).rotate([90, 0, 0])
eye_l = poly([(-17, 6), (-6, 6), (-11.5, 15)])
eye_r = poly([(6, 6), (17, 6), (11.5, 15)])
nose = poly([(-3, 2), (3, 2), (0, -3.5)])
mouth = poly([(-17, -3), (-12, -9), (-8, -5), (-4, -11), (0, -6), (4, -11),
(8, -5), (12, -9), (17, -3), (13, -11), (0, -13), (-13, -11)])
for cut in (eye_l, eye_r, nose, mouth):
shell = shell - cut
3. A stem with a hang hole
stem = Manifold.cylinder(9, 4.2, 3.2, 24).translate([0, 0, H * 0.9 - 2])
hole = Manifold.cylinder(12, 1.6, 1.6, 20).rotate([90, 0, 0]).translate([0, 6, H * 0.9 + 4.5])
pumpkin_mesh = shell + (stem - hole)
Gotchas
-
level_setcalls your function with three numbers(x, y, z), not one tuple. - A clockwise polygon gives an empty
CrossSection, and the boolean silently removes the whole shell. The signed-area check inpolyfixes that. - Keep the bottom of the mouth well above the cut plane. A mouth that reaches the base leaves a thin floppy lip.
- Change the rib count (6, 8, 12) and the squash
H / Rto get a whole family of pumpkins from the same code.
I have not test printed these yet. The meshes come out as a single manifold solid, and the renders look right. The finished lantern and a 3-pack with three shapes and faces are on Cults3D for $1.27.













