Slow Feeder Bowl
A shallow round bowl with one continuous spiral ridge from the centre outward, so the kibble lies in a long winding channel and a dog or cat has to work along it instead of gulping. The spiral is an Archimedean curve, so the channel width is the same everywhere and set directly: wide for a dog, narrow for a cat. The default is 180 mm across with a 25 mm rim, 17 mm channels and an 18 mm ridge, and prints flat with no supports. Wash by hand. Adjustable parameters: bowl diameter, rim height, wall thickness, channel width, ridge thickness and ridge height.
# Slow Feeder Bowl - a spiral ridge turns a bowl into a maze that makes a pet eat slowly
#
# A shallow round bowl with one continuous spiral ridge from the centre outward, so the
# kibble lies in a long winding channel and the pet has to work along it. The spiral is
# an Archimedean curve (radius grows evenly with the angle), so the channel width is the
# same everywhere and set directly: wide for a dog, narrow for a cat. Prints flat with
# no supports; wash by hand.
@param 120..240 step:10 group:"Bowl" desc:"Bowl outer diameter (mm)"
$dia = 180
@param 15..35 step:1 group:"Bowl" desc:"Rim height (mm)"
$rim_h = 25
@param 2..4 step:0.5 group:"Bowl" desc:"Rim and floor thickness (mm)"
$wall = 3
@param 12..30 step:1 group:"Maze" desc:"Channel width between turns of the spiral (mm)"
$channel = 17
@param 2..4 step:0.5 group:"Maze" desc:"Ridge thickness (mm)"
$ridge_t = 3
@param 10..25 step:1 group:"Maze" desc:"Ridge height above the floor (mm)"
$ridge_h = 18
$r_out = $dia / 2
$r_in = $r_out - $wall # inside the rim
$pitch = $channel + $ridge_t # radial distance between turns
$r0 = 6 # the spiral starts here
$turns = ($r_in - $ridge_t / 2 - 3 - $r0) / $pitch
$steps = floor($turns * 36) # one point every 10 deg
assert $turns >= 1.5
# the ridge as one closed polygon: out along the spiral's outer edge, back along its inner edge
$m = $steps + 1
def kk($j) = if $j < $m then $j else 2 * $m - 1 - $j
def rr($j) = $r0 + $pitch * kk($j) / 36 + (if $j < $m then $ridge_t / 2 else -$ridge_t / 2)
$pts = [(rr($j) * cos(10 * kk($j)), rr($j) * sin(10 * kk($j))) for $j in range(2 * $m)]
$spiral = polyline $pts | extrude $ridge_h | translate 0 0 ($wall - 0.01)
$floor = cylinder $r_out $wall center:(true, true, false)
$rim = (cylinder $r_out $rim_h center:(true, true, false)) - (cylinder $r_in ($rim_h + 2) center:(true, true, false) | translate 0 0 -1)
$base = $floor + $rim
$base_c = $base | edges <Z | chamfer 1
$bowl = $base_c + $spiral
assert $bowl.solids == 1
assert $bowl.bbox.x ~ $dia
assert $bowl.bbox.z ~ $rim_h
assert $ridge_h < $rim_h
assert $spiral.bbox.x < 2 * $r_in # the spiral stays inside the rim
assert $bowl.volume > $floor.volume + $rim.volume # the ridge is really there
print "bowl" $bowl.bbox
print "spiral turns" $turns
$bowl