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 basic_geometry(): # SolidPython code can look a lot like OpenSCAD code. It also has # some syntactic sugar built in that can make it look more pythonic. # Here are two identical pieces of geometry, one left and one right. # left_piece uses standard OpenSCAD grammar (note the commas between # block elements; OpenSCAD doesn't require this) left_piece = union()(translate((-15, 0, 0))(cube([10, 5, 3], center=True)), translate( (-10, 0, 0))(difference()(cylinder(r=5, h=15, center=True), cylinder(r=4, h=16, center=True)))) # Right piece uses a more Pythonic grammar. + (plus) is equivalent to union(), # - (minus) is equivalent to difference() and * (star) is equivalent to intersection # solid.utils also defines up(), down(), left(), right(), forward(), and back() # for common transforms. right_piece = right(15)(cube([10, 5, 3], center=True)) cyl = cylinder(r=5, h=15, center=True) - cylinder(r=4, h=16, center=True) right_piece += right(10)(cyl) return union()(left_piece, right_piece)
def doohickey(): hole_cyl = translate( (0, 0, -EPSILON))(cylinder(r=m3_rad, h=doohickey_h + 2 * EPSILON)) d = difference()(cube([30, 10, doohickey_h], center=True), translate((-10, 0, 0))(hole_cyl), hole_cyl, translate((10, 0, 0))(hole_cyl)) return d
def fibcube(): outside = cube([m.fibcube_side, m.fibcube_side, m.fibcube_height], center=False) top = translate( [ m.fibcube_wall + m.fibcube_extra / 2, m.fibcube_wall + m.fibcube_extra / 2, m.fibcube_height, ] )( cube( [ m.fibcube_plopp - m.fibcube_extra, m.fibcube_plopp - m.fibcube_extra, m.fibcube_top_margin, ], center=False, ) ) inside = translate([m.fibcube_wall, m.fibcube_wall, -m.fibcube_wall])( cube([m.fibcube_plopp, m.fibcube_plopp, m.fibcube_height], center=False) ) fc = outside + top fc = fc + hole()(inside) return fc
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 heat_set_insert(diameter, depth, excess_diameter, excess_depth, taper_angle_degrees = 8, negative_depth=0, negative_diameter=None): """ taper angle is going to specify the deflection off of zero s.t. the diameter is slightly less as you move closer to the bottom. The taper angle specifies the slope of the line off of the radial axis. The excess parameters are for a smaller hole that collects the melted material, so that it's not the same size as the portion that bonds to the insert. negative_depth creates material of exactly diameter but the hole, to create a path to move the insert during assembly and provide screwdriver access. If negative_diameter is specified, that's used instead of the insert hole diameter. """ top_radius = diameter / 2.0 if taper_angle_degrees == 0: bottom_radius = diameter / 2.0 else: bottom_radius = diameter/2.0 - tan(taper_angle_degrees * pi / 180) * depth / 2.0 negative_hole = None if negative_depth != 0: if negative_diameter is None: negative_radius = diameter / 2.0 else: negative_radius = negative_diameter / 2.0 negative_hole = so.translate((0,0,-negative_depth - 0.01))(so.cylinder(r=negative_radius, h=negative_depth + 0.01)) insert_hole = so.translate((0,0,-0.01))(so.cylinder(r1=top_radius, r2=bottom_radius, h=depth + 0.01)) excess_hole = so.translate((0,0,depth - 0.01))(so.cylinder(r=excess_diameter / 2.0, h=excess_depth + 0.01)) total = insert_hole + excess_hole if negative_hole is not None: total += negative_hole return total
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 base_bracket(mount_screw_hole, through_screw='m4', chamfer=1, clearance=0.25, base_flange_width = 20, base_flange_thickness=25, bottom_thickness=10, height=50, holes_offset=10): # bolt holes at 20-bottom_thickness and 40-bottom_thickness offsets for rail body = bracket(bottom_thickness=bottom_thickness, chamfer=chamfer, height=height, clearance=clearance, through_offsets=[20,40]) bracket_bottom = so.translate((-15-base_flange_width,-20-base_flange_width,0))(so.cube((30+base_flange_width*2,40+base_flange_width*2,base_flange_thickness))) bracket_chamfer = so.translate((-15-chamfer,-20-chamfer,-chamfer))(so.cube((30+2*chamfer,40+2*chamfer,chamfer))) body += chamfer_hull(x=True, y=True, z=[1])(bracket_bottom) for x in [1,-1]: for y in [1,-1]: body -= so.translate((x*(15+base_flange_width - holes_offset),y*(20+base_flange_width - holes_offset),base_flange_thickness+chamfer+0.001))(mount_screw_hole) return body
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 top_bracket(through_screw='m4', chamfer=1, clearance=0.25, bottom_thickness=10, height=30): body = bracket(bottom_thickness=bottom_thickness, chamfer=chamfer, height=height, clearance=clearance, through_offsets=[20]) d = screw_nut[through_screw]['depth'] + 2.5 nut_recess = so.translate((0,0,bottom_thickness-d))(hex(screw_nut[through_screw]['width'], d+10)) bolt_hole = so.cylinder(r=screw_clearance[through_screw]/2.0, h=bottom_thickness) return body - nut_recess - bolt_hole
def my_animate(_time: Optional[float] = 0) -> OpenSCADObject: # _time will range from 0 to 1, not including 1 rads = _time * 2 * 3.1416 rad = 15 c = translate((rad * cos(rads), rad * sin(rads)))(square(10)) return c
def test_split_body_planar(self): offset = [10, 10, 10] body = translate(offset)(sphere(20)) body_bb = BoundingBox([40, 40, 40], offset) actual = [] for split_dir in [RIGHT_VEC, FORWARD_VEC, UP_VEC]: actual_tuple = split_body_planar(body, body_bb, cutting_plane_normal=split_dir, cut_proportion=0.25) actual.append(actual_tuple) # Ignore the bounding box object that come back, taking only the SCAD # objects actual = [scad_render(a) for splits in actual for a in splits[::2]] expected = [ '\n\nintersection() {\n\ttranslate(v = [10, 10, 10]) {\n\t\tsphere(r = 20);\n\t}\n\ttranslate(v = [-5.0000000000, 10, 10]) {\n\t\tcube(center = true, size = [10.0000000000, 40, 40]);\n\t}\n}', '\n\nintersection() {\n\ttranslate(v = [10, 10, 10]) {\n\t\tsphere(r = 20);\n\t}\n\ttranslate(v = [15.0000000000, 10, 10]) {\n\t\tcube(center = true, size = [30.0000000000, 40, 40]);\n\t}\n}', '\n\nintersection() {\n\ttranslate(v = [10, 10, 10]) {\n\t\tsphere(r = 20);\n\t}\n\ttranslate(v = [10, -5.0000000000, 10]) {\n\t\tcube(center = true, size = [40, 10.0000000000, 40]);\n\t}\n}', '\n\nintersection() {\n\ttranslate(v = [10, 10, 10]) {\n\t\tsphere(r = 20);\n\t}\n\ttranslate(v = [10, 15.0000000000, 10]) {\n\t\tcube(center = true, size = [40, 30.0000000000, 40]);\n\t}\n}', '\n\nintersection() {\n\ttranslate(v = [10, 10, 10]) {\n\t\tsphere(r = 20);\n\t}\n\ttranslate(v = [10, 10, -5.0000000000]) {\n\t\tcube(center = true, size = [40, 40, 10.0000000000]);\n\t}\n}', '\n\nintersection() {\n\ttranslate(v = [10, 10, 10]) {\n\t\tsphere(r = 20);\n\t}\n\ttranslate(v = [10, 10, 15.0000000000]) {\n\t\tcube(center = true, size = [40, 40, 30.0000000000]);\n\t}\n}' ] self.assertEqual(actual, expected)
def my_animate(_time=0): import math # _time will range from 0 to 1, not including 1 rads = _time * 2 * math.pi rad = 15 c = translate([rad * math.cos(rads), rad * math.sin(rads)])(square(10)) return c
def __init__(self, o: OpenSCADObject = None) -> None: if o is None: self._oso = translate([0, 0, 0]) else: self._oso = o self.set_center() self.set_origin()
def fibcubes(): g = translate([0, 0, 0]) for x in range(2): g += right(x * m.fibcube_side)(fibcube()) h = translate([0, 0, 0]) for x in range(2): for y in range(2): h += forward(y * m.fibcube_side)(right(x * m.fibcube_side)(fibcube())) h = right(28)(h) i = translate([0, 0, 0]) for x in range(2): i += right(x * (m.fibcube_side + 3))(fibcube()) i = forward(13)(i) return g + h + i
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 m3_12(): bolt_height = 12 m = union()( head(), translate((0, 0, -bolt_height))( cylinder(r=m3_rad, h=bolt_height) ) ) return m
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 impl(scad): body = None for p in ps: for o in ps[p]: a = so.translate(([0]*p + [o * chamfer] + [0]*2)[:3])(scad) if body is None: body = a else: body += a return so.hull()(body)
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 m3_nut(): hx = cylinder(r=nut_rad, h=nut_height) hx.add_param('$fn', 6) # make the nut hexagonal n = difference()( hx, translate((0, 0, -EPSILON))( cylinder(r=m3_rad, h=nut_height + 2 * EPSILON) ) ) return n
def translate(self, v: Vec3) -> "SolidBuilder": self._center_x += v[0] self._center_y += v[1] self._center_z += v[2] self._origin_x += v[0] self._origin_y += v[1] self._origin_z += v[2] self._oso = translate(v)(self._oso) return self
def carraige_plate_install_holes(length = 200, angle=15, diameter=screw_head_sink['m3']['diameter']): l = diameter cut = so.cylinder(r=l/2.0, h=1) off1 = length * sin(angle*pi/180) off2 = length * cos(angle*pi/180) body = None for m in [1,-1]: for t in [True, False]: x,y = (m,0) if t: x,y=y,x p1 = so.translate((y*41.0/2,x*34.5/2,0))(cut) p2 = so.translate((y*(41.0/2+off1),x*(34.5/2+off1),off2))(cut) p = so.hull()(p1,p2) if body is None: body = p else: body += p return body
def bottom_part(): top = difference() u = union() u2 = union() top.add(u) d = difference() d.add(cylinder(r=innerR + wall + gap, h=toph)) d.add(translate((0, 0, baseH)).add(cylinder(r=innerR + gap, h=toph))) u.add(d) top.add(u2) for i in range(0, 3): a = i * 2 * pi / 3 r = innerR + gap + wall / 2 u.add( translate(((r - 0.3) * cos(a), (r - 0.3) * sin(a), toph - 6)).add(sphere(r=2.4))) u2.add( translate(((r + wall - 0.3) * cos(a), (r + wall - 0.3) * sin(a), toph - 6)).add(sphere(r=2.4))) return top
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 assembly(): return union()( doohickey(c='blue'), translate((-10, 0, doohickey_h / 2))(m3_12()), translate((0, 0, doohickey_h / 2))(m3_16()), translate((10, 0, doohickey_h / 2))(m3_12()), # Nuts translate((-10, 0, -nut_height - doohickey_h / 2))(m3_nut()), translate((0, 0, -nut_height - doohickey_h / 2))(m3_nut()), translate((10, 0, -nut_height - doohickey_h / 2))(m3_nut()), )
def top_part(): maze_path = os.path.join(os.path.dirname(__file__), 'maze7.png') depth_map = build_depth_map(maze_path) d = difference() u = union() u.add(bumpMapCylinder(depth_map, innerR, hn, 0, 255)) u.add(cylinder(r=innerR + wall + gap, h=gripH)) d.add(u) d.add(intersection().add( bumpMapCylinder(depth_map, innerR, hn + 2, wall, 0).set_modifier("")).add( translate((0, 0, baseH)).add( cylinder(r=innerR + 2 * wall, h=h * 1.1).set_modifier("")))) return d
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 board_pegs_old(): arduino_measurements = dict(holes=[[0, 0], [0, 48.2], [50.8, 36.0], [50.8, 5.1]]) rpi_measurements = dict(holes=[[0, 0], [0, 49.0], [58.0, 49.0], [58.0, 0.0]]) os = [arduino_measurements, rpi_measurements] gs = [] for o in os: for (x, y) in o["holes"]: # s = OpenSCADObject() gs.append(right(x)(forward(y)(cube()))) g2 = translate([0, 0, 0]) for g in gs: g2.add(g) g2.add_param("v", [5, 0, 0]) return g2
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