예제 #1
0
def assembly():
    return sp.union()(
        sp.color('red')(spu.left(100.)(wheel.volume())),
        sp.color('green')(left_stub_axle.volume()),
        sp.color('blue')(spu.left(58.)(sp.rotate(
            (0., -90., 0.))(wheel_bushing.volume()))),
    )
예제 #2
0
def assembly():
    outer_bars = sp.rotate([-90, 0, 0])([
        spu.left(d)(box_section.volume(length=outer_length,
                                       center=False,
                                       color='red')) for d in [-outer, outer]
    ])

    inner_bars = sp.rotate([-90, 0, 0])([
        spu.left(d)(box_section.volume(length=inner_length,
                                       center=False,
                                       color='green'))
        for d in [-inner, inner]
    ])

    front_bumper = spu.forward(inner_length +
                               (box_section.default_size[0] / 2.))(sp.rotate(
                                   [0, 90, 0])(box_section.volume(
                                       length=inner * 2. +
                                       box_section.default_size[0],
                                       center=True,
                                       color='blue')))

    mid_bars = spu.forward(outer_length + box_section.default_size[0] / 2.)(
        sp.rotate([0, 90, 0])(
            box_section.volume(length=inner * 2. - box_section.default_size[0],
                               center=True,
                               color='cyan'),
            [
                sp.rotate([0, a, 0])(
                    spu.up(inner + box_section.default_size[0] / 2.)(
                        box_section.volume(
                            length=outer - inner, center=False, color='cyan')))
                for a in [0, 180]
            ],
        ), )

    rear_bar = spu.back(box_section.default_size[0] / 2.)(sp.rotate([
        0, 90, 0
    ])(box_section.volume(length=outer * 2. + box_section.default_size[0],
                          center=True,
                          color='magenta')))

    bearings = spu.forward(rear_axle_position)(sp.rotate([180, 0, 0])([
        sp.translate([x, 0, box_section.default_size[0] / 2.
                      ])(sp.color('orange')(rear_axle_bearing.volume()))
        for x in [-outer, outer]
    ]))

    front_axle_and_wheels = sp.color('gray')(sp.translate(
        (0, 1150, -box_section.default_size[0]))(front_axle.assembly()))

    return sp.union()(
        outer_bars,
        inner_bars,
        front_bumper,
        mid_bars,
        rear_bar,
        bearings,
        front_axle_and_wheels,
    )
 def position_mounting_points(part):
     x_offset = wall_width / 2 - wall_thickness
     y_offset = wall_length / 2 - wall_thickness
     return (left(x_offset)(forward(y_offset)(part)) +
             right(x_offset)(forward(y_offset)(part)) +
             left(x_offset)(back(y_offset)(part)) +
             right(x_offset)(back(y_offset)(part)) +
             right(split_offset - wall_thickness)(forward(y_offset)(part)) +
             right(split_offset + wall_thickness)(forward(y_offset)(part)) +
             right(split_offset - wall_thickness)(back(y_offset)(part)) +
             right(split_offset + wall_thickness)(back(y_offset)(part)))
예제 #4
0
 def front_wall(self):
     door_opening = left(self.door_opening_offset)(up(
         self.door_opening_threshold)(grounded_cube([
             self.door_opening_width, 2 * self.wall_thickness,
             self.door_opening_height
         ])))
     single_door = self.door()
     reverse_door = mirror([1, 0, 0])(single_door)
     single_offset = (self.door_width + self.door_crack) / 2
     right_door = left(self.door_opening_offset -
                       single_offset)(single_door)
     left_door = left(self.door_opening_offset +
                      single_offset)(reverse_door)
     return forward(self.wall_y_offset)(self.main_wall() - door_opening +
                                        right_door + left_door)
예제 #5
0
def final():
    return down(1)(block()) \
        - left(y / 2)( \
            right(21)(down(5.5)(usbconn())) \
            + right(4)(down(2)(button())) \
            + right(14)(arduino()) \
        )
def key_grid_tester(
    length_units,
    width_units,
    wall_height=default_wall_height,
    margin_length=0,
    margin_width=0,
):
    x_grid_size = mount_width + switch_spacing
    y_grid_size = mount_length + switch_spacing

    case = key_grid_tester_walls(
        length_units, width_units, wall_height, margin_length, margin_width
    ) + up(wall_height - plate_thickness)(
        right(x_grid_size * (width_units - 1) / 2)(
            back(y_grid_size * (length_units - 1) / 2)(
                *[
                    left(x_grid_size * x_units)(
                        forward(y_grid_size * y_units)(spaced_switch_plate())
                    )
                    for y_units in range(length_units)
                    for x_units in range(width_units)
                ]
            )
        )
    )

    return case
예제 #7
0
 def tool_holes(self):
     offset = TOOL_HOLE_DISPLACEMENT / 2
     hole = cylinder(r=TOOL_HOLE_DIAMETER / 2 + self.inflation,
                     h=2 * PLATE_THICKNESS,
                     center=True,
                     segments=32)
     return left(offset)(hole) + right(offset)(hole)
예제 #8
0
파일: pegs.py 프로젝트: orwonthe/print3d
def slot_peg_with_catch(
        diameter=DEFAULT_PEG_DIAMETER,
        thickness=DEFAULT_HOLDER_THICKNESS,
        clearance=DEFAULT_CLEARANCE,
        overreach=None,
        slot_width=None,
        slot_clearance=DEFAULT_CONTAINER_CLEARANCE
):
    if overreach is None:
        overreach = diameter
    peg = solid_peg(diameter=diameter, thickness=thickness, clearance=clearance, overreach=overreach)
    if slot_width is None:
        slot_width = 0.35 * min(diameter, overreach)
    hole_height = clearance + overreach
    hole_displacement = slot_clearance + thickness + 0.5 * hole_height
    round_hole_bottom = back(0.5 * hole_height)(
        cylinder(r=0.5 * slot_width, h=2 * diameter, center=True, segments=16))
    hole_cutout = cube([slot_width, hole_height, 2 * diameter], center=True)
    hole = up(hole_displacement)(
        rotate([90.0, 0.0, 0.0])(
            hole_cutout + round_hole_bottom))
    catch_height = clearance + thickness + 0.5 * diameter
    catch_offset = 0.5 * diameter
    unscaled_catch = up(catch_height)(
        rotate([90, 0, 0])(
            cylinder(r=0.5 * diameter, h=0.5 * slot_width, center=True, segments=4)))
    catch = scale([0.4, 1.0, 1.0])(unscaled_catch)
    return peg \
           + left(catch_offset)(catch) \
           + right(catch_offset)(catch) \
           - hole
예제 #9
0
def slots():
    slot = single_slot()

    return left(SLOT_OFFSET_X)(back(SLOT_OFFSET_Y)(union()([
        right(ix * SLOT_SPACING_X)(forward(iy * SLOT_SPACING_Y)(slot))
        for ix in range(SLOT_COUNT_X) for iy in range(SLOT_COUNT_Y)
    ])))
예제 #10
0
def cable_holes(cube_size):
    single_cable_hole = cylinder(r=LED_LEAD_DIAMETER / 2,
                                 h=2 * cube_size,
                                 center=True,
                                 segments=16)
    return left(0.4 * inches)(
        [right(index * 0.2 * inches)(single_cable_hole) for index in range(5)])
예제 #11
0
def assembly():
    axle = sp.color('red')(
        round_bar.volume(
            diameter=axle_diameter, length=axle_length, center=True
        )
    )

    sprocket_assy = spu.up(sprocket_pos)(
        sp.color('green')(sp.rotate((180, 0, 0))(drive_sprocket.assembly()))
    )

    brake_disc_assy = spu.down(brake_disc_pos)(
        sp.color('blue')(brake_disc.assembly())
    )

    wheels = [
        sp.rotate([0, a, 0])(
            spu.left(wheel_centre_distance / 2.)(
                sp.color('cyan')(wheel.assembly())
            )
        ) for a in [0, 180]
    ]

    return sp.union()(
        sp.rotate([0, 90,
                   0])(sp.union()(
                       axle,
                       sprocket_assy,
                       brake_disc_assy,
                   )),
        wheels,
    )
예제 #12
0
파일: door.py 프로젝트: orwonthe/print3d
def box():
    floor = down(BIG)(grounded_cube([BIG, BIG, BIG]))
    side = forward(HALF_LENGTH)(grounded_cube([THICKNESS, LENGTH, HEIGHT]))
    left_side = left(SIDE_OFFSET)(side)
    right_side = right(SIDE_OFFSET)(side)
    rear = forward(THICKNESS / 2)(grounded_cube([WIDTH, THICKNESS, HEIGHT]))
    door = bottom() + left_side + right_side + rear
    return tilt_back(door) - floor
예제 #13
0
def led_mount_holes(cube_size):
    one_hole = cylinder(r=LED_LEAD_DIAMETER / 2,
                        h=cube_size,
                        center=True,
                        segments=16)
    hole_displacement = 0.05 * inches
    return left(hole_displacement)(one_hole) + right(hole_displacement)(
        one_hole)
예제 #14
0
def assembly():
    x_bars = sp.rotate([0, 90, 0])(
        [
            spu.back(d)(
                box_section.volume(
                    length=outer * 2. + box_section.default_size[0],
                    center=True
                )
            ) for d in split_centers(seat_depth)
        ]
    )

    y_bars = sp.rotate([90, 0, 0])(
        [
            spu.left(d)(
                box_section.volume(
                    length=seat_depth - box_section.default_size[0],
                    center=True
                )
            ) for d in [-outer, outer]
        ]
    )

    _mount_bar_centres = (mount_bar_centres - box_section.default_size[0]) / 2.
    mount_bars = spu.up(box_section.default_size[0])(
        sp.rotate([90, 0, 0])(
            [
                spu.left(d)(
                    box_section.volume(
                        length=seat_depth + box_section.default_size[0],
                        center=True
                    )
                ) for d in [-_mount_bar_centres, _mount_bar_centres]
            ]
        )
    )

    frame = sp.union()(
        sp.color('red')(x_bars),
        sp.color('green')(y_bars),
        sp.color('blue')(mount_bars),
    )

    return frame
예제 #15
0
 def single_groove_cut(self, ratio):
     angle = -math.degrees(TAB_ANGLE) * ratio
     side = TAB_DIAMETER + 2 * self.inflation
     offset = TAB_DIAMETER - ratio * GROOVE_THICKNESS
     vertical_offset = TOP_PLATE_THICKNESS
     single = grounded_cube(
         [side, side, GROOVE_THICKNESS + 2 * self.inflation])
     grooves = left(offset)(single) + right(offset)(single) + forward(
         offset)(single) + back(offset)(single)
     return up(vertical_offset)(rotate(angle, v=[0, 0, 1])(grooves))
예제 #16
0
 def support(self):
     thickness = self.plank_size / 2
     base_pad = down(thickness)(
         grounded_cube([self.seat_width, self.table_length, thickness])
     )
     center_bench_pad = up(self.seat_height - self.beam_size)(
         grounded_cube([thickness , self.table_length, self.beam_size])
     )
     center_offset = (self.bench_beam_width - self.beam_size)/2
     bench_pad = up(self.seat_height - self.beam_size)(
         grounded_cube([thickness , self.table_length, self.beam_size])
     )
     return (
             left(self.seat_offset_x)(base_pad) +
             right(self.seat_offset_x)(base_pad) +
             left(self.bench_beam_width/2)(center_bench_pad) +
             right(center_offset)(center_bench_pad) +
             bench_pad +
             up(self.beam_size)
     )
예제 #17
0
 def connector(self, distance_from_surface):
     return optional(self.has_connector)(
         up(distance_from_surface - 1.25)(
             forward(self.plug_offset)(
                 rotate((90, 0, 0))(
                     hull()(
                         left(4 - 1.25)(cylinder_outer(2.5 / 2, 6)),
                         right(4 - 1.25)(cylinder_outer(2.5 / 2, 6)),
                     )
                 )
             )
             + forward(self.plug_offset + self.plug_length)(
                 rotate((90, 0, 0))(
                     hull()(
                         left(4 - 1.25)(cylinder_outer(8.5 / 2, self.plug_length)),
                         right(4 - 1.25)(cylinder_outer(8.5 / 2, self.plug_length)),
                     )
                 )
             )
         )
     )
예제 #18
0
def board_cylinder():
    group = cube()  # FIXME: look into empties/children
    for x in [-m.rpi_hole_x_dist / 2, m.rpi_hole_x_dist / 2]:
        for y in [-m.rpi_hole_y_dist / 2, m.rpi_hole_y_dist / 2]:
            c = cylinder(r=m.rpi_cylinder / 2, h=5)

            c2 = up(m.rpi_board_height)(
                cylinder(r=m.rpi_hole / 2 - m.diameter_margin, h=m.rpi_board_height)
            )
            c = c + c2

            group += forward(y)(left(x)(c))

    return group
예제 #19
0
def button_switch_mount_holes(cube_size, add_cleat=True):
    single_lead_cylinder = cylinder(r=SWITCH_LEAD_DIAMETER / 2,
                                    h=cube_size,
                                    center=True,
                                    segments=16)
    hole_displacement = 0.075 * inches
    lead_hole_pair = left(hole_displacement)(single_lead_cylinder) + right(
        hole_displacement)(single_lead_cylinder)
    lead_hole_pair = rotate(-45)(lead_hole_pair)
    button_holes = [
        right(BUTTON_HOLE_X_OFFSETS[index])(forward(
            BUTTON_HOLE_Y_OFFSETS[index])(lead_hole_pair))
        for index in range(len(BUTTON_HOLE_Y_OFFSETS))
    ]
    return union()(button_holes)
예제 #20
0
    def mounting_posts(self, distance_from_surface):
        positioning_post_height = distance_from_surface + self.board_thickness + 3
        positioning_post = forward(1)(
            up(positioning_post_height / 2)(
                cube((4, 4, positioning_post_height), center=True)
            )
        )

        return (
            back(self.board_length + m2_shaft_radius + FUDGE)(
                mount_post_m2(distance_from_surface)
            )
            + left(7)(positioning_post)
            + right(7)(positioning_post)
        )
def switch_plate():
    top_wall = forward((1.5 + keyswitch_length) / 2)(
        up(plate_thickness / 2)(
            cube((keyswitch_width + 3, 1.5, plate_thickness), center=True) -
            down(notch_plate_thickness)(  # Notch for switch clips
                back(0.75)(cube((notch_width, notch_depth * 2,
                                 plate_thickness),
                                center=True)))))

    left_wall = left((1.5 + keyswitch_width) / 2)(up(plate_thickness / 2)(cube(
        (1.5, keyswitch_length + 3, plate_thickness), center=True)))

    plate_half = top_wall + left_wall

    return plate_half + mirror((0, 1, 0))(mirror((1, 0, 0))(plate_half))
예제 #22
0
def panelmountmini():
    """panelmount

    panel mount for panel Mount cable -B to Mini-B cable
    http://adafru.it/936
    """
    base = cube([40, 20, THICK_WALL], center=True)
    # screw hole
    screw_hole = cylinder(r=1.75, h=THICK_WALL * 2, center=True, segments=30)
    # create two holes + hole usb cube
    base -= hole()(left(14)(screw_hole) + right(14)(screw_hole) +
                   cube([17.5, 12, THICK_WALL * 2], center=True))
    # change orientation
    base = right(0.5 * THICK_WALL)(rotate([0, 90, 0])(rotate([0, 0,
                                                              90])(base)))
    return base
def key_grid_tester_walls(
    length_units, width_units, wall_height, margin_length, margin_width
):
    wall_length, wall_width = key_grid_tester_wall_dimensions(
        length_units, width_units, wall_height, margin_length, margin_width
    )

    top_wall = forward((wall_length - wall_thickness) / 2)(
        cube((wall_width, wall_thickness, wall_height), center=True)
    )

    left_wall = left((wall_width - wall_thickness) / 2)(
        cube((wall_thickness, wall_length, wall_height), center=True)
    )

    return up(wall_height / 2)(
        top_wall + left_wall + rotate(180)(top_wall) + rotate(180)(left_wall)
    )
예제 #24
0
def panel_set():

    front = back(NARROW_WIDTH)(rotate(180, [0, 0, 1])(rotate(90, [1, 0, 0])(
        forward(PANEL_HEIGHT / 2 + PANEL_THICKNESS)(front_panel()))))
    back_piece = forward(NARROW_WIDTH)(rotate(0, [0, 0, 1])(rotate(
        90,
        [1, 0, 0])(forward(PANEL_HEIGHT / 2 + PANEL_THICKNESS)(back_panel()))))
    side = up(1.5 * PANEL_THICKNESS)(left(WIDE_WIDTH / 2)(rotate(
        90, [0, 0, 1])(rotate(90, [1, 0, 0])(forward(PANEL_HEIGHT / 2)(
            side_panel())))))
    mount = down(PANEL_THICKNESS)(right(1.5 * inches + PANEL_THICKNESS)(rotate(
        [90, 0, -90])(mount_panel())))
    # return mount
    return front + \
           back_piece + \
           mount + \
           forward((NARROW_WIDTH - PANEL_THICKNESS) / 2)(side) + \
           back((NARROW_WIDTH - PANEL_THICKNESS) / 2)(side)
예제 #25
0
def makeCrossbar(railModel, chanNames, barThik, barWidth, nBars):
    eps = railModel.eps
    capW = railModel.CapWide
    holeD = railModel.PillarID
    specs = [channelSpecs[cname] for cname in chanNames]
    skips = [spec.ChanWide - spec.ChanHang1 - spec.ChanHang2 for spec in specs]
    barLen = sum([capW + skip for skip in skips])
    bar = cube([barWidth, barLen, barThik])
    holeAt = capW / 2
    for skip in skips:
        bar += hole()(translate([barWidth / 2,
                                 holeAt, -eps])(cylinder(d=holeD,
                                                         h=barThik + 2 * eps)))
        holeAt += capW + skip
    asm = None
    for line in range(nBars):
        asm = left(barWidth + .1)(bar + asm if asm else bar)
    return asm
예제 #26
0
def arduino():
    usbhole = up(2.5)(cube([2.5, 7.5, 9], True))
    board = up(3.75)(cube([2, x, 7.5], True))
    smds = up(1.25)(cube([1.5, 5, 2.5], True))
    dupont = up(2.5)(cube([2.5, 2.5, 5], True))
    pinhole = cube([39, 1, 1], True)

    return (usbhole) \
    + up(1)(left(3.75)(smds)) \
    + up(1)(left(2)(board)) \
    + left(4.25)(up(3.5)(forward((x/2)-1.25)(dupont))) \
    + left(4.25)(up(3.5)(back((x/2)-1.25)(dupont))) \
    + left(5)(up(2.25)(forward((x/2)-1.25)(pinhole))) \
    + left(5)(up(2.25)(back((x/2)-1.25)(pinhole)))
예제 #27
0
def groove_insert(cube_size, insert_sizing):
    assert insert_sizing in INSERT_SIZES
    is_diagonal = not insert_sizing in ['long', 'cross']
    is_cross = insert_sizing == 'cross'
    is_turn_out = 'turnout' in insert_sizing
    is_short = insert_sizing == 'short'
    is_right_handed_turnout = is_turn_out and 'right' in insert_sizing
    if is_diagonal:
        spread = 2 * GROOVE_OFFSET
        if is_short or is_turn_out:
            spread = -spread
        insert_length = (cube_size +
                         spread) * math.sqrt(0.5) + INSERT_THICKNESS
        limiting_length = insert_length * math.sqrt(0.5)
        limiter = rotate(45, [0, 1, 0])(cube(
            [limiting_length, limiting_length, limiting_length]))
        if is_turn_out:
            limiter *= up(INSERT_THICKNESS)(right(INSERT_THICKNESS)(limiter))
    else:
        insert_length = cube_size
        limiter = None

    tab = right((insert_length - INSERT_TAB_HEIGHT) / 2)(cube(
        [INSERT_TAB_LENGTH, INSERT_TAB_HEIGHT, INSERT_THICKNESS]))
    if is_cross:
        insert_length = cube_size / 2 + GROOVE_OFFSET - GROOVE_DIAMETER / 2
    insert = cube([insert_length, INSERT_HEIGHT, INSERT_THICKNESS])
    if limiter:
        insert *= limiter
    if is_cross:
        pole_length = cube_size - insert_length
        pole_offset = cube_size / 2 - GROOVE_OFFSET - GROOVE_DIAMETER / 2
        pole = right(pole_offset)(cube(
            [INSERT_THICKNESS, INSERT_HEIGHT, pole_length]))
        insert += pole
    result = insert + tab
    if is_turn_out:
        result = left(INSERT_THICKNESS)(result)
    if is_right_handed_turnout:
        result = right(insert_length - INSERT_THICKNESS)(mirror([1, 0,
                                                                 0])(result))
    return result
예제 #28
0
def box(width, height, depth, half=False, topbox=False):
    obj = sp.part()
    left = sp.cube([thickness, depth, height])
    right = left.copy()
    right = spu.right(width - thickness)(right)
    obj += left + right

    top = sp.cube([width - 2 * thickness, depth, thickness])
    top = spu.right(thickness)(top)
    if (half):
        ha = spu.up(height / 2 - thickness / 2)(top)
        obj += ha
    h = sp.cylinder(holes / 2, thickness + 10)
    top -= sp.translate([50, 50, 0])(h)
    top -= sp.translate([width - 50, 50, 0])(h)
    top -= sp.translate([width - 50, depth - 50, 0])(h)
    top -= sp.translate([50, depth - 50, 0])(h)
    bottom = top
    top = spu.up(height - thickness)(top)
    obj += top + bottom

    if (not topbox):
        sta = sp.cube([width * 3 / 4, thickness, 100])
        sta = sp.rotate([0, -45, 0])(sta)
        cutout = sp.cube([width, depth, height])
        cutout = spu.down(height / 4)(spu.left(width / 4)(cutout))
        cutout -= spu.back(5)(sp.cube(
            [width / 2 - thickness, depth + 10, height / 2 - thickness]))
        sta -= spu.back(depth / 2)(cutout)
        sta = spu.right(thickness)(sta)
        sta += spu.right(width)(sp.mirror([1, 0, 0])(sta))
        sta += sp.mirror([0, 0, 1])(sta)
        sta = spu.forward(depth - thickness)(sta)
        obj += (spu.up(height / 2)(sta) - ha)

    if (half and topbox):
        window = sp.cube(
            [width - 2 * thickness, thickness, height / 2 - 1.5 * thickness])
        window = sp.translate([thickness, 0, thickness])(window)
        obj += spu.up(height / 2 - thickness / 2)(window)
    return obj
예제 #29
0
def mount():

    padding_bars = sp.color('green')(
        sp.translate((0, 0, -d))(
            sp.rotate((0, 90, 0))(
                box_section.volume(length=mount_overlap, center=False)
            )
        )
    )

    yoke_bars = sp.color('blue')(
        [
            sp.translate((-mount_overhang, 0, z))(
                sp.rotate((0, 90, 0))(
                    box_section.volume(
                        length=mount_overhang + mount_overlap, center=False
                    )
                )
            ) for z in [d, -d * 2.]
        ]
    )

    mount_bar = sp.color('cyan')(
        sp.rotate((0, 90, 0))(box_section.volume(length=magic_1, center=False))
    )

    return spu.left(bar_length / 2.)(
        sp.rotate((0, camber, 0))(
            sp.translate((-magic_1, 0, 0.5 * d))(
                sp.union()(
                    padding_bars,
                    yoke_bars,
                    mount_bar,
                )
            )
        )
    )
예제 #30
0
 def door(self):
     forward_offset = self.door_thickness / 2 - 0.5 * nscale_inches  # slight merge into frame
     main_panel = grounded_cube(
         [self.door_width, self.door_thickness, self.door_height])
     horizontal_beam = grounded_cube(
         [self.door_width, self.door_beam_thickness, self.door_beam_width])
     vertical_beam = grounded_cube(
         [self.door_beam_width, self.door_beam_thickness, self.door_height])
     horizontal_offset = (self.door_width - self.door_beam_thickness) / 2
     vertical_offset = self.door_height - self.door_beam_thickness / 2
     diagonal_offset = (self.door_width - self.door_beam_width) / 2
     diagonal_skew = 2 * diagonal_offset / self.door_height
     diagonal_beam = multmatrix([
         [1, 0, diagonal_skew, -diagonal_offset],
         [0, 1, 0, 0],
         [0, 0, 1, 0],
     ])(vertical_beam)
     beams = horizontal_beam + up(vertical_offset)(horizontal_beam)
     beams += left(horizontal_offset)(vertical_beam)
     beams += right(horizontal_offset)(vertical_beam)
     beams += diagonal_beam
     return up(self.door_opening_threshold - 0.5 * self.door_oversize)(
         forward(forward_offset)(main_panel + forward(
             (self.door_beam_thickness - self.door_thickness) / 2)(beams)))
예제 #31
0
 def screw_holes(self):
     return [
         left(screw_hole_shift)(circle(screw_hole_radius)),
         right(screw_hole_shift + phone_width)(circle(screw_hole_radius))
     ]