To get around this I've started using clamping hubs. They're not too much larger than the captive nut hubs but deform uniformly so the pulley/gear run true.
The picture above shows an example gear. I've used an M3 screw in the clamping hub. It ends up fixing very securely to the 8mm shaft and has the additional advantage of not marking the shaft.
I printed the gear above after using my Python Involute Gear Script to generate the involute profile, followed by the Python Constructive Solid Geometry Library to generate the hub and 3D model. The full source of the script that I used is shown below:
import gears import pyPolyCSG as csg def make_clamp_hub( B ): thickness = 9 hub = csg.cylinder( B/2.0+6, thickness, True ) hub = hub + csg.cylinder( 4.0, B+10, True ).rotate( 90.0, 0.0, 0.0 ).translate( B/2+3, 0, 0 ) hub = hub - csg.cylinder( 2.0, 100, True ).rotate( 90.0, 0.0, 0.0 ).translate( B/2+3, 0, 0 ) hub = hub - csg.box( B/2+6, thickness, 2, True ).translate( B/2+3.5, 0, 0 ) return hub.rotate(90,0,0).translate( 0, 0, thickness/2-0.01 ) def make_gear( pressure_angle, pitch, teeth, thickness, bore ): px, py = gears.gears_make_gear( pressure_angle, teeth, pitch ) coords =  for i in range( 0, len(px) ): coords.append( ( px[i], py[i] ) ) gear = csg.extrusion( coords, thickness ) + make_clamp_hub( bore ).translate( 0, 0, thickness ) gear = gear - csg.cylinder( bore/2.0, thickness*100, True ).rotate( 90.0, 0.0, 0.0 ) return gear pressure_angle = 20.0 pitch = 0.8 N1 = 12 B1 = 8.0+0.7 T1 = 10.0 gear1 = make_gear( pressure_angle, pitch, N1, T1, B1 ) gear1.save_mesh("gear_%gdeg_P%g_%d_tooth.obj" % ( pressure_angle, pitch, N1 ))
I've found that being able to use Python is much more convenient than OpenSCAD, mostly because its possible to define (real) variables and functions/classes. As a result I've pretty much switched to using the Python CSG library from OpenSCAD.