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 rectAreaByOutline(area, bit_diameter, debug=False):
    """ Required arguments: area (as (x=length, y=width), bit_diameter.
    Assumes that the bit is already in the origin corner at required depth of cut.
    This version just cuts progressively smaller rectangles.
    For fastest progress, orient the largest area dimension with the fastest machine axis.
    """
    length, width = area
    if bit_diameter > length or bit_diameter > width:
        raise ValueError("Bit is too large to cut specified area")
    # magic number 0.5 is the pass-to-pass overlap... not sure that I'll enforce it
    min_overlap = 0.5
    min_passes = min(math.ceil(length / bit_diameter),
                     math.ceil(width / bit_diameter))
    min_passes += min_passes % 2

    x_overlap = max(min_overlap, (((min_passes * bit_diameter) - length) /
                                  (min_passes - 1)))
    y_overlap = max(min_overlap,
                    (((min_passes * bit_diameter) - width) / (min_passes - 1)))

    x_step = bit_diameter - x_overlap
    y_step = bit_diameter - y_overlap

    file_text = G.set_INCR_mode()
    current_x = 0
    current_y = 0

    if debug:
        file_text += "; length: " + str(length) + "\n"
        file_text += "; width: " + str(width) + "\n"
        file_text += "; bit_diameter: " + str(bit_diameter) + "\n"
        file_text += "; min_passes: " + str(min_passes) + "\n"
        file_text += "; x_overlap: " + str(x_overlap) + "\n"
        file_text += "; y_overlap: " + str(y_overlap) + "\n"
        file_text += "; x_step: " + str(x_step) + "\n"
        file_text += "; y_step: " + str(y_step) + "\n"

    file_text += _rectOutline(length, width, bit_diameter)
    min_passes -= 2
    while min_passes > 0:
        file_text += G.G1_XY((x_step, y_step))
        current_x += x_step
        current_y += y_step
        length -= (2 * x_step)
        width -= (2 * y_step)
        file_text += _rectOutline(length, width, bit_diameter)
        min_passes -= 2
    file_text += G.G0_XY((-current_x, -current_y))
    file_text += G.set_ABS_mode()
    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