def rectangularPocket(area,
                      target_depth,
                      stock_thickness,
                      safe_Z,
                      cut_per_pass,
                      bit_diameter,
                      debug=False):
    """
    area argument requires tuple (length, width).
    target_depth is an absolute Z coordinate.
    Assumes that the bit is already in the origin corner.
    Origin is (minCornerX + bit_radius, minCornerY + bit_radius)
    """
    file_text = G.set_ABS_mode()
    if debug:
        file_text += "; target_depth: " + str(target_depth) + "\n"
        file_text += "; stock_thickness: " + str(stock_thickness) + "\n"
        file_text += "; cut_per_pass: "******"\n"
        file_text += "; bit_diameter: " + str(bit_diameter) + "\n"
    file_text += G.G0_Z(stock_thickness)
    while stock_thickness > target_depth:
        stock_thickness -= cut_per_pass
        if stock_thickness < target_depth:
            stock_thickness = target_depth
        # Z-axis move by ABSOLUTE coords
        file_text += G.set_ABS_mode()
        file_text += G.G1_Z(stock_thickness)
        file_text += rectAreaByOutline(area, bit_diameter)
    file_text += G.G0_Z(safe_Z)
    return file_text
def tenon(rail, stile, offsets, tenon, mortise_bit_diameter, bit_diameter,
          safe_Z, cut_per_pass):
    """
    Assumed bit location (center) is at refs origin, at safe_Z, and machine is assumed in ABS mode.
    It is usual that mortise_bit_diameter == bit_diameter.
    Requires:
        rail = (rail_face_width, rail_thickness)
        stile = (stile_face_width, stile_thickness)
        offsets = (offset_from_face, offset_from_end)
        tenon = (length, width, depth)
    """
    # Unpacking the tuples
    rail_face_width, rail_thickness = rail
    stile_face_width, stile_thickness = stile
    offset_from_face, offset_from_end = offsets
    length, width, depth = tenon

    bit_radius = bit_diameter / 2.0
    corner_radius = mortise_bit_diameter / 2.0

    file_text = ''

    # if debug:
    #     file_text += "; target_depth: " + str(target_depth) + "\n"
    #     file_text += "; stock_thickness: " + str(stock_thickness) + "\n"
    #     file_text += "; cut_per_pass: "******"\n"
    #     file_text += "; bit_diameter: " + str(bit_diameter) + "\n"

    if (offset_from_face <= bit_diameter) and (offset_from_end <= bit_diameter) and \
        (rail_face_width - offset_from_end - length <= bit_diameter) and \
        (rail_thickness - offset_from_face - width <= bit_diameter):
        file_text += G.set_ABS_mode()
        file_text += G.G0_Z(safe_Z)
        file_text += G.set_INCR_mode()
        file_text += G.G0_X(offset_from_end + corner_radius)
        file_text += G.set_ABS_mode()
        file_text += G.G0_Z(stile_face_width)
        # Making target_depth a reference from machine's Z = 0.
        target_depth = stile_face_width - depth
        while stile_face_width > target_depth:
            stile_face_width -= cut_per_pass
            if stile_face_width < target_depth:
                stile_face_width = target_depth
            # Z-axis move by ABSOLUTE coords
            file_text += G.set_ABS_mode()
            file_text += G.G1_Z(stile_face_width)

            file_text += G.set_INCR_mode()
            file_text += roundedRectangle(length, width, mortise_bit_diameter,
                                          bit_diameter, 'inside')
    else:
        file_text = 'Not implemented yet'

    file_text += G.set_ABS_mode()
    file_text += G.G0_Z(safe_Z)
    return file_text
def bore_circle_ID(Z_safe, stock_thickness, cut_per_pass, target_depth,
                   cutter_diameter, circle_diameter):
    """use G2; from specified diameter and thickness;
       cutter compensation in function.
       Note that this method mixes ABSOLUTE with INCREMENTAL modes:
       all moves in XY are in INCR and all moves in Z are ABS."""

    assert cutter_diameter <= circle_diameter, "bit is too large for desired hole"
    assert Z_safe > stock_thickness, "Z_safe is too short for stock thickness"

    # alternate path: do a straight drill
    if cutter_diameter == circle_diameter:
        file_text = G.set_ABS_mode()
        file_text += G.G0_Z(stock_thickness)
        file_text += G.G1_Z(target_depth)
        file_text += G.set_dwell(0.5)
        file_text += G.G0_Z(Z_safe)
        return file_text

    off_set = (circle_diameter - cutter_diameter) / 2.0

    file_text = G.set_ABS_mode()
    file_text += G.G0_Z(Z_safe)
    # XY-plane move to starting point
    file_text += G.set_INCR_mode()
    file_text += G.G0_XY((-off_set, 0))
    # Z-axis move to starting point
    file_text += G.set_ABS_mode()
    file_text += G.G0_Z(stock_thickness)
    while stock_thickness > target_depth:
        stock_thickness -= cut_per_pass
        if stock_thickness < target_depth:
            stock_thickness = target_depth
        # Z-axis move
        file_text += G.set_ABS_mode()
        file_text += G.G1_Z(stock_thickness)
        # XY-plane arc move
        file_text += G.G2XY_to_INCR_FULL((0, 0), (off_set, 0))
    # At end of cut, ensures that the program reaches the very bottom
    file_text += G.set_dwell(0.5)
    # Z-axis move
    # TODO: move this to before the return to origin
    file_text += G.set_ABS_mode()
    file_text += G.G0_Z(Z_safe)
    # Then put the bit back to (0,0)
    file_text += G.set_INCR_mode()
    file_text += G.G0_XY((off_set, 0))
    return file_text
def polar_holes(Z_safe, stock_thickness, cut_per_pass, target_depth,
                cutter_diameter, circle_diameter, num_holes,
                hole_circle_diameter):
    assert num_holes > 1, "too few holes to form a circle of holes; must be at least 2"

    hole_circle_radius = hole_circle_diameter / 2.0

    file_text = G.set_ABS_mode()
    file_text += G.G0_Z(Z_safe)

    radians_increment = 2 * math.pi / num_holes

    # drill first hole
    x = math.cos(0) * hole_circle_radius
    y = math.sin(0) * hole_circle_radius
    file_text += G.set_INCR_mode()
    file_text += G.G0_XY((x, y))
    file_text += bore_circle_ID(Z_safe, stock_thickness, cut_per_pass,
                                target_depth, cutter_diameter, circle_diameter)

    # drill all other holes
    for i in xrange(1, int(num_holes)):
        x = -(math.cos((i - 1) * radians_increment) * hole_circle_radius) + (
            math.cos(i * radians_increment) * hole_circle_radius)
        y = -(math.sin((i - 1) * radians_increment) * hole_circle_radius) + (
            math.sin(i * radians_increment) * hole_circle_radius)
        file_text += G.set_INCR_mode()
        file_text += G.G0_XY((x, y))
        file_text += bore_circle_ID(Z_safe, stock_thickness, cut_per_pass,
                                    target_depth, cutter_diameter,
                                    circle_diameter)

    # return to Z_safe and origin
    file_text += G.set_ABS_mode()
    file_text += G.G0_Z(Z_safe)
    file_text += G.set_INCR_mode()
    x = -(math.cos((num_holes - 1) * radians_increment) * hole_circle_radius)
    y = -(math.sin((num_holes - 1) * radians_increment) * hole_circle_radius)
    file_text += G.G0_XY((x, y))

    return file_text
def bore_tabbed_ID(Z_safe, stock_thickness, cut_per_pass, tab_thickness,
                   cutter_diameter, circle_diameter, tab_width):
    """ Cut three tabs."""
    assert tab_thickness <= cut_per_pass, "script not set to handle cut_per_pass when it's less than tab thickness"

    off_set = (circle_diameter - cutter_diameter) / 2.0
    path_length = math.pi * off_set * 2
    # NOTE: radius = off_set, path_length is circumference of the circle that the cutter will be tracing

    assert path_length > 6.0 * (
        tab_width +
        cutter_diameter), "tabs and/or bit are too large for the circle to cut"

    # gap_radians is the gap (in radians) between the start and stop of each pair of cuts
    gap_radians = (cutter_diameter + tab_width) / off_set
    # file_text = "% cutting bore_tabbed_ID \n"
    file_text = G.set_ABS_mode()
    file_text += G.G0_Z(Z_safe)

    # XY-plane move to starting point, creating the first tab
    # at approximately 180 degrees
    file_text += G.set_INCR_mode()
    x = -math.cos(gap_radians) * off_set
    y = math.sin(gap_radians) * off_set
    file_text += G.G0_XY((x, y))
    file_text += G.set_ABS_mode()

    # 1 G2 cut after the first tab
    file_text += G.G1_Z(0)
    x = (math.cos(gap_radians) + math.cos(math.pi / 3.0)) * off_set
    y = (-math.sin(gap_radians) + math.sin((2 * math.pi) / 3.0)) * off_set
    i = math.cos(gap_radians) * off_set
    j = -math.sin(gap_radians) * off_set
    file_text += G.G2XY_to_INCR_FULL((x, y), (i, j))

    # 2 G2 create the second tab
    # at approximately 60 degrees
    file_text += G.G0_Z(tab_thickness)
    x = (math.cos((math.pi / 3.0) - gap_radians) -
         math.cos(math.pi / 3.0)) * off_set
    y = (math.sin((math.pi / 3.0) - gap_radians) -
         math.sin(math.pi / 3.0)) * off_set
    i = -math.cos(math.pi / 3.0) * off_set
    j = -math.sin(math.pi / 3.0) * off_set
    file_text += G.G2XY_to_INCR_FULL((x, y), (i, j))

    # 3 G2 cut after the second tab
    file_text += G.set_ABS_mode()
    file_text += G.G1_Z(0)
    x = 0
    y = -2 * math.sin((math.pi / 3.0) - gap_radians) * off_set
    i = -math.cos((math.pi / 3.0) - gap_radians) * off_set
    j = -math.sin((math.pi / 3.0) - gap_radians) * off_set
    file_text += G.G2XY_to_INCR_FULL((x, y), (i, j))

    # 4 G2 create the third tab
    file_text += G.G0_Z(tab_thickness)
    x = -(math.cos(
        (math.pi / 3.0) - gap_radians) - math.cos(math.pi / 3.0)) * off_set
    y = (math.sin((math.pi / 3.0) - gap_radians) -
         math.sin(math.pi / 3.0)) * off_set
    i = -math.cos((math.pi / 3.0) - gap_radians) * off_set
    j = math.sin((math.pi / 3.0) - gap_radians) * off_set
    file_text += G.G2XY_to_INCR_FULL((x, y), (i, j))

    # 5 G2 cut after the third tab
    file_text += G.set_ABS_mode()
    file_text += G.G1_Z(0)
    x = -(1 + math.cos(math.pi / 3.0)) * off_set
    y = math.sin(math.pi / 3.0) * off_set
    i = -math.cos(math.pi / 3.0) * off_set
    j = math.sin(math.pi / 3.0) * off_set
    file_text += G.G2XY_to_INCR_FULL((x, y), (i, j))

    # return to Z_safe and origin
    file_text += G.set_ABS_mode()
    file_text += G.G0_Z(Z_safe)
    file_text += G.set_INCR_mode()
    file_text += G.G0_XY((off_set, 0))

    return file_text