Folding Tablet Stand
A folding desk stand for phones and small tablets that prints in one piece with a working hinge and needs no assembly. The front support swings up out of the base frame on 45 degree cone pins, and a tooth on a flexible front bar clicks into notches on the hinge hub: three stops at 50, 62 and 74 degrees with the default settings, each with a steep flank so the device's weight cannot fold it, and a deeper notch keeps it closed for carrying. A lip with a small rim holds the bottom edge of the device, sized for 12 mm including the case. Folded it is 90 x 140 x 24 mm, and open it stands 96 to 121 mm tall on a 140 mm deep base. Print it flat exactly as laid out, with no supports; PETG suits the flexing bar best, PLA also works. You can change the width, support length, device thickness, lowest stop angle, step between stops, number of stops and the hinge clearance.
# Folding Tablet Stand - print-in-place hinge with angle detents
# Two leaves printed flat in one go, folded (A lying inside B), with no supports:
# B = base / rear leg: an open frame that lies on the desk (front spring bar,
# two side rails with hinge cheeks, rear bar)
# A = front support: the panel the device leans on, with the lip that holds
# the device's bottom edge, and a toothed hub at its lower end
# The hinge axis runs along X at the front of the base. A's hub carries 45 deg
# cone pins that sit in cone sockets in B's cheeks, with moving clearance all round.
# Angle detent: a tooth on B's front spring bar drops into notches on A's hub.
# Each notch has a steep flank on the closing side, so the device's weight cannot
# fold the stand, and a 45 deg ramp on the opening side, so it clicks open easily.
# Use: lift the top of the support; it clicks through the stops - 50, 62 and 74 deg
# from the desk with the defaults (angle_low + n x angle_step). The device angle
# equals the support angle, since the device lies flat on the support face.
# Stop at the angle you want and put the device on the lip. To fold, push the
# support firmly back down - the spring bar flexes and the support snaps flat
# into the base, where a deeper notch keeps it closed.
# Axes: X = width, Y = front to back (front at y = 0), Z = up. Print as laid out.
@param 80..100 desc:"Overall width (mm)" group:"Size"
width = 90
@param 90..150 desc:"Support length from the hinge axis (mm)" group:"Size"
support_len = 115
@param 8..14 step:0.5 desc:"Device thickness including case (mm)" group:"Device"
device_t = 12
@param 45..55 desc:"Lowest stop angle from the desk (deg)" group:"Angles"
angle_low = 50
@param 11..14 desc:"Step between stops (deg)" group:"Angles"
angle_step = 12
@param 2..4 desc:"Number of stops" group:"Angles"
stops = 3
@param 0.3..0.6 step:0.05 desc:"Moving clearance (mm)" group:"Print"
clear = 0.4
# Fixed design values
plate_t = 5 # thickness of the support panel and the base frame
hub_r = 12 # hub radius; the hinge axis sits at z = hub_r
rail_w = 10 # width of each side rail / hinge cheek
rear_w = 10 # depth of the rear bar
bar_t = 2.0 # spring bar thickness (Y); it flexes 1 mm between stops
bar_h = 15 # spring bar height (Z)
tooth_h = 1.4 # tooth height (radial)
tooth_f = 0.4 # flat on the tooth tip
band_w = 16 # width (X) of the toothed band at the middle of the bar
ramp_k = 1.0 # tan(45 deg): opening flank
stop_k = 0.577 # tan(30 deg): closing flank, measured from the radial line
notch_m = 0.15 # extra room around the tooth in a stop notch
cone_r = 5 # cone pin base radius (45 deg cone)
rim = 3 # lip rim that hooks over the front of the device
# Derived layout
ax_y = bar_t + clear + hub_r # hinge axis Y
ax_z = hub_r # hinge axis Z
xa = width / 2 - rail_w # inner face of a cheek
ha = xa - clear # half width of the support
base_len = ax_y + support_len + clear + rear_w
lip_y = ax_y + hub_r + 1 # face the device's bottom edge sits on
lip_z = plate_t + device_t + 0.5 # where the rim starts (device front + 0.5)
lip_top = lip_z + rim + 1.5
def yz(pts, x0, x1) = workplane YZ | polyline pts | extrude (x1 - x0) | translate x0 0 0
def xcyl(r, x0, x1) = cylinder r (x1 - x0) | rotate 0 90 0 | translate ((x0 + x1) / 2) ax_y ax_z
def blk(x0, x1, y0, y1, z0, z1) = box (x1 - x0) (y1 - y0) (z1 - z0) | translate ((x0 + x1) / 2) ((y0 + y1) / 2) ((z0 + z1) / 2)
def about_axis(s, $deg) = s | rotate $deg 0 0 origin:(0, ax_y, ax_z)
# ---- Detent tooth (YZ profile, B frame). Tip points +Y at the axis height.
tooth_pts = [
(bar_t - 0.3, ax_z - tooth_f / 2 - (tooth_h + 0.3) * stop_k),
(bar_t + tooth_h, ax_z - tooth_f / 2),
(bar_t + tooth_h, ax_z + tooth_f / 2),
(bar_t - 0.3, ax_z + tooth_f / 2 + (tooth_h + 0.3) * ramp_k)
]
def tooth(w) = workplane YZ | polyline tooth_pts | extrude w | translate (-w / 2) 0 0
def tooth_cut(m, w) = workplane YZ | polyline tooth_pts | offset m join:"miter" | extrude w | translate (-w / 2) 0 0
# ---- B: base frame (rear leg)
s2 = sqrt(2) / 2
cheek_pts = [(0, 0), (ax_y, 0), (ax_y, 2 * hub_r), (0, 2 * hub_r)]
def cheek(x0, x1) = union [yz(cheek_pts, x0, x1), xcyl(hub_r, x0, x1)]
# Cone socket = the pin cone moved outward by clear * sqrt(2) (0.4 mm normal gap)
def socket(sx) = cone (cone_r + 1) 0 (cone_r + 1) | rotate 0 (sx * 90) 0 | translate (sx * (ha - 1 + (cone_r + 1) / 2 + clear * sqrt(2))) ax_y ax_z
B = union [
blk(-xa - 0.01, xa + 0.01, 0, bar_t, 0, bar_h), # front spring bar
tooth(band_w), # detent tooth
cheek(xa, width / 2), cheek(-width / 2, -xa), # hinge cheeks
blk(xa, width / 2, 0, base_len, 0, plate_t), # side rails
blk(-width / 2, -xa, 0, base_len, 0, plate_t),
blk(-width / 2, width / 2, base_len - rear_w, base_len, 0, plate_t) # rear bar
] | diff [socket(1), socket(-1)]
# ---- A: front support
# Hub = cylinder plus a 45 deg flat-bottomed foot so its underside needs no support
foot_pts = [
(ax_y - hub_r * s2, ax_z - hub_r * s2),
(ax_y - hub_r * s2 + (ax_z - hub_r * s2), 0),
(ax_y + hub_r * s2 - (ax_z - hub_r * s2), 0),
(ax_y + hub_r * s2, ax_z - hub_r * s2),
(ax_y, ax_z)
]
lip_pts = [
(ax_y, plate_t - 0.01), (ax_y, lip_top), (lip_y + rim, lip_top),
(lip_y + rim, lip_z + rim), (lip_y, lip_z), (lip_y, plate_t - 0.01)
]
def pin(sx) = cone (cone_r + 1) 0 (cone_r + 1) | rotate 0 (sx * 90) 0 | translate (sx * (ha - 1 + (cone_r + 1) / 2)) ax_y ax_z
stop_angles = [angle_low + i * angle_step for i in range(stops)]
notches = union [about_axis(tooth_cut(notch_m, band_w + 2 * clear), -a) for a in stop_angles]
A = union [
xcyl(hub_r, -ha, ha),
yz(foot_pts, -ha, ha),
blk(-ha, ha, ax_y, ax_y + support_len, 0, plate_t), # panel
yz(lip_pts, -ha, ha), # lip
pin(1), pin(-1)
] | diff [tooth_cut(clear, band_w + 2 * clear), notches] # fold notch (also the print gap) + stop notches
# ---- Checks
assert A.solids == 1
assert B.solids == 1
assert clear(A, B) # printed as separate parts
assert B.bbox.x ~ width
assert B.bbox.y ~ base_len
assert A.bbox.z <= 2 * hub_r + 0.01 # nothing above the hub top
assert A.bbox.min[2] >= -0.001 and B.bbox.min[2] >= -0.001
# Stops: at each stop angle the tooth sits in its notch with room to spare (clear),
# 4 deg either side it overlaps the hub, so the bar must flex (a real detent)
pitch_mm = angle_step * pi / 180 * hub_r
notch_mm = tooth_f + (tooth_h - clear) * (ramp_k + stop_k) + 2 * notch_m
assert pitch_mm > notch_mm # notches do not merge
assert clear(about_axis(A, stop_angles[0]), B)
assert clear(about_axis(A, stop_angles[stops - 1]), B)
assert clear(about_axis(A, stop_angles[0] + 4), B) == false
assert clear(about_axis(A, stop_angles[0] - 4), B) == false
assert about_axis(A, stop_angles[stops - 1]).bbox.min[2] >= -0.01 # stands on the desk
print "stop angles (deg)" stop_angles
print "notch pitch / width (mm)" pitch_mm notch_mm
print "folded size" (union [A, B]).bbox
B | color "steelblue"
A | color "orange"