Screw Jack
A small screw jack for leveling a shelf, steadying a wobbly table leg or lifting a light workpiece: a base with a printed internal thread and a threaded spindle whose scalloped hand wheel doubles as the load pad. The thread is M16-like but coarse (3 mm pitch, 45 degree flanks), so one turn of the wheel raises the pad by 3 mm and the full travel is 30 mm; the nut in the base is cut with the spindle thread grown by the clearance, so the two profiles match and the fit is the same along the whole flank. The base is 56 mm across and 49 mm tall, the pad stands 57 mm high fully down and 87 mm fully raised, and the nut stays fully engaged over its 4 turns even at the top of the travel. Both parts print without supports: the base standing on its foot and the spindle upside-down on its wheel (set part to print); the steepest overhangs are the 45 degree thread flanks and lead-in cones. Adjustable parameters: lift travel, pitch, thread radius, clearance, nut height, foot and wheel radius, number of scallops, and the spindle position shown in the assembled view.
# Screw Jack - a small lifting stand with a printed lead screw, 30 mm of lift
#
# Two parts: a base (foot flange + round column with an internal thread at
# the top) and a spindle (threaded rod with a scalloped hand wheel on top).
# The flat top of the wheel is the load pad: set the jack under a shelf,
# a wobbly table leg or a workpiece and turn the wheel from the side; one
# turn raises the pad by one pitch (3 mm), the full travel is `lift`.
# The spindle screws down until the wheel rests on the column top and up
# until `lift`; even fully raised the nut is still engaged over its whole
# height plus one extra pitch of thread below it.
#
# Thread: single start, right-handed, M16-like but coarse (pitch 3), 90 deg
# included angle (45 deg flanks, printable in any orientation), flat crest.
# The internal thread of the base is cut with the spindle thread grown
# radially by `clearance`, so the two profiles match by construction.
#
# Print orientation (part = "print"): base standing on its foot, spindle
# upside-down standing on its wheel. No supports; the deepest overhangs are
# the 45 deg thread flanks and the 45 deg lead-in cones inside the base.
# Axes: Z up, base floor on Z = 0 in the assembled view.
@param choices:["assembled", "print", "base", "spindle"] desc:"Output: the assembled jack, the print layout, or one part"
part = "assembled"
@param 0..1 step:0.05 desc:"Spindle position in the assembled view: 0 = fully down, 1 = fully raised"
raise = 0.5
@param 15..40 step:1 group:"Thread" desc:"Lift travel of the pad (mm)"
lift = 30
@param 2..4 step:0.5 group:"Thread" desc:"Thread pitch (mm); one turn = one pitch of lift"
pitch = 3.0
@param 0.2..0.8 step:0.05 group:"Thread" desc:"Radial clearance between spindle and base thread (mm)"
clearance = 0.4
@param 7..10 step:0.5 group:"Thread" desc:"Major radius of the spindle thread (mm)"
r_major = 8.0
@param 8..18 step:1 group:"Base" desc:"Height of the internal thread (nut) in the base (mm)"
nut_h = 12
@param 20..32 step:1 group:"Base" desc:"Foot flange radius (mm)"
r_foot = 28
@param 16..26 step:1 group:"Wheel" desc:"Hand wheel radius (mm)"
r_wheel = 22
@param 8..16 group:"Wheel" desc:"Number of finger scallops on the wheel"
scallops = 12
# ---- Derived dimensions ----
depth = 0.4 * pitch # thread depth, crest to root (1.2 at pitch 3)
land = pitch - 2 * depth # crest flat; gives 45 deg flanks
r_minor = r_major - depth # spindle root radius
rn_root = r_major + clearance # base thread root (groove bottom)
rn_crest = rn_root - depth # base thread crest
r_bore = r_major + 1.0 # plain bore under the nut (the spindle hangs in it)
r_body = rn_root + 6.6 # column radius (6.6 mm wall around the thread root)
floor_t = 3.0 # base floor
foot_t = 4.0 # foot flange thickness
wheel_t = 8.0 # hand wheel thickness
gap = 10 # spacing between the parts on the bed
thread_len = lift + nut_h + pitch # threaded length of the spindle
h_base = floor_t + 1 + thread_len # column top: spindle tip 1 mm off the floor when fully down
band_z = h_base - nut_h # bottom of the nut (threaded band)
z_tip = h_base - thread_len # spindle tip when fully down
# ===== Base (standing on its foot) =====
foot = cylinder r_foot foot_t | floor | faces >Z | chamfer 1.5
collar = cone (r_body + 3) r_body 3 | floor | translate 0 0 foot_t
column = cylinder r_body h_base | floor | faces >Z | chamfer 1
body = foot + collar + column
assert body.solids == 1
# cavity: plain bore up to a 45 deg cone that narrows to the thread crest, then the nut
bore = cylinder r_bore (band_z - floor_t) | floor | translate 0 0 floor_t
lead_lo = cone r_bore rn_crest (r_bore - rn_crest) | floor | translate 0 0 (band_z - (r_bore - rn_crest))
lead_hi = cone rn_crest (rn_root + 0.6) (rn_root + 0.6 - rn_crest) | floor | translate 0 0 (h_base - (rn_root + 0.6 - rn_crest))
# the cutter starts a whole number of pitches below the spindle tip so both helices are in phase
cutter = thread rn_root pitch (nut_h + 4 * pitch) land:land depth:depth center:(true,true,false)
| translate 0 0 (band_z - 2 * pitch)
base = body - bore - lead_lo - lead_hi - cutter
assert base.solids == 1
assert base.bbox.z ~ h_base
assert base.bbox.x ~ 2 * r_foot
# ===== Spindle (in its fully-down position: wheel resting on the column top) =====
rod = thread r_major pitch thread_len land:land depth:depth chamfer:(depth, 0) center:(true,true,false)
| translate 0 0 z_tip
wheel = cylinder r_wheel wheel_t | floor
| faces >Z | chamfer 1
| faces <Z | chamfer 0.8
| translate 0 0 h_base
notches = cylinder 4 (wheel_t + 2) | floor | translate 0 0 (h_base - 1) | polar scallops (r_wheel + 1.5)
spindle0 = rod + wheel - notches
assert spindle0.solids == 1
assert spindle0.bbox.z ~ (thread_len + wheel_t)
assert spindle0.bbox.x ~ 2 * r_wheel tol:2 # scallops notch the rim on the X axis
# spindle raised by z: it must also turn, one full turn per pitch
def spindle_at(z) = spindle0 | rotate 0 0 (360 * z / pitch) | translate 0 0 z
# ---- Fit checks (assembled) ----
assert clear(base, spindle_at(0))
assert clear(base, spindle_at(pitch / 2))
assert clear(base, spindle_at(lift / 2))
assert clear(base, spindle_at(lift))
# the threads really engage: shifted half a pitch without turning, or 1 mm sideways, the spindle hits the nut
assert clear(base, spindle0 | translate 0 0 (pitch / 2)) == false
assert clear(base, spindle0 | translate 1 0 0) == false
# the gap is at least clearance - 0.05 everywhere: a spindle thread grown by
# that much still clears the nut, one grown by clearance + 0.1 does not
probe_in = thread (r_major + clearance - 0.05) pitch (nut_h + 2 * pitch) land:land depth:depth center:(true,true,false) | translate 0 0 (band_z - pitch)
probe_out = thread (r_major + clearance + 0.1) pitch (nut_h + 2 * pitch) land:land depth:depth center:(true,true,false) | translate 0 0 (band_z - pitch)
assert clear(probe_in, base)
assert clear(probe_out, base) == false
# travel: the pad rises by exactly lift, and the nut stays fully engaged at the top
pad_lo = (spindle_at(0)).bbox.max[2]
pad_hi = (spindle_at(lift)).bbox.max[2]
assert (pad_hi - pad_lo) ~ lift
assert (z_tip + lift + pitch) <= band_z + 0.001
print "lift travel" (pad_hi - pad_lo) "pad height down/up" pad_lo pad_hi
print "thread: major d" (2 * r_major) "pitch" pitch "depth" depth "nut turns" (nut_h / pitch)
# ---- Views ----
asm = base + spindle_at(raise * lift)
spindle_prt = spindle0 | rotate 180 0 0 | floor | translate (r_foot + r_wheel + gap) 0 0
assert spindle_prt.bbox.min[2] ~ 0 tol:0.01
$print = base + spindle_prt
print "base" base.bbox
print "spindle (print)" spindle_prt.bbox
if part == "assembled" then asm
else if part == "print" then $print
else if part == "base" then base
else (spindle0 | rotate 180 0 0 | floor)