def fill_polygon(self, paths):
        rotated = 0
        fudge_factor = 0.03
        while len(paths) > 2:
            if len(paths) < 4:
                self.fill_triangle(paths, color="red")
                return
            shapes = [[Path(*paths), "none", "blue"],
                      [Path(*paths), "none", "green"]]
            write_debug("close", shapes)
            paths = remove_close_paths(paths)

            if len(paths) <= 2:
                return
            # check whether the next triangle is concave
            test_line1 = Line(start=paths[0].start, end=paths[1].end)
            test_line1 = Line(start=test_line1.point(fudge_factor),
                              end=test_line1.point(1 - fudge_factor))
            comparison_path = Path(*paths)
            if test_line1.length() == 0:
                has_intersection = True
            else:
                has_intersection = len([
                    1 for line in paths if len(line.intersect(test_line1)) > 0
                ]) > 0

            if not path1_is_contained_in_path2(
                    test_line1, comparison_path) or has_intersection:
                shapes = [[comparison_path, "none", "blue"],
                          [test_line1, "none", "black"]]
                write_debug("anim", shapes)
                # rotate the paths
                paths = paths[1:] + [paths[0]]
                rotated += 1
                if rotated >= len(paths):
                    print("failed to rotate into a concave path -> ",
                          (test_line1.start.real, test_line1.start.imag),
                          (test_line1.end.real, test_line1.end.imag),
                          [(p.start.real, p.start.imag) for p in paths])
                    return
                continue
            side = shorter_side(paths)

            test_line2 = Line(start=paths[1].start, end=paths[2].end)
            test_line2 = Line(start=test_line2.point(fudge_factor),
                              end=test_line2.point(1 - fudge_factor))
            test_line3 = Line(start=paths[-1 + side].end,
                              end=paths[(3 + side) % len(paths)].start)
            test_line3 = Line(start=test_line3.point(fudge_factor),
                              end=test_line3.point(1 - fudge_factor))

            num_intersections = []
            for path in comparison_path:
                if test_line3.length() == 0:
                    print("test line 3 is degenerate!")
                num_intersections += test_line3.intersect(path)
                num_intersections += test_line2.intersect(path)

            rect_not_concave = not path1_is_contained_in_path2(
                test_line2, comparison_path)

            # test for concavity. If concave, fill as triangle
            if is_concave(
                    paths) or len(num_intersections) > 0 or rect_not_concave:
                self.fill_triangle(paths, color="blue")
                shapes = [[Path(*paths), "none", "black"]]
                to_remove = []
                to_remove.append(paths.pop(0))
                to_remove.append(paths.pop(0))
                for shape in to_remove:
                    shapes.append([shape, "none", "blue"])
                closing_line = Line(start=paths[-1].end, end=paths[0].start)
                shapes.append([closing_line, "none", "green"])
                shapes.append([test_line1, "none", "red"])
                write_debug("rem", shapes)

            else:
                # check whether the next triangle is concave
                side, side2 = self.fill_trap(paths)
                if side:
                    paths = paths[1:] + [paths[0]]
                shapes = [[Path(*paths), "none", "black"]]
                to_remove = []
                to_remove.append(paths.pop(0))
                to_remove.append(paths.pop(0))
                to_remove.append(paths.pop(0))
                # if the trap was stitched in the vertical (perpendicular to the
                # stitches), don't remove that segment
                linecolors = ["blue", "purple", "pink"]
                for i, shape in enumerate(to_remove):
                    shapes.append([shape, "none", linecolors[i]])
                closing_line = Line(start=paths[-1].end, end=paths[0].start)
                shapes.append([closing_line, "none", "green"])
                shapes.append([test_line2, "none", "purple"])
                write_debug("rem", shapes)
                delta = closing_line.length() - (test_line3.length() /
                                                 (1.0 - 2.0 * fudge_factor))
                if abs(delta) > 1e-14:
                    print("closing line different than test!", side,
                          test_line3, closing_line)
            rotated = 0
            if paths[-1].end != paths[0].start:
                # check for intersections
                closing_line = Line(start=paths[-1].end, end=paths[0].start)
                paths.insert(0, closing_line)
            else:
                print("removed paths but they connected anyway")
def offset_curve(path, offset_distance, steps=200):
    """Takes in a Path object, `path`, and a distance,
    `offset_distance`, and outputs an piecewise-linear approximation
    of the 'parallel' offset curve."""
    nls = []
    if 'intersect' in locals():
        del intersect
    '''    
    for n, segX in enumerate(path):

        u1 = segX.unit_tangent(0.0)
        u2 = segX.unit_tangent(1.0)
        mag = 1.0*cm
        tan1 = Line(segX.point(0.0), segX.point(0.0) + mag*u1*-1).reversed()
        tan2 = Line(segX.point(1.0), segX.point(1.0) + mag*u2)
        for seg in tan1, segX, tan2:
            for k in range(steps):
                t = k / float(steps)
                offset_vector = offset_distance * seg.normal(t)
                nl = Line(seg.point(t), seg.point(t) + offset_vector)
                nls.append(nl)
    '''

    for n, segX in enumerate(path):
        if n == len(path) - 1:
            n = -1
        if 'intersect' in locals():
            segX = segX.cropped(intersect[1], 1)
            del intersect
        segXN9 = segX.normal(0.9) * offset_distance
        segXN1 = segX.normal(1.0) * offset_distance
        segYN0 = path[n + 1].normal(0.0) * offset_distance
        segYN1 = path[n + 1].normal(0.1) * offset_distance
        nlX = Line(segX.point(0.9) + segXN9, segX.point(1) + segXN1)
        nlY = Line(path[n + 1].point(0) + segYN0,
                   path[n + 1].point(0.1) + segYN1)

        #print nlX.intersect(nlY)
        if nlX.intersect(nlY):
            intersect = nlX.intersect(nlY)[0]
            seg = segX.cropped(0, intersect[0])
            for k in range(steps):
                t = k / float(steps)
                offset_vector = offset_distance * seg.normal(t)
                nl = Line(seg.point(t), seg.point(t) + offset_vector)
                nls.append(nl)
        else:
            nls_X = []
            nls_Y = []
            for k in range(50):
                t = k / float(50)
                offset_vector_X = offset_distance * segX.normal(t)
                nl = Line(segX.point(t), segX.point(t) + offset_vector_X)
                nls_X.append(nl)

                offset_vector_Y = offset_distance * path[n + 1].normal(t)
                nl = Line(path[n + 1].point(t),
                          path[n + 1].point(t) + offset_vector_Y)
                nls_Y.append(nl)

            connect_the_dots_X = [
                Line(nls_X[k].end, nls_X[k + 1].end)
                for k in range(len(nls_X) - 1)
            ]
            connect_the_dots_Y = [
                Line(nls_Y[k].end, nls_Y[k + 1].end)
                for k in range(len(nls_Y) - 1)
            ]

            for n1, x in enumerate(connect_the_dots_X):
                for m, y in enumerate(connect_the_dots_Y):
                    if x.intersect(y):
                        intersect = [n1 / 50., m / 50.]  #x.intersect(y)

            if 'intersect' in locals():
                seg = segX.cropped(0, intersect[0])
                for k in range(steps):
                    t = k / float(steps)
                    offset_vector = offset_distance * seg.normal(t)
                    nl = Line(seg.point(t), seg.point(t) + offset_vector)
                    nls.append(nl)

            else:
                u1 = segX.unit_tangent(1.0)
                u2 = path[n + 1].unit_tangent(0.0)
                mag = 3.0 * cm  # to ensure it will intersect
                tan1 = Line(segX.point(1.0), segX.point(1.0) + mag * u1)
                tan2 = Line(path[n + 1].point(0.0),
                            path[n + 1].point(0.0) + mag * u2 * -1).reversed()
                tan1N0 = tan1.normal(0) * offset_distance
                tan1N1 = tan1.normal(1.0) * offset_distance
                tan2N1 = tan2.normal(1) * offset_distance
                tan2N0 = tan2.normal(0) * offset_distance
                nlX = Line(tan1.point(0) + tan1N0, tan1.point(1) + tan1N1)
                nlY = Line(tan2.point(0) + tan2N0, tan2.point(1) + tan2N1)

                # calc angle
                a = np.array([np.real(tan1[1]), np.imag(tan1[1])])
                b = np.array([np.real(tan1[0]), np.imag(tan1[0])])
                c = np.array([np.real(tan2[0]), np.imag(tan2[0])])  # tan2[0]

                ba = a - b
                bc = c - b

                from math import acos
                from math import sqrt
                from math import pi

                def length(v):
                    return sqrt(v[0]**2 + v[1]**2)

                def dot_product(v, w):
                    return v[0] * w[0] + v[1] * w[1]

                def determinant(v, w):
                    return v[0] * w[1] - v[1] * w[0]

                def inner_angle(v, w):
                    cosx = dot_product(v, w) / (length(v) * length(w))
                    rad = acos(cosx)  # in radians
                    return rad * 180 / pi  # returns degrees

                def angle_clockwise(A, B):
                    inner = inner_angle(A, B)
                    det = determinant(A, B)
                    if det < 0:  #this is a property of the det. If the det < 0 then B is clockwise of A
                        return inner
                    else:  # if the det > 0 then A is immediately clockwise of B
                        return 360 - inner

                #cosine_angle = np.dot(bc, ba) / (np.linalg.norm(ba) * np.linalg.norm(bc))
                #angle = np.arccos(cosine_angle)
                # print angle_clockwise(bc, ba)
                if angle_clockwise(bc, ba) < 90.0:
                    seg = segX
                    for k in range(steps):
                        t = k / float(steps)
                        offset_vector = offset_distance * seg.normal(t)
                        nl = Line(seg.point(t), seg.point(t) + offset_vector)
                        nls.append(nl)

                elif len(nlX.intersect(nlY)) > 0:

                    intersectT = nlX.intersect(nlY)
                    #print intersectT
                    tan1 = tan1.cropped(0, intersectT[0][0])
                    tan2 = tan2.cropped(intersectT[0][1], 1)
                    #print intersectT

                    for seg in segX, tan1, tan2:
                        for k in range(steps):
                            t = k / float(steps)
                            offset_vector = offset_distance * seg.normal(t)
                            nl = Line(seg.point(t),
                                      seg.point(t) + offset_vector)
                            nls.append(nl)
                else:
                    seg = segX
                    for k in range(steps):
                        t = k / float(steps)
                        offset_vector = offset_distance * seg.normal(t)
                        nl = Line(seg.point(t), seg.point(t) + offset_vector)
                        nls.append(nl)

    connect_the_dots = [
        Line(nls[k].end, nls[k + 1].end) for k in range(len(nls) - 1)
    ]
    if path.isclosed():
        connect_the_dots.append(Line(nls[-1].end, nls[0].end))
    offset_path = Path(*connect_the_dots)
    return offset_path