Modeling a Dutch windmill drivetrain step by step
This is the model that started the whole project. I was trying to understand the speed-up ratio in a traditional Dutch smock mill — the kind you see in Kinderdijk — and couldn't find a simulation tool that handled the combination of bevel gears and a fixed-frame horizontal shaft without a lot of manual coordinate bookkeeping. Molenspin was built to make this easy. Here's the full walkthrough.
The drivetrain in brief
A Dutch windmill drivetrain typically has four main elements:
- Brake wheel — a large horizontal gear (120–180 teeth, wooden cogs) fixed to the vertical sail shaft. Rotates at sail speed: typically 8–16 RPM in working wind.
- Wallower — a small gear (24–36 teeth) on a horizontal shaft, meshing with the brake wheel at 90°. This is the bevel pair.
- Stone nut — a smaller gear on the same horizontal shaft as the wallower (or a separate shaft driven by belt), engaging the millstone spindle.
- Millstone spindle — drives the upper millstone. Target: 80–120 RPM for coarse grinding, up to 200 RPM for fine flour.
Step 1 — the sail shaft
import molenspin as ms
asm = ms.Assembly(name="smock-mill", default_module=4.0)
# Sail shaft: 12 RPM at working wind
sail_shaft = asm.add_shaft(omega_rpm=12.0, name="sail")
Step 2 — brake wheel and wallower
# Brake wheel: 144-tooth wood-cog gear on the sail shaft
brake_wheel = asm.add_gear(teeth=144, driven_by=sail_shaft, name="brake_wheel")
# Wallower: 24-tooth gear, 90-degree bevel mesh with brake wheel
wallower = asm.add_gear(teeth=24, name="wallower")
bevel = ms.BevelPair(
gear_a=brake_wheel,
gear_b=wallower,
shaft_angle=90.0
)
# Wallower shaft position: anchored 650 mm below frame origin
asm.add_constraint(ms.PivotConstraint(
shaft=wallower.shaft,
position=(0.0, -650.0),
fixed_angle=True,
))
print(f"After bevel: wallower = {wallower.shaft.omega_rpm:.1f} RPM")
# After bevel: wallower = 72.0 RPM (12 × 144/24)
Step 3 — stone nut and millstone
# Stone nut: 18-tooth gear on same shaft as wallower
stone_nut = asm.add_gear(teeth=18, driven_by=wallower.shaft, name="stone_nut")
# Millstone spindle gear: 54-tooth
spindle_gear = asm.add_gear(teeth=54, meshes_with=stone_nut, name="spindle")
print(f"Millstone: {spindle_gear.shaft.omega_rpm:.1f} RPM")
# Millstone: 24.0 RPM (72 × 18/54)
24 RPM is too slow for grinding. Historical records suggest stone nuts typically had a 1:3 ratio with an additional belt stage. Let me add that:
# Belt stage: 1:4 step-up to the final millstone shaft
belt = ms.BeltDrive(
sprocket_driver=asm.add_gear(teeth=14, driven_by=spindle_gear.shaft),
sprocket_driven=asm.add_gear(teeth=56),
slip=0.01
)
mill_shaft = belt.sprocket_driven.shaft
print(f"Final millstone: {mill_shaft.omega_rpm:.2f} RPM")
# Final millstone: 6.00 RPM — still too slow, historical records suggest ~100 RPM
After more research I found that historical brake wheels often had far higher tooth counts — some records mention 180-tooth wheels with 12-tooth wallowers (ratio 15:1) rather than the 6:1 I used. With a 15:1 bevel and a 1:4 belt stage the sail-to-millstone ratio is 60:1, giving 720 RPM from 12 RPM sails. That matches the historical records much better.
Step 4 — simulate and export
result = asm.simulate(duration=5.0, dt=1e-3)
result.print_summary()
result.export_svg("windmill.svg", fps=24, show_pitch_circles=True)
What I learned
The main surprise was how sensitive the final RPM is to the tooth count on the wallower. A 24-tooth wallower gives 6:1 from the bevel stage; a 12-tooth wallower gives 12:1. That doubles the millstone speed from the same sail input. Historical millwrights apparently tuned this by swapping the wallower — it's a relatively easy gear to change because it's at the top of the horizontal shaft, accessible from inside the cap.