def multipart_hole(): # It's good to be able to keep holes empty, but often we want to put # things (bolts, etc.) in them. The way to do this is to declare the # object containing the hole a "part". Then, the hole will remain # empty no matter what you add to the 'part'. But if you put an object # that is NOT part of the 'part' into the hole, it will still appear. # On the left (not_part), here's what happens if we try to put an object # into an explicit hole: the object gets erased by the hole. # On the right (is_part), we mark the cube-with-hole as a "part", # and then insert the same 'bolt' cylinder into it. The entire # bolt rematins. b = cube(10, center=True) c = cylinder(r=2, h=12, center=True) # A cube with an explicit hole not_part = b - hole()(c) # Mark this cube-with-hole as a separate part from the cylinder is_part = part()(not_part.copy()) # This fits in the holes bolt = cylinder(r=1.5, h=14, center=True) + up(8)(cylinder( r=2.5, h=2.5, center=True)) # The section of the bolt inside not_part disappears. The section # of the bolt inside is_part is still there. return not_part + bolt + right(45)(is_part + bolt)
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 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 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 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 test_separate_part_hole(self): # Make two parts, a block with hole, and a cylinder that # fits inside it. Make them separate parts, meaning # holes will be defined at the level of the part_root node, # not the overall node. This allows us to preserve holes as # first class space, but then to actually fill them in with # the parts intended to fit in them. b = cube(10, center=True) c = cylinder(r=2, h=12, center=True) p1 = b - hole()(c) # Mark this cube-with-hole as a separate part from the cylinder p1 = part()(p1) # This fits in the hole. If p1 is set as a part_root, it will all appear. # If not, the portion of the cylinder inside the cube will not appear, # since it would have been removed by the hole in p1 p2 = cylinder(r=1.5, h=14, center=True) a = p1 + p2 expected = '\n\nunion() {\n\tdifference(){\n\t\tdifference() {\n\t\t\tcube(center = true, size = 10);\n\t\t}\n\t\t/* Holes Below*/\n\t\tunion(){\n\t\t\tcylinder(center = true, h = 12, r = 2);\n\t\t} /* End Holes */ \n\t}\n\tcylinder(center = true, h = 14, r = 1.5000000000);\n}' actual = scad_render(a) self.assertEqual(expected, actual)
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 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 test_explicit_hole(self): a = cube(10, center=True) + hole()(cylinder(2, 20, center=True)) expected = '\n\ndifference(){\n\tunion() {\n\t\tcube(center = true, size = 10);\n\t}\n\t/* Holes Below*/\n\tunion(){\n\t\tcylinder(center = true, h = 20, r = 2);\n\t} /* End Holes */ \n}' actual = scad_render(a) self.assertEqual(expected, actual)