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