def sidewall(): body = chamfer_hull(z=True, y=True)(so.cube((depth, thickness, height))) insert = so.rotate((0,90,0))(m3_heatset_insert_hole) for i in range(1,4): body -= so.translate((0,thickness/2.0,height/4.0*i))(insert) body -= so.translate((depth,thickness/2.0,height/4.0*i))(so.rotate((0,0,180))(insert)) return body
def pulley_arms(height=40, through_screw='m4', arm_width=15, arm_thickness=7.5, pully_width=10, base_width=30, base_thickness=10, spin_clearance=0.5): arm_base = so.translate((0,-arm_width/2.0,0))(so.cube((arm_thickness, arm_width, 1))) arm = so.hull()(so.translate((0,0,height-arm_width/2.0))(so.rotate((0,90,0))(so.cylinder(r=arm_width/2.0, h=arm_thickness))) + arm_base) def arms_xf_left(obj): return so.translate(((pully_width + spin_clearance)/2.0,0,0))(obj) def arms_xf_right(obj): return so.translate((-(pully_width + spin_clearance)/2.0,0,0))(so.rotate((0,0,180))(obj)) arms = arms_xf_left(arm) + arms_xf_right(arm) plate_orig = so.translate((-base_width/2.0,-base_width/2.0,-base_thickness))(so.cube((base_width, base_width, base_thickness))) plate = so.hull()(plate_orig, so.translate((0,0,base_thickness/2.0))(arms_xf_left(arm_base))) plate += so.hull()(plate_orig, so.translate((0,0,base_thickness/2.0))(arms_xf_right(arm_base))) # add mount holes spacing = arm_thickness*2.0+pully_width bolt_hole = so.rotate((0,90,0))(so.translate((0,0,-spacing))(so.cylinder(r=screw_clearance[through_screw]/2.0, h=spacing*2.0))) nut_recess = so.translate((-spacing/2.0,0,0))(so.rotate((0,90,0))(hex(screw_nut[through_screw]['width'], screw_nut[through_screw]['depth']))) bolt_hole += nut_recess head_recess = so.translate((spacing/2.0,0,0))(so.rotate((0,-90,0))(so.cylinder(r=screw_head_sink[through_screw]['diameter']/2.0, h=screw_head_sink[through_screw]['h']))) bolt_hole += head_recess arms -= so.translate((0,0,height-arm_width/2.0))(bolt_hole) # add base mount hole head_recess = so.translate((0,0,-5))(so.cylinder(r=screw_head_sink[through_screw]['diameter']/2.0, h=screw_head_sink[through_screw]['h']*base_thickness)) bolt_hole = so.translate((0,0,-base_thickness))(so.cylinder(r=screw_clearance[through_screw]/2.0, h=base_thickness*2.0)) bolt_hole += head_recess return arms + plate - bolt_hole
def stopper(screw='m4'): body = so.cube((40,20,10), center=True) nut_recess = hex(screw_nut[screw]['width'], screw_nut[screw]['depth']) bolt_hole = so.translate((0,0,-10))(so.cylinder(r=screw_clearance[screw]/2.0, h=20)) nut_slide = so.translate((0,-screw_nut[screw]['width']/2.0))(so.cube((20, screw_nut[screw]['width'], screw_nut[screw]['depth']))) nut_attachment = so.rotate((0,-90,-90))(so.translate((0,0,-screw_nut[screw]['depth']/2.0))(nut_slide + nut_recess) + bolt_hole) return body - so.rotate((0,0,180))(so.translate((0,-13,-10))(expand_for_fit(0.3)(rail_section(20)))) - so.translate((0,-5,0))(nut_attachment)
def test_hole_transform_propagation(self): # earlier versions of holes had problems where a hole # that was used a couple places wouldn't propagate correctly. # Confirm that's still happening as it's supposed to h = hole()(rotate(a=90, v=[0, 1, 0])(cylinder(2, 20, center=True))) h_vert = rotate(a=-90, v=[0, 1, 0])(h) a = cube(10, center=True) + h + h_vert expected = '\n\ndifference(){\n\tunion() {\n\t\tcube(center = true, size = 10);\n\t\trotate(a = -90, v = [0, 1, 0]) {\n\t\t}\n\t}\n\t/* Holes Below*/\n\tunion(){\n\t\trotate(a = 90, v = [0, 1, 0]) {\n\t\t\tcylinder(center = true, h = 20, r = 2);\n\t\t}\n\t\trotate(a = -90, v = [0, 1, 0]){\n\t\t\trotate(a = 90, v = [0, 1, 0]) {\n\t\t\t\tcylinder(center = true, h = 20, r = 2);\n\t\t\t}\n\t\t}\n\t} /* End Holes */ \n}' actual = scad_render(a) self.assertEqual(expected, actual)
def double_side_rail(h, bottom_thickness=10, holes=None): section = so.translate((0,5,0))(rail_section(h)) stack = section + so.rotate((0,0,180))(section) + so.translate((-8,-5,0))(so.cube((16,10,h))) if holes is not None: bolt_hole = so.rotate((0,90,0))(so.translate((0,0,-40))(so.cylinder(r=screw_clearance[holes]/2.0, h=80))) def mkhole(offset): nonlocal stack stack -= so.translate((0,0,offset))(bolt_hole) mkhole(20-bottom_thickness) mkhole(40-bottom_thickness) mkhole(h-10) return stack
def iron_holder(thickness=20, depth=40, length=45, iron_diameter=20.5, chamfer=1, iron_holder_thickness=5,arm_screw='m4', arm_mount_dist=20, gap=5, split_screw='m4', cup_diameter=30, cup_thickness=5): arm = chamfer_hull(x=True,y=True,z=[1])(so.translate((-thickness/2.0, -depth/2.0, 0))(so.cube((thickness,depth,length + cup_diameter)))) counterweight_cup = so.translate((0,depth/2.0,length-cup_thickness))(chamfer_hull(x=True,y=True)(so.rotate((90,0,0))(so.cylinder(r=cup_diameter/2.0+cup_thickness, h=depth))) - so.hole()(so.translate((0,depth/2.0-cup_thickness,0))(so.rotate((90,0,0))(so.cylinder(r=cup_diameter/2.0, h=depth))))) holder = so.translate((0,0,cup_diameter+length))(so.rotate((0,-90,0))(split_lock(iron_diameter, thickness=iron_holder_thickness, depth=depth, lip=10, chamfer=chamfer, gap=gap, screw=split_screw))) rope_tie = so.translate((0,depth/2.0+chamfer,length/2.0-iron_holder_thickness))(so.rotate((-90,90,0))(arch())) nut_recess = hex(screw_nut[arm_screw]['width'], screw_nut[arm_screw]['depth']) bolt_hole = so.cylinder(r=screw_clearance[arm_screw]/2.0, h=10) nut_slide = so.translate((0,-screw_nut[arm_screw]['width']/2.0))(so.cube((thickness, screw_nut[arm_screw]['width'], screw_nut[arm_screw]['depth']))) nut_attachment = so.translate((0,0,5))(nut_slide + nut_recess) + bolt_hole plate_install_holes = so.translate((0,0,-0.1))(carraige_plate_install_holes(diameter=4.95)) return counterweight_cup + arm + holder - so.translate((0,arm_mount_dist/2.0,0))(nut_attachment) - so.translate((0,-arm_mount_dist/2.0,0))(nut_attachment) + rope_tie - plate_install_holes
def bracket(bottom_thickness, chamfer, height, clearance, through_offsets, through_screw='m4'): body_orig = so.translate((-15,-20,0))(so.cube((30,40,height))) body = chamfer_hull(x=True,y=True,z=[1], chamfer=chamfer)(body_orig) rail_hole = so.minkowski()(so.translate((0,0,bottom_thickness))(expand_for_fit(0.3)(double_side_rail(height))), so.cube([clearance]*3)) body -= so.hole()(rail_hole) # add mount holes bolt_hole = so.rotate((0,90,0))(so.translate((0,0,-50))(so.cylinder(r=screw_clearance[through_screw]/2.0, h=200))) nut_recess = so.translate((-15.5-chamfer-100,0,0))(so.rotate((0,90,0))(hex(screw_nut[through_screw]['width'], 100+screw_nut[through_screw]['depth']))) bolt_hole += nut_recess head_recess = so.translate((15.5+chamfer,0,0))(so.rotate((0,-90,0))(so.translate((0,0,-100))(so.cylinder(r=screw_head_sink[through_screw]['diameter']/2.0, h=100+screw_head_sink[through_screw]['h'])))) bolt_hole += head_recess for off in through_offsets: body -= so.hole()(so.translate((0,0,off))(bolt_hole)) return body
def split_lock(diameter, thickness=3, depth=40, lip=10, chamfer=1, gap=2, screw='m4', shape='circle'): lip_part = so.translate((diameter/2.0,-thickness/2.0,0))(so.cube((lip,thickness,depth))) if shape == 'circle': hole = so.cylinder(r=diameter/2.0, h=depth*2) brace = so.cylinder(r=diameter/2.0+thickness, h=depth) elif shape == 'square': hole = so.rotate((0,0,45))(so.translate((-diameter/2.0,-diameter/2.0,0))(so.cube((diameter,diameter,depth*2)))) brace = so.rotate((0,0,45))(so.translate((-diameter/2.0-thickness,-diameter/2.0-thickness,0))(so.cube((2*thickness+diameter,2*thickness+diameter,depth)))) holder = so.translate((0,depth/2.0,0))(chamfer_hull(x=True,y=True)(so.rotate((90,0,0))(brace + lip_part)) - so.hole()(so.translate((0,depth/2.0,0))(so.rotate((90,0,0))(hole)))) split = so.translate((0, -depth/2.0-chamfer, -gap/2.0))(so.cube((thickness + diameter + lip,depth+chamfer*2,gap))) split_nut_recess = so.translate((0,0,chamfer))(hex(screw_nut[screw]['width'], screw_nut[screw]['depth'])) split_nut_slide = so.translate((0,-screw_nut[screw]['width']/2, chamfer))(so.cube((thickness + diameter, screw_nut[screw]['width'], screw_nut[screw]['depth']))) split_bolt_hole = so.translate((0,0,-thickness*2-chamfer))(so.cylinder(r=screw_clearance[screw]/2.0, h=100)) split_head_recess = so.translate((0,0,-diameter-thickness*2.5))(so.cylinder(r=screw_head_sink[screw]['diameter']/2.0, h=diameter+thickness)) split_tensioner = so.translate(((diameter+lip)/2.0,0,thickness/2.0+chamfer))(split_nut_recess + split_bolt_hole + split_nut_slide + split_head_recess) return holder - split - so.hole()(split_tensioner)
def hex(width, h, fillet_radius = 0.1): """ width is the distance between opposing flat sides """ r = width/2.0/cos(pi/6.0) # magic so that we have the width b/w flat faces instead of corners pole = so.translate((r - fillet_radius, 0, 0))(so.cylinder(r=fillet_radius, h=h)) body = pole for i in range(1,6): body += so.rotate((0,0,60 * i))(pole) return so.hull()(body)
def sidewall_clamp(): # my wood is 19.5mm wide and 38.6mm tall body = chamfer_hull(z=True, y=True)(so.cube((20, thickness, height))) body += chamfer_hull(z=True, y=True, x=[1])(so.translate((19,0,0))(so.cube((1, thickness, height))) + so.translate((50,-thickness*0.25,0))(so.cube((10, thickness*1.5, 45)))) wood_cavity = so.translate((20,0,5))(so.cube((100,19.5,38.6))) body -= wood_cavity insert = so.rotate((0,90,0))(m3_heatset_insert_hole) for i in range(1,4): body -= so.translate((0,thickness/2.0,height/4.0*i))(insert) body -= so.translate((depth,thickness/2.0,height/4.0*i))(so.rotate((0,0,180))(insert)) nut_recess = so.translate((54,-5,15))(so.rotate((90,30,0))(hex(screw_nut['m3']['width'], screw_nut['m3']['depth']))) screw_recess = so.translate((54,22.4+5,15))(so.rotate((90,0,0))(so.cylinder(screw_head_sink['m3']['diameter']/2.0, screw_nut['m3']['depth']))) screw_hole = so.translate((54,22.4+10,15))(so.rotate((90,0,0))(so.cylinder(screw_clearance['m3']/2.0, 100))) screw_capture = nut_recess + screw_recess + screw_hole def add_screw(x,y): nonlocal body body -= so.translate((-x,0,y))(screw_capture) add_screw(0,0) add_screw(0,17) add_screw(17/2.0,17/2.0) return body
def basewall(passive=False): body = chamfer_hull(z=True, y=True, x=[-1])(so.cube((thickness, width, height))) m3_hole = so.translate((thickness + 1.0 ,0,0))(so.rotate((0,-90,0))(so.cylinder(r=screw_clearance['m3']/2.0, h=m3_support_depth) + so.translate((0,0,m3_support_depth))(so.cylinder(r=screw_head_sink['m3']['diameter']/2.0, h=thickness+2.0-m3_support_depth)))) for i in range(1,4): body -= so.translate((0,thickness/2.0,height/4.0*i))(m3_hole) body -= so.translate((0,width-thickness/2.0,height/4.0*i))(m3_hole) shaft_hole = so.cylinder(r=39.0/2+0.05, h=10) + so.cylinder(r=15.2/2+0.3, h=thickness+2) for i in [0,90,180,270]: shaft_hole += so.rotate((0,0,i))(so.translate((19.5,0,0))(so.cylinder(r=2, h=thickness+2))) shaft_hole = so.rotate((0,-90,0))(shaft_hole) servo_offset = 75 servo_vertical_offset = 8 servo_shaft_offset = 9.7 gear_spacing = 50 gear_angle = 0.75 for offset in [servo_offset - gear_spacing * cos(gear_angle), 110, 155, 200]: body -= so.translate((thickness + 1, offset, servo_vertical_offset + servo_shaft_offset + gear_spacing * sin(gear_angle)))(shaft_hole) if not passive: body -= so.translate((-1, servo_offset, servo_vertical_offset))(servo_mount()) if passive: body = so.mirror((1,0,0))(body) return body
def pipe_intersection_no_hole(): pipe_od = 12 pipe_id = 10 seg_length = 30 outer = cylinder(r=pipe_od, h=seg_length, center=True) inner = cylinder(r=pipe_id, h=seg_length + 2, center=True) pipe_a = outer - inner pipe_b = rotate(a=90, v=FORWARD_VEC)(pipe_a) # pipe_a and pipe_b are both hollow, but because # their central voids aren't explicitly holes, # the union of both pipes has unwanted internal walls return pipe_a + pipe_b
def servo_mount(): body = so.translate((0, -19.9/2, 0))(so.cube((thickness+2.0, 19.9, 40.5))) body = so.minkowski()(body, so.cube((0.5,0.5,0.5))) insert = so.rotate((0,90,0))(m3_heatset_insert_hole) for x in [-5, 5]: for y in [48.7/2.0, -48.7/2.0]: body += so.translate((0, x, y+40.5/2))(insert) # tips span 55.35, body spans 40.4 # holes are 1cm apart by 48.7mm apart # shaft is in the middle about 9.37mm off of the bottom # also don't forget the cable route body += so.translate((thickness/2.0+1,0,-0.5))(so.cube((thickness+2,7,1), center=True)) body += so.translate((thickness/2.0+1+5.5,0,-2))(so.cube((12,7,4), center=True)) return body
def insert(item, pos=[None, None, None], x=0, y=0, z=0, ex=0, size=[None, None, None], length=0, rot=[None, None, None], rotX=0, rotY=0, rotZ=0, width=0, height=0, depth=100, rad=0, rad2=0, color=opsc.colDefault, alpha=1, OOwidth=0, OOheight=0, holes=True, negative=True, name=""): returnValue = "" if pos[0] != None: x = pos[0] y = pos[1] z = pos[2] if rot[0] != None: rotX = rot[0] rotY = rot[1] rotZ = rot[2] returnValue = translate((x, y, z))(rotate( (rotX, rotY, rotZ))(OOEBInsertIf(item, pos, x, y, z, ex, size, length, rot, rotX, rotY, rotZ, width, height, depth, rad, rad2, color, alpha, OOwidth, OOheight, holes, negative, name))) return returnValue
def pipe_intersection_hole(): pipe_od = 12 pipe_id = 10 seg_length = 30 outer = cylinder(r=pipe_od, h=seg_length, center=True) inner = cylinder(r=pipe_id, h=seg_length + 2, center=True) # By declaring that the internal void of pipe_a should # explicitly remain empty, the combination of both pipes # is empty all the way through. # Any OpenSCAD / SolidPython object can be declared a hole(), # and after that will always be empty pipe_a = outer + hole()(inner) # Note that "pipe_a = outer - hole()(inner)" would work identically; # inner will always be subtracted now that it's a hole pipe_b = rotate(a=90, v=FORWARD_VEC)(pipe_a) return pipe_a + pipe_b
def arch(thickness=5, hole_width=5, hole_height=5,chamfer=1): pillar = chamfer_hull(x=True,y=True,chamfer=chamfer)(so.translate((-thickness/2.0,hole_width/2.0+chamfer,0))(so.cube((5,5, hole_height+thickness)))) cross = chamfer_hull(x=True,y=True,z=True, chamfer=chamfer)(so.translate((-thickness/2.0,-hole_width/2.0-thickness-chamfer,hole_height+chamfer))(so.cube((thickness, hole_width+2*(thickness+chamfer), thickness)))) out = pillar + so.rotate((0,0,180))(pillar) + cross return out
def counterweight(thickness=30, depth=50, length=55, cup_diameter=30, chamfer=1, cup_thickness=5,arm_screw='m4', arm_mount_dist=20, press_rod_diameter=13.06, gap=2): arm = chamfer_hull(x=True,y=True,z=[1])(so.translate((-thickness/2.0, -depth/2.0, 0))(so.cube((thickness,depth,length)))) holder = so.translate((0,depth/2.0,length-cup_thickness))(chamfer_hull(x=True,y=True)(so.rotate((90,0,0))(so.cylinder(r=cup_diameter/2.0+cup_thickness, h=depth))) - so.hole()(so.translate((0,depth/2.0-cup_thickness,0))(so.rotate((90,0,0))(so.cylinder(r=cup_diameter/2.0, h=depth))))) nut_recess = hex(screw_nut[arm_screw]['width'], screw_nut[arm_screw]['depth']) bolt_hole = so.cylinder(r=screw_clearance[arm_screw]/2.0, h=10) nut_slide = so.translate((0,-screw_nut[arm_screw]['width']/2.0))(so.cube((thickness, screw_nut[arm_screw]['width'], screw_nut[arm_screw]['depth']))) nut_attachment = so.translate((0,0,5))(nut_slide + nut_recess) + bolt_hole shaft_holder = so.translate((cup_thickness,-depth/4.0,0))(split_lock(diameter=press_rod_diameter, depth=depth/2.0, shape='square', gap=gap)) rope_tie = so.translate((0,depth/2.0+chamfer,length-cup_diameter/2.0-cup_thickness*2))(so.rotate((-90,90,0))(arch())) plate_install_holes = so.translate((0,0,-0.1))(carraige_plate_install_holes(diameter=4.95)) return arm + holder - so.translate((0,arm_mount_dist/2.0,0))(nut_attachment) - so.translate((0,-arm_mount_dist/2.0,0))(nut_attachment) + so.translate((0,0,length+cup_diameter/2.0+cup_thickness))(so.rotate((0,-90,0))(shaft_holder)) + rope_tie - plate_install_holes
def arms_xf_right(obj): return so.translate((-(pully_width + spin_clearance)/2.0,0,0))(so.rotate((0,0,180))(obj))
def main(params: Params): # face face_profile = roundrect( [LGX_FACE_WIDTH, LGX_FACE_HEIGHT + params.lgx_face_extra_height], params.face_radius, ) cutout_profile = roundrect([params.poe_face_width, params.poe_face_height], params.poe_face_radius) cutout_x = LGX_FACE_WIDTH / 2 - params.poe_face_width / 2 cutout_y = params.poe_face_lift + params.platform_thickness cutout = translate([cutout_x, cutout_y, 0])(cutout_profile) cutout_extrude = debug( down(params.lgx_rack_thickness)( linear_extrude(params.face_thickness + params.lgx_rack_thickness)(cutout))) post = circle(d=params.face_post_d) posts = post + right(params.face_post_separation)(post) posts = translate([ LGX_FACE_WIDTH / 2 - params.face_post_separation / 2, LGX_FACE_HEIGHT / 2, 0 ])(posts) face = difference()(face_profile, posts) face_extrusion = linear_extrude(height=params.face_thickness)(face) tray_depth = (params.platform_thickness + params.poe_depth + params.lgx_rack_thickness) # tray tray = linear_extrude(height=params.platform_thickness)( square(size=[LGX_PLATFORM_WIDTH, tray_depth])) poe_holder_outer_params = [ params.poe_width + params.platform_thickness * 2, tray_depth, ] poe_holder_profile = difference()( square(poe_holder_outer_params), right(params.platform_thickness)(forward(params.lgx_rack_thickness)( square([ params.poe_width, params.poe_depth, ]))), ) poe_holder_extrude = linear_extrude( height=params.poe_retainer_height)(poe_holder_profile) poe_holder = translate([ LGX_PLATFORM_WIDTH / 2 - poe_holder_outer_params[0] / 2, 0, params.platform_thickness, ])(poe_holder_extrude) tray = union()(poe_holder, tray) tray = rotate([270, 0, 0])(translate( [LGX_FACE_WIDTH / 2 - LGX_PLATFORM_WIDTH / 2, 0, 0])(tray)) # support support_start_inset = (params.platform_thickness + LGX_FACE_WIDTH / 2 - LGX_PLATFORM_WIDTH / 2) point_extent = LGX_FACE_HEIGHT - params.platform_thickness support_profile = polygon([[0, 0], [0, point_extent], [point_extent, 0]]) support = linear_extrude(height=params.platform_thickness)(support_profile) support = rotate([270, 0, 90])(support) support_left = translate([support_start_inset, 0, 0])(support) support_right = translate([ support_start_inset + LGX_PLATFORM_WIDTH - params.platform_thickness, 0, 0 ])(support) return difference()(union()(face_extrusion, tray, support_left, support_right), cutout_extrude)
def servo_gear(): gear = gears.herringbone_gear(modul=1.0, tooth_number=25, width=11, bore=8, helix_angle=35) insert_hole = so.mirror((0,0,1))(so.translate((15/2.0, 0, -11))(screw_2_56_insert_hole)) for a in [0,90,180,270]: gear -= so.rotate((0,0,a))(insert_hole) return gear
def stopper(screw='m4'): body = so.cube((40,20,10), center=True) nut_recess = hex(screw_nut[screw]['width'], screw_nut[screw]['depth']) bolt_hole = so.translate((0,0,-10))(so.cylinder(r=screw_clearance[screw]/2.0, h=20)) nut_slide = so.translate((0,-screw_nut[screw]['width']/2.0))(so.cube((20, screw_nut[screw]['width'], screw_nut[screw]['depth']))) nut_attachment = so.rotate((0,-90,-90))(so.translate((0,0,-screw_nut[screw]['depth']/2.0))(nut_slide + nut_recess) + bolt_hole) return body - so.rotate((0,0,180))(so.translate((0,-13,-10))(expand_for_fit(0.3)(rail_section(20)))) - so.translate((0,-5,0))(nut_attachment) wood_screw = countersunk_screw(5.9, 4.5, 10.8) scad = pulley() # NOTE the carriage has 4x m3 mounting holes with captive nuts centered on the edges of a rectangle approx. 34.8mm x 41.4mm scad = so.scale((25.4,25.4,25.4))(so.import_('LB-V1-CARRIAGE.stl')) scad = stopper() scad = so.rotate((90,0,0))(double_side_rail(180, holes='m4')) # orient for printing scad = base_bracket(mount_screw_hole=wood_screw) scad = so.rotate((90,0,0))(carriage_plate()) scad = so.rotate((90,0,0))(counterweight()) scad = so.rotate((90,0,0))(iron_holder()) scad = pulley_arms() scad = top_bracket() #scad = arch() #scad=split_lock(diameter=8) #scad = hex(20, 5) # TODO make a set-screw lock for the soldering iron # TODO add tie points for rope SEGMENTS = 48 s.scad_render_to_file(scad, 'parts.scad', file_header=f'$fn = {SEGMENTS};')
def rotate(self, a: float, v) -> "SolidBuilder": self._oso = rotate(a, v)(self._oso) return self