ln_0_2_back.translate(trans2) ln_6_26_back = ln(pt6, pt26, stroke=col_gds) ln_6_26_back.translate(trans2) ln_30_29_back = ln(pt30, pt29, stroke=col_gds) ln_30_29_back.translate(trans2) dwg.add(ln_0_2_back) dwg.add(ln_6_26_back) dwg.add(ln_30_29_back) # Draw paths # Path1 seg = Line(complex(pt10[0] * cm, pt10[1] * cm), complex(pt6[0] * cm, pt6[1] * cm)) t = seg.ilength(1.0 * cm) pt10A = pt("10A", np.real(seg.point(t)) / cm, np.imag(seg.point(t)) / cm) p = (pt2cm(pt10A), pt2cm((pt0[0], pt0[1] + 0.75)), pt2cm(pt11)) pf_10A_11 = fitpath(p, 10e-1) sp_10A_11 = pathtosvg(pf_10A_11) seg_10A_11 = parse_path(sp_10A_11) t = seg_10A_11.ilength(4.0 * cm) pt10B = pt("10B", pt10A[0] - abs(pt10A[0] - np.real(seg_10A_11.point(t)) / cm), (np.imag(seg_10A_11.point(t)) / cm)) #t = seg_10A_11.ilength(1.0*cm) #pt10C = pt("10C", np.real(seg.point(t))/cm, np.imag(seg.point(t))/cm) path1 = svgwrite.path.Path('%s' % sp_10A_11, fill="none", stroke=col_sew) # (pt2ph(pt10B), # only curve 0.25, not 0.5
def scan_lines(paths, current_y=None): bbox = overall_bbox(paths) lines = [] fudge_factor = 0.01 orientation = abs(bbox[3]-bbox[2]) > abs(bbox[1]-bbox[0]) if not current_y: current_y = bbox[2] if orientation else bbox[0] max_pos = bbox[3] if orientation else bbox[1] debug_shapes = [[paths, "none", "gray"]] while current_y < max_pos: current_y += MINIMUM_STITCH_DISTANCE if orientation: left = min(bbox[0], bbox[1]) right = max(bbox[0], bbox[1]) if left < 0: left *= 1.0 + fudge_factor else: left *= 1.0 - fudge_factor if right < 0: right *= 1.0 - fudge_factor else: right *= 1.0 + fudge_factor test_line = Line(start=current_y*1j+left, end=current_y*1j+right) else: up = min(bbox[2], bbox[3]) down = max(bbox[2], bbox[3]) if up < 0: up *= 1.0 + fudge_factor else: up *= 1.0 - fudge_factor if down < 0: down *= 1.0 - fudge_factor else: down *= 1.0 + fudge_factor test_line = Line(start=current_y + up*1j, end=current_y + down *1j) squash_intersections = [] for path in paths: if path.start == path.end: continue intersections = path.intersect(test_line) if len(intersections) > 0: squash_intersections += [test_line.point(p[1]) for p in intersections] if len(squash_intersections) == 0: continue intersections = sorted(squash_intersections, key=lambda x: abs(x-test_line.start)) if len(squash_intersections) < 2: continue debug_shapes.append([test_line, "none", "black"]) for i in range(0, 2*int(len(intersections)/2), 2): def format_center(ind): return (intersections[ind].real, intersections[ind].imag) debug_shapes.append([Circle(center=format_center(i), r=1, fill="red")]) debug_shapes.append([Circle(center=format_center(i+1), r=1, fill="blue")]) line = Line(start=intersections[i], end=intersections[i+1]) debug_shapes.append([line, "none", "green"]) if line.length() > MAXIMUM_STITCH: num_segments = ceil(line.length() / MAXIMUM_STITCH) for seg_i in range(int(num_segments)): lines.append(Line(start=line.point(seg_i/num_segments), end=line.point((seg_i+1)/num_segments))) else: lines.append(line) write_debug("fillscan", debug_shapes) return lines
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 generate_sorted_transects(ring_list, center, angles2use=None): from options4rings import basic_output_on, N_transects from misc4rings import transect_from_angle, normalLineAt_t_toInnerSeg_intersects_withOuter from andysSVGpathTools import pathlistXlineIntersections from andysmod import Timer, format_time from svgpathtools import Line from random import uniform from time import time as current_time from operator import itemgetter tmp = sorted(enumerate(ring_list), key = lambda tup: tup[1].sort_index) ring_sorting, sorted_ring_list = zip(*tmp) unsorted_index = lambda idx: ring_sorting[idx] #Find transects tr_gen_start_time = current_time() data = [] data_indices = [] angles = [] for dummy_index in range(N_transects): #estimate time remaining if dummy_index != 0: total_elapsed_time = current_time() - tr_gen_start_time estimated_time_remaining = (N_transects - dummy_index)*total_elapsed_time/dummy_index timer_str = 'Transect %s of %s || Est. Remaining Time = %s || Elapsed Time = %s'%(dummy_index+1,N_transects,format_time(estimated_time_remaining),format_time(total_elapsed_time)) overwrite_progress = True else: timer_str = 'transect %s of %s'%(dummy_index+1,N_transects) overwrite_progress = False print('') #generate current transect with Timer(timer_str,overwrite=overwrite_progress): # sorted_closed_rings = (r for r in sorted_ring_list if r.isClosed()) if angles2use: test_angle = angles2use[dummy_index] else: test_angle = uniform(0,1) # test_angle = 0.408 angles.append(test_angle) transect = [center] transect_rings = ['core'] # find first (innermost) closed ring # next_closed_ring = sorted_closed_rings.next() next_closed_ring = next(r for r in sorted_ring_list if r.isClosed()) next_closed_ring_sidx = next_closed_ring.sort_index #Find first transect segment (from core/center) # Start by finding line that leaves center at angle and goes to # the first closed ring nl2bdry, seg_outer, t_outer = transect_from_angle(test_angle, center, next_closed_ring.path, 'debug') # Make normal line a little longer end2use = nl2bdry.start + 1.5*(nl2bdry.end - nl2bdry.start) nl2bdry = Line(nl2bdry.start, end2use) pot_paths = [r.path for r in sorted_ring_list[0:next_closed_ring_sidx + 1]] #Note: intersections returned as (tl, path_index, seg, tp) pot_path_inters = pathlistXlineIntersections(nl2bdry, pot_paths) tl, path_index, seg, tp = min(pot_path_inters, key=itemgetter(0)) #updates transect.append(nl2bdry.point(tl)) transect_rings.append(unsorted_index(path_index)) cur_pos_si = path_index next_closed_ring = next(r for r in sorted_ring_list if (r.sort_index > cur_pos_si and r.isClosed())) # next_closed_ring = sorted_closed_rings.next() next_closed_ring_sidx = next_closed_ring.sort_index #now for the rest of the transects num_rings_checked = 0 while (cur_pos_si < len(ring_list) - 1 and num_rings_checked < len(ring_list)): # < is correct, already did first num_rings_checked += 1 inner_t = tp inner_seg = seg # Find outwards normal line from current position to the next # closed ring nl2bdry, seg_outer, t_outer = normalLineAt_t_toInnerSeg_intersects_withOuter(inner_t, inner_seg, next_closed_ring.path, center, 'debug') # Make the normal line a bit longer to avoid numerical error end2use = nl2bdry.start + 1.5*(nl2bdry.end - nl2bdry.start) nl2bdry = Line(nl2bdry.start, end2use) pot_paths = [r.path for r in sorted_ring_list[cur_pos_si+1:next_closed_ring_sidx+1]] tl, path_index, seg, tp = min(pathlistXlineIntersections(nl2bdry,pot_paths), key=itemgetter(0)) #updates transect.append(nl2bdry.point(tl)) cur_pos_si += path_index + 1 transect_rings.append(unsorted_index(cur_pos_si)) if cur_pos_si < len(ring_list)-1: # next_closed_ring = sorted_closed_rings.next() next_closed_ring = next(r for r in sorted_ring_list if (r.sort_index > cur_pos_si and r.isClosed())) next_closed_ring_sidx = next_closed_ring.sort_index data.append(transect) data_indices.append(transect_rings) return data, data_indices, angles
def generate_unsorted_transects(ring_list, center): from options4rings import basic_output_on, warnings_output_on, N_transects, unsorted_transect_debug_output_folder, unsorted_transect_debug_on, colordict from misc4rings import transect_from_angle, normalLineAt_t_toInnerSeg_intersects_withOuter from andysSVGpathTools import pathlistXlineIntersections from andysmod import Timer import operator from random import uniform #Find outer boundary ring for r in ring_list: if r.color == colordict['boundary']: boundary_ring = r break else: warnings_output_on.dprint("[Warning:] Having trouble finding outer boundary - it should be color %s. Will now search for a ring of a similar color and if one is found, will use that.\n"%colordict['boundary']) from misc4rings import closestColor for r in ring_list: if colordict['boundary'] == closestColor(r.color,colordict): boundary_ring = r basic_output_on.dprint("Found a ring of color %s, using that one."%r.color) break else: warnings_output_on.dprint("[Warning:] Outer boundary could not be found by color (or similar color). This is possibly caused by the outer boundary ring not being closed - in this case you'd be able to see a (possibly quite small) gap between it's startpoint and endpoint. Using the ring of greatest maximum radius as the boundary ring (and hoping if there is a gap none of the transects hit it).\n") keyfcn = lambda x: x.maxR boundary_ring = max(ring_list,key=keyfcn) #Find transects from time import time as current_time from andysmod import format_time tr_gen_start_time = current_time() data = [] data_indices = [] angles = [] for dummy_index in range(N_transects): #dummy_index only used to create loop #estimate time remaining if dummy_index != 0: total_elapsed_time = current_time() - tr_gen_start_time estimated_time_remaining = (N_transects - dummy_index)*total_elapsed_time/dummy_index timer_str = 'Transect %s of %s || Est. Remaining Time = %s || Elapsed Time = %s'%(dummy_index+1,N_transects,format_time(estimated_time_remaining),format_time(total_elapsed_time)) overwrite_progress = True else: timer_str = 'transect %s of %s'%(dummy_index+1,N_transects) overwrite_progress = False print('') #generate current transect with Timer(timer_str, overwrite=overwrite_progress): if unsorted_transect_debug_on: print('') test_angle = uniform(0, 1) # test_angle = 0.408 angles.append(test_angle) transect = [center] transect_rings = ['core'] unused_ring_indices = range(len(ring_list)) #used to keep track of which rings I've used and thus don't need to be checked in the future # Find first transect segment (from core/center) # normal line to use to find intersections (from center to boundary ring) nl2bdry, seg_outer, t_outer = transect_from_angle(test_angle, center, boundary_ring.path, 'debug') #make normal line a little longer nl2bdry = Line(nl2bdry.start, nl2bdry.start + 1.5*(nl2bdry.end-nl2bdry.start)) tmp = pathlistXlineIntersections(nl2bdry, [ring_list[i].path for i in unused_ring_indices]) (tl,path_index,seg,tp) = min(tmp, key=operator.itemgetter(0)) #(tl,path_index,seg,tp) transect.append(nl2bdry.point(tl)) transect_rings.append(unused_ring_indices[path_index]) del unused_ring_indices[path_index] #now for the rest of the transect num_rings_checked = 0 while (ring_list[transect_rings[-1]] != boundary_ring and num_rings_checked < len(ring_list)): # < is correct, already did first num_rings_checked += 1 inner_path = ring_list[transect_rings[-1]].path inner_t = tp inner_seg = seg # normal line to use to find intersections (from center to boundary ring) nl2bdry, seg_outer, t_outer = normalLineAt_t_toInnerSeg_intersects_withOuter(inner_t, inner_seg, boundary_ring.path, center, 'debug') # make normal line a little longer nl2bdry = Line(nl2bdry.start,nl2bdry.start + 1.5*(nl2bdry.end-nl2bdry.start)) normal_line_intersections = pathlistXlineIntersections(nl2bdry, [ring_list[i].path for i in unused_ring_indices]) try: # (tl,path_index,seg,tp) tl, path_index, seg, tp = min(normal_line_intersections, key=operator.itemgetter(0)) except ValueError: raise if unsorted_transect_debug_on: from andysmod import format001 inner_path_index = transect_rings[-1] used_ring_paths = [r.path for i,r in enumerate(ring_list) if i not in unused_ring_indices+[inner_path_index]] used_ring_colors = ['black']*len(used_ring_paths) unused_ring_paths = [ring_list[i].path for i in unused_ring_indices] unused_ring_colors = [ring_list[i].color for i in unused_ring_indices] transect_so_far = Path(*[Line(transect[i-1],transect[i]) for i in range(1,len(transect))]) paths = used_ring_paths + unused_ring_paths + [transect_so_far] +[inner_path] + [nl2bdry] colors = used_ring_colors + unused_ring_colors + ['green']+['blue'] + ['black'] nodes_so_far = transect[1:-1] potential_nodes = [nl2bdry.point(tltmp) for (tltmp,path_indextmp,segtmp,tptmp) in normal_line_intersections] nodes = nodes_so_far + potential_nodes node_colors = ['red']*len(nodes_so_far) + ['purple']*len(potential_nodes) save_name = unsorted_transect_debug_output_folder+'unsorted_transect_debug_%s.svg'%format001(3,len(transect)) disvg(paths,colors,nodes=nodes,node_colors=node_colors,center=center,filename=save_name,openInBrowser=False) print("Done with %s out of (at most) %s transect segments"%(len(transect),len(ring_list))) transect.append(nl2bdry.point(tl)) transect_rings.append(unused_ring_indices[path_index]) del unused_ring_indices[path_index] data.append(transect) data_indices.append(transect_rings) return data, data_indices, angles
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