def snap(self, tree, threshold): def process(points): for i, p in enumerate(points): best, _, dist = tree.nearest_neighbor([p.real, p.imag]) if dist < threshold: points[i] = complex(best[0], best[1]) return points path = parse_path(self['d']) newPath = Path() for seg in path: points = process([seg.start, seg.end]) if isinstance(seg, Line): newSeg = Line(*points) newPath.append(newSeg) elif isinstance(seg, CubicBezier): newSeg = CubicBezier(points[0], seg.control1, seg.control2, points[1]) newPath.append(newSeg) self['d'] = newPath.d() return self
class byA_Path(byA_FrozenClass): def __init__(self, *segments, **kw): byA_FrozenClass.__init__(self) self._segments = Path() for p in segments: assert isinstance(p, byA_Line) or isinstance(p, byA_CubicBezier) if isinstance(p, byA_Line): self.insert(-1, p) if isinstance(p, byA_CubicBezier): self.insert(-1, p) if 'closed' in kw: self._segments.closed = kw['closed'] # DEPRECATED self._freeze("byA_Path") def insert(self, index, value): assert isinstance(value, byA_Line) or isinstance( value, byA_CubicBezier) if isinstance(value, byA_Line): self._segments.insert(index, value._svgline) if isinstance(value, byA_CubicBezier): self._segments.insert(index, value._svgcubicbezier) def append(self, value): assert isinstance(value, byA_Line) or isinstance( value, byA_CubicBezier) if isinstance(value, byA_Line): self._segments.append(value._svgline) if isinstance(value, byA_CubicBezier): self._segments.append(value._cubicbezier) def toStr(self): return self._segments.d()
def remove_degenerate_segments(path): #This function removes any segment that starts and ends at the same point new_path = Path() for seg in path: if seg.start!=seg.end: new_path.append(seg) return new_path
def generateInBetweens(poseA, poseB, steps): inv = Inventory() # make pairs pairs = [] for key in ORDER: if key in poseA.inv and key in poseB.inv: partA = poseA.inv[key] partB = poseB.inv[key] if len(partA) != 1 or len(partB) != 1: print('Too many parts {0} - A: {1} B: {2}'.format( key, partA.keys(), partB.keys())) continue pairs.append((key, partA.values()[0], partB.values()[0])) # If there are 3 steps, there are 4 gaps between start and finish # |------1------2------3------| gaps = steps + 1 # process pairs for key, a, b in pairs: pathA = parse_path(a['d']) pathB = parse_path(b['d']) if len(pathA) != len(pathB): print('Unmatched segments {0} - A: {1} B: {2}'.format( key, pathA, pathB)) continue for step in range(1, gaps): newPath = Path() for i in range(len(pathA)): segA = pathA[i] segB = pathB[i] if isinstance(segA, Line): points = _deltaPoints([segA.start, segA.end], [segB.start, segB.end], step, gaps) newPath.append(Line(*points)) elif isinstance(segA, CubicBezier): points = _deltaPoints( [segA.start, segA.control1, segA.control2, segA.end], [segB.start, segB.control1, segB.control2, segB.end], step, gaps) newPath.append(CubicBezier(*points)) newPart = Part(newPath.d()) newPart['x'] = int(_delta(a['x'], b['x'], step, gaps)) newPart['y'] = int(_delta(a['y'], b['y'], step, gaps)) newPart['z'] = int(_delta(a['z'], b['z'], step, gaps)) inv.addPart(key, newPart) print(key, step, newPart) return inv
def _remove_zero_length_lines(cls, paths): new_paths = [] for path in paths: pp = list(filter(lambda x: x.start != x.end, path)) newpath = Path() for p in pp: newpath.append(p) new_paths.append(newpath) return new_paths
def findMiddleOfConnectingPath(self): #creates path starting of end of down_ladder1, go to nearest (with t> t0) up-ladder or if no up-ladder then down_ladder at end and repeat until getting to bottom of down_ladder0 maxIts = 1000 #### Tolerance traveled_path = Path() iters = 0 (irORcr_new,T_new) = self.down_ladder1 doneyet = False while iters < maxIts and not doneyet: iters =iters+ 1 # ##DEBUG sd;fjadsfljkjl; # if self.ring.path[0].start == (260.778+153.954j): # from misc4rings import dis # from svgpathtools import Line # p2d=[self.completed_path, # self.down_ladder0[0].ORring.path, # self.down_ladder1[0].ORring.path] # clrs = ['blue','green','red'] # if iters>1: # p2d.append(Path(*traveled_path)) # clrs.append('black') # lad0a = self.down_ladder0[0].ORring.path.point(self.down_ladder0[1]) # lad0b = self.ORring.path[0].start # lad0 = Line(lad0a,lad0b) # lad1a = self.ORring.path[-1].end # lad1b = self.down_ladder1[0].ORring.path.point(self.down_ladder1[1]) # lad1 = Line(lad1a,lad1b) # dis(p2d,clrs,lines=[lad0,lad1]) # print abs(lad0.start-lad0.end) # print abs(lad1.start-lad1.end) # bla=1 # ##end of DEBUG sd;fjadsfljkjl; traveled_path_part, irORcr_new, T_new = self.followPathBackwards2LadderAndUpDown(irORcr_new, T_new) for seg in traveled_path_part: traveled_path.append(seg) if irORcr_new == self: return traveled_path # if irORcr_new == self.down_ladder0[0]: # doneyet = True # irORcr_new_path = irORcr_new.ORring.path # if T_new != self.down_ladder0[1]: # for seg in reversePath(cropPath(irORcr_new_path,self.down_ladder0[1],T_new)): # traveled_path.append(seg) # break if (irORcr_new, T_new) == self.down_ladder0: return traveled_path if iters >= maxIts-1: raise Exception("findMiddleOfConnectingPath reached maxIts") return traveled_path
def displaySVGPaths_transects_old(ringList,data_transects,transect_angles,filename): #creates and saves an svf file displaying the input paths import svgwrite transectPaths = [] for tran_index in range(len(data_transects)): tran_path = Path() for seg_index in range(len(data_transects[tran_index])-1): start_pt = data_transects[tran_index][seg_index] end_pt = data_transects[tran_index][seg_index+1] tran_path.append(Line(start_pt,end_pt)) transectPaths.append(tran_path) ringPaths = [r.path for r in ringList] ringColors = [r.color for r in ringList] pathList = ringPaths+transectPaths colors = ringColors + ['black']*len(transectPaths) transect_nodes = [item for sublist in data_transects for item in sublist] #flatten data_transects transect_nodes = [(z.real,z.imag) for z in transect_nodes] center = ringList[0].center center = (center.real,center.imag) dwg = svgwrite.Drawing(filename+'_transects.svg',size=('2000px','2000px'),viewBox="0 0 2000 2000") dwg.add(dwg.rect(insert=(0, 0), size=('100%', '100%'), rx=None, ry=None, fill='white')) #add white background for i,p in enumerate(pathList): if isinstance(p,Path): ps = path2str(p) elif isinstance(p,Line) or isinstance(p,CubicBezier): ps = path2str(Path(p)) else: ps = p dwg.add(dwg.path(ps, stroke=colors[i], fill='none')) #add a purple dot whenever a transect crosses a ring for pt in transect_nodes: dwg.add(dwg.circle(pt,1, stroke='purple', fill='purple')) #add a blue dot at the core/center of the sample dwg.add(dwg.circle(center,2, stroke='blue', fill='blue')) #add text giving angle in radians/2pi (so in [0,1]) at the end of each transect for k,theta in enumerate(transect_angles): try: if len(data_transects[k])>1: paragraph = dwg.add(dwg.g(font_size=14)) n_vec = data_transects[k][-1]-data_transects[k][-2] n_vec = n_vec/abs(n_vec) text_coords = 10*n_vec + data_transects[k][-1] text_coords = (text_coords.real,text_coords.imag) paragraph.add(dwg.text('%.3f'%theta, text_coords)) else: print('Skipping degenerate transect at angle %s'%theta) except: print('Skipping problemsome transect at angle %s'%theta) dwg.save()
def flatten_beziers(svg_d): sp = parse_path(svg_d) spn = SVGPath() for seg in sp: if isinstance(seg, Line): spn.append(seg) elif isinstance(seg, CubicBezier): B = [seg.bpoints()] foo = np.dot(B, CUBIC_TO_POLY_SAMPLE) spn.extend([Line(x, y) for x, y in zip(foo[0, :-1], foo[0, 1:])]) else: raise RuntimeError(f"unsupported {seg}") return spn.d()
def replace_point(self, ptbefore, ptafter): pathstr = self._g.elements[0].tostring() pathstr = pathstr.split('"')[3] pathbefore = parse_path(pathstr) pathafter = Path() for i in pathbefore: if isinstance(i, Line): if (isclose(i.start.real, ptbefore._x) and isclose(i.start.imag, ptbefore._y)): i.start = ptafter._x + 1j * ptafter._y if (isclose(i.end.real, ptbefore._x) and isclose(i.end.imag, ptbefore._y)): i.end = ptafter._x + 1j * ptafter._y pathafter.append(i) self.add(svgwrite.path.Path(d=pathafter.d()))
def displaySVGPaths_transects(ring_list, data_transects, transect_angles, skipped_angle_indices, fn=None): if not fn: filename = opt.output_directory + ring_list[0].svgname else: filename = fn transectPaths = [] for tran_index in range(len(data_transects)): tran_path = Path() for seg_index in range(len(data_transects[tran_index]) - 1): start_pt = data_transects[tran_index][seg_index] end_pt = data_transects[tran_index][seg_index + 1] tran_path.append(Line(start_pt,end_pt)) transectPaths.append(tran_path) ringPaths = [r.path for r in ring_list] ringColors = [r.color for r in ring_list] pathList = ringPaths + transectPaths colors = ringColors + ['black']*len(transectPaths) transect_nodes = [item for sublist in data_transects for item in sublist] # flatten data_transects nodes = transect_nodes + [ring_list[0].center] node_colors = ['purple']*len(transect_nodes) + ['blue'] text = ['%.3f' % theta for idx, theta in enumerate(transect_angles) if idx not in skipped_angle_indices] text += ['skipped %.3f' % transect_angles[idx] for idx in skipped_angle_indices] text_path = [] for tr in data_transects: end = tr[-1] last_seg = Line(tr[-2], tr[-1]) u = last_seg.unit_tangent(1) text_path.append(Path(Line(end + 10*u, end + 100*u))) # handle skipped transects bdry_ring = max(ring_list, key=lambda ring: ring.maxR) bdry_length = bdry_ring.path.length() for idx in skipped_angle_indices: s = bdry_length * transect_angles[idx] T = inv_arclength(bdry_ring.path, s) u = bdry_ring.path.normal(T) end = bdry_ring.path.point(T) text_path.append(Line(end + 10*u, end + 100*u)) wsvg(pathList, colors, nodes=nodes, node_colors=node_colors, text=text, text_path=text_path, filename=filename+'_transects.svg')
def scale_path_to_bbox(path, bbox): """ Scales a path to be contained by a bounding box. Expands the path from the center of the bounding box. Does not place the path in the center of the bounding box. bbox has the same format as svgpathtools', (x1, y1, x2, y2) """ original_bbox = path.bbox() bbox_center = compute_bbox_center(bbox) # we'll be comparing each path bbox coordinate to the bbox center # it's easier done if we have a tuple with 4 elements for the center too two_centers = ( bbox_center.real, bbox_center.imag, bbox_center.real, bbox_center.imag, ) # furthest projection is the path bbox coordinate that has the most # difference with the bbox center # destination is the corresponding wanted bbox coordinate # center is the corresponding center coordinate furthest_projection, destination, center = max( zip(original_bbox, bbox, two_centers), key=lambda t: abs(t[0] - t[2])) scale = abs(center - destination) / abs(center - furthest_projection) # if we just applied the scaling factor, the path would be shifted # in a top-left to bottom-right motion so we need to translate the path # back to make the scaling effect from the bbox' center translation_constant = bbox_center * (1 - scale) new_path = Path() for node in path: node.start = node.start * scale + translation_constant node.end = node.end * scale + translation_constant if isinstance(node, CubicBezier): node.control1 = node.control1 * scale + translation_constant node.control2 = node.control2 * scale + translation_constant elif isinstance(node, QuadraticBezier): node.control = node.control * scale elif isinstance(node, Arc): node.radius = node.radius * scale new_path.append(node) return new_path
def quantize(self): path = parse_path(self['d']) newPath = Path() for seg in path: if isinstance(seg, Line): newSeg = Line( complex(round(seg.start.real), round(seg.start.imag)), complex(round(seg.end.real), round(seg.end.imag))) newPath.append(newSeg) elif isinstance(seg, CubicBezier): newSeg = CubicBezier( complex(round(seg.start.real), round(seg.start.imag)), complex(round(seg.control1.real), round(seg.control1.imag)), complex(round(seg.control2.real), round(seg.control2.imag)), complex(round(seg.end.real), round(seg.end.imag))) newPath.append(newSeg) self['d'] = newPath.d() return self
def order_paths(paths): total_length = sum([path.length() for path in paths]) samples_per_path = [ round(args.N_points * path.length() / total_length) for path in paths ] # Order the shapes in each path in paths for i, path in enumerate(paths): tmp = Path(path.pop(0)) while len(path) > 1: curr_end = tmp[-1].end next_start = np.argmin( np.abs([next_path.start - curr_end for next_path in path])) tmp.append(path.pop(next_start)) tmp.append(path[0]) paths[i] = tmp # Sample points along the paths shape = [[ path.point(i / samples_in_path) for i in range(samples_in_path) if samples_in_path > 0 ] for path, samples_in_path in zip(paths, samples_per_path)] # Order the paths tmp = shape.pop(0) while len(shape) > 0: curr_end = tmp[-1] next_start = np.argmin(np.abs([path[0] - curr_end for path in shape])) tmp.extend(shape.pop(next_start)) # Center and normalize the points shape = np.conjugate(tmp) shape -= np.mean(shape) shape /= np.max(np.abs(shape)) return shape
def glyphToPaths(g, yMul=-1): paths = [] contours = [] yOffs = -font.info.unitsPerEm # decompose components if len(g.components): font.newGlyph('__svgsync') ng = font['__svgsync'] ng.width = g.width ng.appendGlyph(g) ng.decompose() g = ng for c in g: curve = False points = c.points path = Path() currentPos = 0j controlPoints = [] for x in range(len(points)): p = points[x] # print 'p#' + str(x) + '.type = ' + repr(p.type) if p.type == 'move': currentPos = vec2(p.x, (p.y + yOffs) * yMul) elif p.type == 'offcurve': controlPoints.append(p) elif p.type == 'curve': pos = vec2(p.x, (p.y + yOffs) * yMul) if len(controlPoints) == 2: cp1, cp2 = controlPoints path.append(CubicBezier( currentPos, vec2(cp1.x, (cp1.y + yOffs) * yMul), vec2(cp2.x, (cp2.y + yOffs) * yMul), pos)) else: if len(controlPoints) != 1: raise Exception('unexpected number of control points for curve') cp = controlPoints[0] path.append(QuadraticBezier(currentPos, vec2(cp.x, (cp.y + yOffs) * yMul), pos)) currentPos = pos controlPoints = [] elif p.type == 'line': pos = vec2(p.x, (p.y + yOffs) * yMul) path.append(Line(currentPos, pos)) currentPos = pos paths.append(path) if font.has_key('__svgsync'): font.removeGlyph('__svgsync') return paths
def augment(path_nested, num): path_list = [] path = Path() for p in path_nested: for segment in p: path.append(segment) end_points_list = [] for segment in path: s = segment.bpoints()[0] e = segment.bpoints()[-1] end_points_list.append((s.real, s.imag)) end_points_list.append((e.real, e.imag)) end_points = np.array(end_points_list) hull_points = end_points[ConvexHull(end_points).vertices] idx_xmin, idx_ymin = np.argmin(hull_points, axis=0) idx_xmax, idx_ymax = np.argmax(hull_points, axis=0) x_range = 0.15 * (hull_points[idx_xmax][0] - hull_points[idx_xmin][0]) y_range = 0.15 * (hull_points[idx_ymax][1] - hull_points[idx_ymin][1]) idx_min_max = np.unique([idx_xmin, idx_ymin, idx_xmax, idx_ymax]) for _ in range(num): # global deformation p = hull_points q = hull_points.copy() for idx in idx_min_max: x, y = p[idx] q[idx] = (x + random.gauss(0, x_range), y + y_range * random.gauss(0, y_range)) path_deformed = Path() for segment in path: points = [] for v in segment.bpoints(): real, imag = moving_least_square_with_rigid_transformation( p, q, np.array([v.real, v.imag]), max(x_range, y_range)) point_xformed = complex(real, imag) points.append(point_xformed) if len(segment.bpoints()) == 2: line = Line(points[0], points[1]) path_deformed.append(line) else: cubic_bezier = CubicBezier(points[0], points[1], points[2], points[3]) path_deformed.append(cubic_bezier) path_list.append(path_deformed) return path_list
def mergePath(path): sortedPath = Path() sortedPath.append(path.pop(0)[0]) while (len(path) != 0): noneFound = True for (i, element) in enumerate(path): if (sortedPath[-1].end == element[0].start): sortedPath.append(path.pop(i)[0]) noneFound = False break if (sortedPath[-1].end == element[0].end): sortedPath.append(path.pop(i)[0].reversed()) noneFound = False break if noneFound: print("error in svg: not a closed figure") return sortedPath
class IncompleteRing(object): def __init__(self, ring): self.ring = ring self.innerCR_ring = None self.outerCR_ring = None self.completed_path = Path() self.overlap0 = False #This is related to a deprecated piece of code and must be False. self.overlap1 = False #This is related to a deprecated piece of code and must be False. self.corrected_start = None #set in case of overlap (point, seg,t) where seg is a segment in self and seg(t)=point self.corrected_end = None #set in case of overlap (point, seg,t) where seg is a segment in self and seg(t)=point self.ir_start = self.ring.point(0) self.ir_end = self.ring.point(1) self.up_ladders = [] self.down_ladder0 = None #(irORcr0,T0) ~ startpoint down-ladder on this ir and (and T-val on connecting ring it connects at - irORcr0 can be incompleteRing object or completeRing object) self.down_ladder1 = None self.transect0fails = [] #records IRs are "below" self, but failed to provide a transect to self.ir_start self.transect1fails = [] #records IRs are "below" self, but failed to provide a transect to self.ir_end self.transect0found = False self.transect1found = False self.isCore = False self.ORring = self.ring # def __repr__(self): # return '<IncompleteRing based on ring = %s>' %self.ring def __eq__(self, other): if not isinstance(other, IncompleteRing): return NotImplemented if self.ring != other.ring: return False return True def __ne__(self, other): if not isinstance(other, CompleteRing): return NotImplemented return not self == other def set_inner(self, ring): self.innerCR_ring = ring def set_outer(self, ring): self.outerCR_ring = ring def sortUpLadders(self): self.up_ladders = sortby(self.up_ladders,1) self.up_ladders.reverse() # this as my newer cleaned up version, but I broke it i think (7-19-16) # def addSegsToCP(self, segs, tol_closure=opt.tol_isApproxClosedPath): # """input a list of segments to append to self.completed_path # this function will stop adding segs if a seg endpoint is near the # completed_path startpoint""" # if len(segs)==0: # raise Exception("No segs given to insert") # # # Iterate through segs to check if segments join together nicely # # and (fix them if need be and) append them to completed_path # for seg in segs: # # This block checks if cp is (nearly) closed. # # If so, closes it with a Line, and returns the fcn # if len(self.completed_path)!=0: # cp_start, cp_end = self.completed_path[0].start, self.completed_path[-1].end # if abs(cp_start - cp_end) < tol_closure: # if cp_start==cp_end: # # then do nothing else and return # return # else: # # then close completed_path with a line and return # self.completed_path.append(Line(cp_start, cp_end)) # return # # elif seg.start != self.completed_path[-1].end: # # then seg does not start at the end of completed_path, # # fix it then add it on # current_endpoint = self.completed_path[-1].end # if abs(seg.start - current_endpoint) < tol_closure: # # then seg is slightly off from current end of # # completed_path, fix seg and insert it into # # completed_path # if isinstance(seg, CubicBezier): # P0, P1, P2, P3 = seg.bpoints() # newseg = CubicBezier(current_endpoint, P1, P2, P3) # elif isinstance(seg, Line): # newseg = Line(current_endpoint, seg.end) # else: # raise Exception('Path segment is neither Line ' # 'object nor CubicBezier object.') # self.completed_path.insert(len(self.completed_path), newseg) # else: # raise Exception("Segment being added to path does not " # "start at path endpoint.") # else: # # then seg does not need to be fixed, so go ahead and insert it # self.completed_path.insert(len(self.completed_path), seg) def addSegsToCP(self, segs, tol_closure=opt.tol_isApproxClosedPath): #input a list of segments to append to self.completed_path #this function will stop adding segs if a seg endpoint is near the completed_path startpoint if len(segs)==0: raise Exception("No segs given to insert") #Iterate through segs to check if segments join together nicely #and (fix them if need be and) append them to completed_path for seg in segs: #This block checks if cp is (nearly) closed. #If so, closes it with a Line, and returns the fcn if len(self.completed_path)!=0: cp_start, cp_end = self.completed_path[0].start, self.completed_path[-1].end if abs(cp_start - cp_end) < tol_closure: if cp_start==cp_end: #then do nothing else and return return else: #then close completed_path with a line and return self.completed_path.append(Line(cp_start,cp_end)) return if len(self.completed_path)!=0 and seg.start != self.completed_path[-1].end: #then seg does not start at the end of completed_path, fix it then add it on current_endpoint = self.completed_path[-1].end if abs(seg.start - current_endpoint) < tol_closure: #then seg is slightly off from current end of completed_path, fix seg and insert it into completed_path if isinstance(seg,CubicBezier): P0,P1,P2,P3 = cubPoints(seg) newseg = CubicBezier(current_endpoint,P1,P2,P3) elif isinstance(seg,Line): newseg = Line(current_endpoint,seg.end) else: raise Exception('Path segment is neither Line object nor CubicBezier object.') self.completed_path.insert(len(self.completed_path),newseg) else: raise Exception("Segment being added to path does not start at path endpoint.") else: #then seg does not need to be fixed, so go ahead and insert it self.completed_path.insert(len(self.completed_path),seg) def addConnectingPathToCP(self, connecting_path, seg0, t0, seg1, t1): # first find orientation by checking whether t0 is closer to start or end. T0, T1 = segt2PathT(connecting_path, seg0, t0), segt2PathT(connecting_path, seg1, t1) i0, i1 = connecting_path.index(seg0), connecting_path.index(seg1) first_seg = reverseSeg(trimSeg(seg1, 0, t1)) last_seg = reverseSeg(trimSeg(seg0, t0, 1)) if T0 > T1: # discontinuity between intersection points if isApproxClosedPath(connecting_path): middle_segs = [reverseSeg(connecting_path[i1-i]) for i in range(1, (i1-i0) % len(connecting_path))] else: raise Exception("ir jumps another ir's gap. This case is not " "implimented yet") elif T0 < T1: # discontinuity NOT between intersection points middle_segs = [reverseSeg(connecting_path[i1+i0-i]) for i in range(i0 + 1, i1)] else: raise Exception("T0=T1, this means there's a bug in either " "pathXpathIntersections fcn or " "trimAndAddTransectsBeforeCompletion fcn") # first seg if isDegenerateSegment(first_seg): tmpSeg = copyobject(middle_segs.pop(0)) tmpSeg.start = first_seg.start first_seg = tmpSeg if first_seg.end == self.completed_path[0].start: self.completed_path.insert(0,first_seg) else: printPath(first_seg) printPath(last_seg) printPath(connecting_path) raise Exception("first_seg is set up wrongly") # middle segs self.addSegsToCP(middle_segs) # last seg if isDegenerateSegment(last_seg): middle_segs[-1].end = last_seg.end else: self.addSegsToCP([last_seg]) def trimAndAddTransectsBeforeCompletion(self): # Retrieve transect endpoints if necessary (irORcr0, T0), (irORcr1, T1) = self.down_ladder0, self.down_ladder1 tr0_start_pt = irORcr0.ORring.point(T0) tr1_end_pt = irORcr1.ORring.point(T1) if not self.overlap0: # then no overlap at start, add transect0 to beginning of # connected path (before the ir itself) i0 = -1 startSeg = Line(tr0_start_pt, self.ir_start) else: # overlap at start, trim the first seg in the ir (don't connect # anything, just trim) i0 = self.ring.path.index(self.corrected_start[1]) startSeg = trimSeg(self.corrected_start[1], self.corrected_start[2],1) if not self.overlap1: # then no overlap at end to add transect1 to connected path # (append to end of the ir) i1 = len(self.ring.path) endSeg = Line(self.ir_end, tr1_end_pt) else: # overlap at end, trim the last seg in the ir (don't connect # anything, just trim) i1 = self.ring.path.index(self.corrected_end[1]) endSeg = trimSeg(self.corrected_end[1], 0, self.corrected_end[2]) # first seg if isDegenerateSegment(startSeg): tmpSeg = copyobject(self.ring.path[i0 + 1]) tmpSeg.start = startSeg.start startSeg = tmpSeg i0 += 1 self.addSegsToCP([startSeg]) else: self.addSegsToCP([startSeg]) # middle segs if i0 + 1 != i1: self.addSegsToCP([self.ring.path[i] for i in range(i0+1, i1)]) # last seg if isDegenerateSegment(endSeg): self.completed_path[-1].end = endSeg.end else: self.addSegsToCP([endSeg]) def irpoint(self, pos): return self.ring.point(pos) def area(self): if not isinstance(self.completed_path,Path): return "Fail" if (self.completed_path is None or not isApproxClosedPath(self.completed_path)): # raise Exception("completed_path not complete. Distance between start and end: %s"%abs(self.completed_path.point(0) - self.completed_path.point(1))) return "Fail" return areaEnclosed(self.completed_path) def type(self, colordict): for (key, val) in colordict.items(): if self.ring.color == val: return key else: raise Exception("Incomplete Ring color not in colordict... you shouldn't have gotten this far. Bug detected.") # def info(self,cp_index): # ###### "complete ring index, complete?, inner BrookID, outer BrookID, inner color, outer color, area, area Ignoring IRs, averageRadius, minR, maxR, IRs contained" # return str(cp_index) + "," + "Incomplete"+"," + "N/A" + ", " + self.ring.brook_tag + "," + "N/A" + ", " + self.ring.color +"," + str(self.area()) +", "+ "N/A"+","+str(self.ring.aveR())+","+str(self.ring.minR)+","+str(self.ring.maxR)+","+"N/A" def info(self, cp_index, colordict): ###### "complete ring index, type, # of IRs contained, minR, maxR, aveR, area, area Ignoring IRs" return str(cp_index)+","+self.type(colordict)+","+"N/A"+","+str(self.ring.minR)+","+ str(self.ring.maxR)+","+str(self.ring.aveR())+","+str(self.area())+","+"N/A" def followPathBackwards2LadderAndUpDown(self, irORcr, T0): """irORcr is the path being followed, self is the IR to be completed returns (traveled_path,irORcr_new,t_new) made from the part of irORcr's path before T0 (and after ladder) plus the line from ladder (the first one that is encountered)""" rds = remove_degenerate_segments irORcr_path = irORcr.ORring.path thetaprekey = Theta_Tstar(T0) thetakey = lambda lad: thetaprekey.distfcn(lad[1]) sorted_upLadders = sorted(irORcr.up_ladders, key=thetakey) if isinstance(irORcr, CompleteRing): ir_new, T = sorted_upLadders[0] if T != T0: reversed_path_followed = reversePath(cropPath(irORcr_path, T, T0)) else: # this happens when up and down ladder are at same location reversed_path_followed = Path() # add the ladder to reversed_path_followed if (irORcr, T) == ir_new.down_ladder0: if not ir_new.overlap0: ladder = Line(irORcr_path.point(T), ir_new.irpoint(0)) reversed_path_followed.append(ladder) T_ir_new = 0 else: T_ir_new = segt2PathT(ir_new.ring.path, ir_new.corrected_start[1], ir_new.corrected_start[2]) elif (irORcr, T) == ir_new.down_ladder1: if not ir_new.overlap1: ladder = Line(irORcr_path.point(T), ir_new.irpoint(1)) reversed_path_followed.append(ladder) T_ir_new = 1 else: T_ir_new = segt2PathT(ir_new.ring.path, ir_new.corrected_end[1], ir_new.corrected_end[2]) else: raise Exception("this case shouldn't be reached, mistake in " "logic or didn't set downladder somewhere.") return rds(reversed_path_followed), ir_new, T_ir_new else: # current ring to follow to ladder is incomplete ring irORcr_path = irORcr.ring.path for ir_new, T in sorted_upLadders: if T < T0: # Note: always following path backwards reversed_path_followed = irORcr_path.cropped(T, T0).reversed() if (irORcr, T) == ir_new.down_ladder0: if not ir_new.overlap0: ladder = Line(irORcr_path.point(T), ir_new.irpoint(0)) reversed_path_followed.append(ladder) T_ir_new = 0 else: T_ir_new = segt2PathT(ir_new.ring.path, ir_new.corrected_start[1], ir_new.corrected_start[2]) elif (irORcr, T) == ir_new.down_ladder1: if not ir_new.overlap1: ladder = Line(irORcr_path.point(T), ir_new.irpoint(1)) reversed_path_followed.append(ladder) T_ir_new = 1 else: T_ir_new = segt2PathT(ir_new.ring.path, ir_new.corrected_end[1], ir_new.corrected_end[2]) else: tmp_mes = ("this case shouldn't be reached, mistake " "in logic or didn't set downladder " "somewhere.") raise Exception(tmp_mes) return rds(reversed_path_followed), ir_new, T_ir_new # none of the upladder were between 0 and T0, # so use downladder at 0 else: (irORcr_new, T_new) = irORcr.down_ladder0 irORcr_new_path = irORcr_new.ORring.path ###Should T0==0 ever? if T0 != 0: reversed_path_followed = irORcr.ring.path.cropped(0, T0).reversed() else: reversed_path_followed = Path() if irORcr.overlap0 == False: ladder = Line(irORcr_path.point(0), irORcr_new_path.point(T_new)) reversed_path_followed.append(ladder) return rds(reversed_path_followed), irORcr_new, T_new def findMiddleOfConnectingPath(self): #creates path starting of end of down_ladder1, go to nearest (with t> t0) up-ladder or if no up-ladder then down_ladder at end and repeat until getting to bottom of down_ladder0 maxIts = 1000 #### Tolerance traveled_path = Path() iters = 0 (irORcr_new,T_new) = self.down_ladder1 doneyet = False while iters < maxIts and not doneyet: iters =iters+ 1 # ##DEBUG sd;fjadsfljkjl; # if self.ring.path[0].start == (260.778+153.954j): # from misc4rings import dis # from svgpathtools import Line # p2d=[self.completed_path, # self.down_ladder0[0].ORring.path, # self.down_ladder1[0].ORring.path] # clrs = ['blue','green','red'] # if iters>1: # p2d.append(Path(*traveled_path)) # clrs.append('black') # lad0a = self.down_ladder0[0].ORring.path.point(self.down_ladder0[1]) # lad0b = self.ORring.path[0].start # lad0 = Line(lad0a,lad0b) # lad1a = self.ORring.path[-1].end # lad1b = self.down_ladder1[0].ORring.path.point(self.down_ladder1[1]) # lad1 = Line(lad1a,lad1b) # dis(p2d,clrs,lines=[lad0,lad1]) # print abs(lad0.start-lad0.end) # print abs(lad1.start-lad1.end) # bla=1 # ##end of DEBUG sd;fjadsfljkjl; traveled_path_part, irORcr_new, T_new = self.followPathBackwards2LadderAndUpDown(irORcr_new, T_new) for seg in traveled_path_part: traveled_path.append(seg) if irORcr_new == self: return traveled_path # if irORcr_new == self.down_ladder0[0]: # doneyet = True # irORcr_new_path = irORcr_new.ORring.path # if T_new != self.down_ladder0[1]: # for seg in reversePath(cropPath(irORcr_new_path,self.down_ladder0[1],T_new)): # traveled_path.append(seg) # break if (irORcr_new, T_new) == self.down_ladder0: return traveled_path if iters >= maxIts-1: raise Exception("findMiddleOfConnectingPath reached maxIts") return traveled_path def hardComplete(self, tol_closure=opt.tol_isApproxClosedPath): self.trimAndAddTransectsBeforeCompletion() meatOf_connecting_path = self.findMiddleOfConnectingPath() ###this is a Path object self.addSegsToCP(meatOf_connecting_path) cp_start,cp_end = self.completed_path[0].start, self.completed_path[-1].end #check newly finished connecting_path is closed if abs(cp_start - cp_end) >= tol_closure: raise Exception("Connecting path should be finished but is not closed.") #test for weird .point() bug where .point(1) != end if (abs(cp_start - self.completed_path.point(0)) >= tol_closure or abs(cp_end - self.completed_path.point(1)) >= tol_closure): self.completed_path = parse_path(path2str(self.completed_path)) raise Exception("weird .point() bug where .point(1) != end... I just added this check in on 3-5-15, so maybe if this happens it doesn't even matter. Try removing this code-block... or change svgpathtools.Path.point() method to return one or the other.") def findTransect2endpointFromInnerPath_normal(self,irORcr_innerPath,innerPath,T_range,Tpf,endBin): #Tpf: If The T0 transect intersects self and the T1 does not, then Tpf should be True, otherwise it should be false. #Note: If this endpoint's transect is already found, then this function returns (False,False,False,False) #Returns: (irORcr,nL,seg_irORcr,t_irORcr) where irORcr is the inner path that the transect, nL, leaves from and seg_irORcr and t_irORcr correspond to innerPath and nL points from seg_irORcr.point(t_irORcr) to the desired endpoint #Note: irORcr will differ from irORcr_innerPath in the case where irORcr_innerPath admits a transect but this transect intersects with a (less-inner) previously failed path. The less-inner path is then output. (T0,T1) = T_range if T1<T0: if T1==0: T1=1 else: Exception("I don't think T_range should ever have T0>T1. Check over findTransect2endpointsFromInnerPath_normal to see if this is acceptable.") if endBin == 0 and self.transect0found: return False, False, False, False elif endBin == 1 and self.transect1found: return False, False, False, False elif endBin not in {0,1}: raise Exception("endBin not a binary - so there is a typo somewhere when calling this fcn") if irORcr_innerPath.isCore: return irORcr_innerPath, Line(irORcr_innerPath.inner.path.point(0), self.irpoint(endBin)), irORcr_innerPath.inner.path[0], 0 # make transect from center (actually path.point(0)) to the endpoint maxIts = 100 ##### tolerance its = 0 while (abs(innerPath.point(T0) - innerPath.point(T1)) >= opt.tol_isApproxClosedPath and its <= maxIts): its += 1 T = float((T0+T1))/2 center = self.ring.center nL = normalLineAtT_toInner_intersects_withOuter(T,innerPath,self.ring.path,center)[0] #check if transect from innerPath.point(T) intersects with outerPath if Tpf: #p x f if nL != False: #p p f T0 = T else: #p f f T1 = T else: #f x p if nL != False: #f p p T1 = T else: #f f p T0 = T # ###DEBUG asdfkljhjdkdjjjdkkk # if self.ORring.point(0)==(296.238+285.506j): # from misc4rings import dis # print "endBin=%s\nT0=%s\nT=%s\nT1=%s\n"%(endBin,T0,T,T1) # if isNear(innerPath.point(T0),innerPath.point(T1)): # print "Exit Criterion Met!!!" # if nL==False: # nLtmp = normalLineAtT_toInner_intersects_withOuter(T,innerPath,self.ring.path,center,'debug')[0] # else: # nLtmp = nL # dis([innerPath,self.ring.path,Path(nLtmp)],['green','red','blue'],nodes=[center,innerPath.point(T0),innerPath.point(T1)],node_colors=['blue','green','red']) # bla=1 # ###end of DEBUG asdfkljhjdkdjjjdkkk if its>=maxIts: raise Exception("while loop for finding transect by bisection reached maxIts without terminating") if nL != False: #x p x t_inner, seg_inner = pathT2tseg(innerPath, T) else: #x f x if Tpf: #p f f nL = normalLineAtT_toInner_intersects_withOuter(T0,innerPath,self.ring.path,center)[0] (t_inner,seg_inner) = pathT2tseg(innerPath,T0) else: #f f p nL = normalLineAtT_toInner_intersects_withOuter(T1,innerPath,self.ring.path,center)[0] (t_inner,seg_inner) = pathT2tseg(innerPath,T1) transect_info = (irORcr_innerPath,nL,seg_inner,t_inner) ###Important Note: check that transect does not go through any other rings while headed to its target endpoint (Andy has note explaining this "7th case") ###If this intersection does happen... just cut off the line at the intersection point - this leads to transect not being normal to the ring it emanates from. if endBin == 0: failed_IRs_2check = self.transect0fails else: failed_IRs_2check = self.transect1fails keyfcn = lambda x: x.ORring.sort_index failed_IRs_2check = sorted(failed_IRs_2check,key=keyfcn) tr_line = transect_info[1] exclusions = [] #used to check the line from closest_pt to endpoint doesn't intersect num_passed = 0 run_again = True while run_again: run_again = False for idx,fIR in enumerate(failed_IRs_2check): num_passed +=1 if idx in exclusions: continue intersectionList = pathXpathIntersections(Path(tr_line),fIR.ring.path) if len(intersectionList) == 0: continue else: if len(intersectionList) > 1: print("Warning: Transect-FailedPath intersection check returned multiple intersections. This is possible, but should be very rare.") # (seg_tl, seg_fIR, t_tl, t_fIR) = intersectionList[0] t_fIR,seg_fIR = closestPointInPath(self.ORring.point(endBin),fIR.ring.path)[1:3] fIR_closest_pt = seg_fIR.point(t_fIR) if endBin: new_nonNormal_transect = Line(self.ring.path[-1].end,fIR_closest_pt) else: new_nonNormal_transect = Line(fIR_closest_pt,self.ring.path[0].start) transect_info = (fIR,new_nonNormal_transect,seg_fIR,t_fIR) exclusions += range(idx+1) run_again = True break return transect_info def findTransects2endpointsFromInnerPath_normal(self,irORcr_innerPath,innerPath): """Finds transects to both endpoints (not just that specified by endBin - see outdated description below) Note: This fcn will attempt to find transects for endpoints where the transects have not been found and will return (False, False, False) for those that have. Returns: (irORcr,nL,seg_irORcr,t_irORcr) where irORcr is the inner path that the transect, nL, leaves from and seg_irORcr and t_irORcr correspond to innerPath and nL points from seg_irORcr.point(t_irORcr) to the desired endpoint. Note: irORcr will differ from irORcr_innerPath in the case where irORcr_innerPath admits a transect but this transect intersects with a (less-inner) previously failed path. The less-inner path is then output.""" #Outdated Instructions #This fcn is meant to find the transect line from (and normal to) the # inner path that goes through OuterPt. It does this using the # bisection method. #INPUT: innerPath and outerPath are Path objects, center is a point # representing the core, endBin specifies which end point in outerPath # we hope to find the transect headed towards (so must be a 0 or a 1) #OUTPUT: Returns (transect_Line,inner_seg,inner_t) where normal_line # is the transverse Line object normal to innerPath intersecting # outerPath at outerPt or, if such a line does not exist, returns # (False,False,False,False) # inner_seg is the segment of innerPath that this normal transect line # begins at, s.t. seg.point(inner_t) = transect_Line.point(0) outerPath = self.ring.path center = self.ring.center tol_numberDetectionLines_transectLine_normal = 20 ##### tolerance N = tol_numberDetectionLines_transectLine_normal # if self.transect0found and not self.transect1found: # return (False,False,False,False), self.findTransect2endpointFromInnerPath_normal(innerPath) # if not self.transect0found and self.transect1found: # return self.findTransect2endpoint0FromInnerPath_normal(innerPath), (False,False,False,False) # if self.transect0found and self.transect1found: # raise Exception("Both transects already found... this is a bug.") # For a visual explanation of the following code block and the six # cases, see Andy's "Gap Analysis of Transects to Endpoints" # check if transect from innerPath.point(0) intersect with outerPath nL_from0, seg_from0, t_from0 = normalLineAtT_toInner_intersects_withOuter(0, innerPath, outerPath, center) if isApproxClosedPath(innerPath): (nL_from1,seg_from1,t_from1) = (nL_from0,seg_from0,t_from0) else: #check if transect from innerPath.point(1) intersect with outerPath nL_from1, seg_from1, t_from1 = normalLineAtT_toInner_intersects_withOuter(1, innerPath, outerPath, center) #Case: TF if nL_from0 and not nL_from1: return (False,False,False,False), self.findTransect2endpointFromInnerPath_normal(irORcr_innerPath,innerPath,(0,1),True,1) #Case: FT if (not nL_from0) and nL_from1: return self.findTransect2endpointFromInnerPath_normal(irORcr_innerPath,innerPath,(0,1),False,0), (False,False,False,False) # determine All, None, or Some (see notes in notebook on this agorithm # for explanation) max_pass_Tk = 0 min_pass_Tk = 1 max_fail_Tk = 0 min_fail_Tk = 1 somePass = False someFail = False dT = float(1)/(N-1) for k in range(1,N-1): Tk = k*dT nLk, outer_segk, outer_tk = normalLineAtT_toInner_intersects_withOuter(Tk, innerPath, outerPath, center) if nLk != False: somePass = True if Tk > max_pass_Tk: max_pass_Tk = Tk # max_pass = (nLk,outer_segk,outer_tk) if Tk < min_pass_Tk: min_pass_Tk = Tk # min_pass = (nLk,outer_segk,outer_tk) else: someFail = True if Tk > max_fail_Tk: max_fail_Tk = Tk # max_fail = (nLk,outer_segk,outer_tk) if Tk < min_fail_Tk: min_fail_Tk = Tk # min_fail = (nLk,outer_segk,outer_tk) if somePass and someFail: #Case: TT & only some pass [note: TT & some iff TT & T0>T1] if nL_from0 and nL_from1: Trange0 = (max_fail_Tk, (max_fail_Tk + dT)%1) Tpf0 = False Trange1 = ((min_fail_Tk - dT)%1, min_fail_Tk) Tpf1 = True #Case: FF & only some pass elif (not nL_from0) and (not nL_from1): Trange0 = ((min_pass_Tk - dT)%1, min_pass_Tk) Tpf0 = False Trange1 = (max_pass_Tk, (max_pass_Tk + dT)%1) Tpf1 = True for Ttestindex,T2test in enumerate(Trange0 + Trange1): #debugging only if T2test>1 or T2test < 0: print Ttestindex print T2test raise Exception() args = irORcr_innerPath, innerPath, Trange0, Tpf0, 0 tmp1 = self.findTransect2endpointFromInnerPath_normal(*args) args = irORcr_innerPath, innerPath, Trange1, Tpf1, 1 tmp2 = self.findTransect2endpointFromInnerPath_normal(*args) return tmp1, tmp2 #Cases: (TT & all) or (FF & none) [note: TT & all iff TT & T0<T1] else: return (False, False, False, False), (False, False, False, False)
if type(penult_segment) == CubicBezier: s = copy.copy(penult_segment[4]) e = copy.copy(initial[1]) pathlist[-1] = (['Line', s, e]) elif type(penult_segment) == Line: s = copy.copy(penult_segment[2]) e = copy.copy(initial[1]) pathlist[-1] = (['Line', s, e]) #for i in range(len(pathlist)): # print('-> {}\t{}'.format(pathlist[i][3], pathlist[(i + 1) % (len(pathlist)) ][0])) # print('<-: {}\t{}'.format(pathlist[i][0], pathlist[(i -1) % (len(pathlist)) ][3])) for i in pathlist: if i[0] == 'CubicBezier': newpath.append(CubicBezier(i[1], i[2], i[3], i[4])) elif i[0] == 'Line': newpath.append(Line(i[1], i[2])) newpaths.append(newpath) j += 1 print('\tfinished generating frame {}'.format(FRAME + 1)) # export svg #newpaths.reverse() svg_newfile = svg_file + '_' + str(FRAME + 1) + '.svg' wsvg(newpaths, attributes=attr, svg_attributes=svg_attr, filename=svg_newfile)
def followPathBackwards2LadderAndUpDown(self, irORcr, T0): """irORcr is the path being followed, self is the IR to be completed returns (traveled_path,irORcr_new,t_new) made from the part of irORcr's path before T0 (and after ladder) plus the line from ladder (the first one that is encountered)""" rds = remove_degenerate_segments irORcr_path = irORcr.ORring.path thetaprekey = Theta_Tstar(T0) thetakey = lambda lad: thetaprekey.distfcn(lad[1]) sorted_upLadders = sorted(irORcr.up_ladders, key=thetakey) if isinstance(irORcr, CompleteRing): ir_new, T = sorted_upLadders[0] if T != T0: reversed_path_followed = reversePath(cropPath(irORcr_path, T, T0)) else: # this happens when up and down ladder are at same location reversed_path_followed = Path() # add the ladder to reversed_path_followed if (irORcr, T) == ir_new.down_ladder0: if not ir_new.overlap0: ladder = Line(irORcr_path.point(T), ir_new.irpoint(0)) reversed_path_followed.append(ladder) T_ir_new = 0 else: T_ir_new = segt2PathT(ir_new.ring.path, ir_new.corrected_start[1], ir_new.corrected_start[2]) elif (irORcr, T) == ir_new.down_ladder1: if not ir_new.overlap1: ladder = Line(irORcr_path.point(T), ir_new.irpoint(1)) reversed_path_followed.append(ladder) T_ir_new = 1 else: T_ir_new = segt2PathT(ir_new.ring.path, ir_new.corrected_end[1], ir_new.corrected_end[2]) else: raise Exception("this case shouldn't be reached, mistake in " "logic or didn't set downladder somewhere.") return rds(reversed_path_followed), ir_new, T_ir_new else: # current ring to follow to ladder is incomplete ring irORcr_path = irORcr.ring.path for ir_new, T in sorted_upLadders: if T < T0: # Note: always following path backwards reversed_path_followed = irORcr_path.cropped(T, T0).reversed() if (irORcr, T) == ir_new.down_ladder0: if not ir_new.overlap0: ladder = Line(irORcr_path.point(T), ir_new.irpoint(0)) reversed_path_followed.append(ladder) T_ir_new = 0 else: T_ir_new = segt2PathT(ir_new.ring.path, ir_new.corrected_start[1], ir_new.corrected_start[2]) elif (irORcr, T) == ir_new.down_ladder1: if not ir_new.overlap1: ladder = Line(irORcr_path.point(T), ir_new.irpoint(1)) reversed_path_followed.append(ladder) T_ir_new = 1 else: T_ir_new = segt2PathT(ir_new.ring.path, ir_new.corrected_end[1], ir_new.corrected_end[2]) else: tmp_mes = ("this case shouldn't be reached, mistake " "in logic or didn't set downladder " "somewhere.") raise Exception(tmp_mes) return rds(reversed_path_followed), ir_new, T_ir_new # none of the upladder were between 0 and T0, # so use downladder at 0 else: (irORcr_new, T_new) = irORcr.down_ladder0 irORcr_new_path = irORcr_new.ORring.path ###Should T0==0 ever? if T0 != 0: reversed_path_followed = irORcr.ring.path.cropped(0, T0).reversed() else: reversed_path_followed = Path() if irORcr.overlap0 == False: ladder = Line(irORcr_path.point(0), irORcr_new_path.point(T_new)) reversed_path_followed.append(ladder) return rds(reversed_path_followed), irORcr_new, T_new
def calc_path_freetype_decompose(self, fontsize: float) -> Path: ''' ''' outline: freetype.Outline = self.face.glyph.outline def move_to(a, ctx): ctx.append("M {},{}".format(a.x, a.y)) def line_to(a, ctx): ctx.append("L {},{}".format(a.x, a.y)) def conic_to(a, b, ctx): ctx.append("Q {},{} {},{}".format(a.x, a.y, b.x, b.y)) def cubic_to(a, b, c, ctx): ctx.append("C {},{} {},{} {},{}".format(a.x, a.y, b.x, b.y, c.x, c.y)) ctx: List[str] = [] outline.decompose(ctx, move_to=move_to, line_to=line_to, conic_to=conic_to, cubic_to=cubic_to) path_str = " ".join(ctx) # build a Path instance path = parse_path(path_str) # this path is not at the right position - it has to be scaled, shifted and flipped yflip = self.yflip_value if yflip == None: yflip = self.bbox.yMax xshift = self.xshift_value if xshift == None: xshift = self.bbox.xMin # extra scaling scaling = fontsize / self.CHAR_SIZE # let's go path = path.translated(-xshift) # flipping means a special transformation ... matrix(1,0, 0,-1, 0,7.5857) apath = Path() tf = np.identity(3) tf[1][1] = -1 for seg in path: aseg = xxpath.transform(seg, tf) apath.append(aseg) # and the companion translation path = apath.translated(yflip * 1j) # finally path = path.scaled(scaling, scaling) self.path = path return self.path
def parse_path(pathdef, current_pos=0j, tree_element=None): # In the SVG specs, initial movetos are absolute, even if # specified as 'm'. This is the default behavior here as well. # But if you pass in a current_pos variable, the initial moveto # will be relative to that current_pos. This is useful. elements = list(_tokenize_path(pathdef)) # Reverse for easy use of .pop() elements.reverse() if tree_element is None: segments = Path() else: segments = Path(tree_element=tree_element) start_pos = None command = None while elements: if elements[-1] in COMMANDS: # New command. last_command = command # Used by S and T command = elements.pop() absolute = command in UPPERCASE command = command.upper() else: # If this element starts with numbers, it is an implicit command # and we don't change the command. Check that it's allowed: if command is None: raise ValueError( "Unallowed implicit command in %s, position %s" % (pathdef, len(pathdef.split()) - len(elements))) if command == 'M': # Moveto command. x = elements.pop() y = elements.pop() pos = float(x) + float(y) * 1j if absolute: current_pos = pos else: current_pos += pos # when M is called, reset start_pos # This behavior of Z is defined in svg spec: # http://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand start_pos = current_pos # Implicit moveto commands are treated as lineto commands. # So we set command to lineto here, in case there are # further implicit commands after this moveto. command = 'L' elif command == 'Z': # Close path if not (current_pos == start_pos): segments.append(Line(current_pos, start_pos)) segments.closed = True current_pos = start_pos command = None elif command == 'L': x = elements.pop() y = elements.pop() pos = float(x) + float(y) * 1j if not absolute: pos += current_pos segments.append(Line(current_pos, pos)) current_pos = pos elif command == 'H': x = elements.pop() pos = float(x) + current_pos.imag * 1j if not absolute: pos += current_pos.real segments.append(Line(current_pos, pos)) current_pos = pos elif command == 'V': y = elements.pop() pos = current_pos.real + float(y) * 1j if not absolute: pos += current_pos.imag * 1j segments.append(Line(current_pos, pos)) current_pos = pos elif command == 'C': control1 = float(elements.pop()) + float(elements.pop()) * 1j control2 = float(elements.pop()) + float(elements.pop()) * 1j end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: control1 += current_pos control2 += current_pos end += current_pos segments.append(CubicBezier(current_pos, control1, control2, end)) current_pos = end elif command == 'S': # Smooth curve. First control point is the "reflection" of # the second control point in the previous path. if last_command not in 'CS': # If there is no previous command or if the previous command # was not an C, c, S or s, assume the first control point is # coincident with the current point. control1 = current_pos else: # The first control point is assumed to be the reflection of # the second control point on the previous command relative # to the current point. control1 = current_pos + current_pos - segments[-1].control2 control2 = float(elements.pop()) + float(elements.pop()) * 1j end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: control2 += current_pos end += current_pos segments.append(CubicBezier(current_pos, control1, control2, end)) current_pos = end elif command == 'Q': control = float(elements.pop()) + float(elements.pop()) * 1j end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: control += current_pos end += current_pos segments.append(QuadraticBezier(current_pos, control, end)) current_pos = end elif command == 'T': # Smooth curve. Control point is the "reflection" of # the second control point in the previous path. if last_command not in 'QT': # If there is no previous command or if the previous command # was not an Q, q, T or t, assume the first control point is # coincident with the current point. control = current_pos else: # The control point is assumed to be the reflection of # the control point on the previous command relative # to the current point. control = current_pos + current_pos - segments[-1].control end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: end += current_pos segments.append(QuadraticBezier(current_pos, control, end)) current_pos = end elif command == 'A': radius = float(elements.pop()) + float(elements.pop()) * 1j rotation = float(elements.pop()) arc = float(elements.pop()) sweep = float(elements.pop()) end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: end += current_pos segments.append(Arc(current_pos, radius, rotation, arc, sweep, end)) current_pos = end return segments
def get_graphics_in_rect(paths, attribute_dicts, boundary): paths_out = [] attributes_out = [] left, top, right, bottom = boundary boundary_rect = polygon(left + 1j * top, right + 1j * top, right + 1j * bottom, left + 1j * bottom) for path, attributes in zip(paths, attribute_dicts): new_path = Path() for seg in path: # the line is inside the patch if it coincides with one of the edges # svgpathtools assumes that two segments do not coinside when it searches for intersections so we need to address this case separately if isinstance(seg, Line): if any(seg == edge for edge in boundary_rect): new_path.append(seg) continue # for a given segment find all its intersections with the boundary intersections = [] for edge in boundary_rect: intersections = intersections + seg.intersect(edge) # if svgpathtools found no intersections then there are 3 cases if len(intersections) == 0: seg_is_in_patch = point_is_in_rect(seg.start, boundary) # the segment lies completely inside the patch if seg_is_in_patch == 1: new_path.append(seg) continue # the segment lies completely outside the patch elif seg_is_in_patch == -1: continue # the segment is line and it lies inside on of the edges of the patch else: new_path.append(seg) continue else: # minmax the intersection point since sometimes it lies slightly outside of the segment split_points = [ max(min(pair[0], 1), 0) for pair in intersections ] split_points.append(0) split_points.append(1) split_points = sorted(set(split_points)) # split segment in the intersection points for i in range(len(split_points) - 1): subseg = seg.cropped(split_points[i], split_points[i + 1]) start_is_in_patch = point_is_in_rect( subseg.start, boundary) end_is_in_patch = point_is_in_rect(subseg.end, boundary) # for each type of segment if one of the endpoints lies outside of the patch then the segment is outside if start_is_in_patch == -1 or end_is_in_patch == -1: continue # else # the lines are either on the boundary or inside if isinstance(subseg, Line): new_path.append(subseg) continue # all the other types -- Arc, QuadraticBezier, CubicBezier -- have no more common points with the boundary other than the endpoints, so any inner point of the segment is either inside or outside, which corresponds to the insideness of the whole segment # the only exception is when the patch boundary is tangent to the segment in an inner point, in which case this point isn't considered as intersection point by svgpathtools.whatsoever, in this case the segment is inside the patch if point_is_in_rect(subseg.point(.5), boundary) >= 0: new_path.append(subseg) continue if len(new_path) > 0: paths_out.append(new_path) attributes_out.append(attributes) return paths_out, attributes_out
def main(): parser = argparse.ArgumentParser() parser.add_argument('--folder', '-f', help='Path to data folder') parser.add_argument('--point_num', '-p', help='Point number for each sample', type=int, default=1024) parser.add_argument('--save_ply', '-s', help='Convert .pts to .ply', action='store_true') parser.add_argument('--augment', '-a', help='Data augmentation', action='store_true') args = parser.parse_args() print(args) batch_size = 2048 fold_num = 3 tag_aug = '_ag' if args.augment else '' folder_svg = args.folder if args.folder else '../../data/tu_berlin/svg' root_folder = os.path.dirname(folder_svg) folder_pts = os.path.join(root_folder, 'pts' + tag_aug) filelist_svg = [line.strip() for line in open(os.path.join(folder_svg, 'filelist.txt'))] category_label = dict() with open(os.path.join(os.path.dirname(folder_svg), 'categories.txt'), 'w') as file_categories: for filename in filelist_svg: category = os.path.split(filename)[0] if category not in category_label: file_categories.write('%s %d\n' % (category, len(category_label))) category_label[category] = len(category_label) filelist_svg_failed = [] data = np.zeros((batch_size, args.point_num, 6)) label = np.zeros((batch_size), dtype=np.int32) for idx_fold in range(fold_num): filelist_svg_fold = [filename for i, filename in enumerate(filelist_svg) if i % fold_num == idx_fold] random.seed(idx_fold) random.shuffle(filelist_svg_fold) filename_filelist_svg_fold = os.path.join(root_folder, 'filelist_fold_%d.txt' % (idx_fold)) with open(filename_filelist_svg_fold, 'w') as filelist_svg_fold_file: for filename in filelist_svg_fold: filelist_svg_fold_file.write('%s\n' % (filename)) idx_h5 = 0 idx = 0 filename_filelist_h5 = os.path.join(root_folder, 'fold_%d_files%s.txt' % (idx_fold, tag_aug)) with open(filename_filelist_h5, 'w') as filelist_h5_file: for idx_file, filename in enumerate(filelist_svg_fold): filename_svg = os.path.join(folder_svg, filename) try: paths, attributes = svg2paths(filename_svg) except: filelist_svg_failed.append(filename_svg) print('{}-Failed to parse {}!'.format(datetime.now(), filename_svg)) continue points_array = np.zeros(shape=(args.point_num, 3), dtype=np.float32) normals_array = np.zeros(shape=(args.point_num, 3), dtype=np.float32) path = Path() for p in paths: p_non_empty = Path() for segment in p: if segment.length() > 0: p_non_empty.append(segment) if len(p_non_empty) != 0: path.append(p_non_empty) path_list = [] if args.augment: for removal_idx in range(6): path_with_removal = Path() for p in path[:math.ceil((0.4 + removal_idx * 0.1) * len(paths))]: path_with_removal.append(p) path_list.append(path_with_removal) path_list = path_list + augment(path, 6) else: path_list.append(path) for path_idx, path in enumerate(path_list): for sample_idx in range(args.point_num): sample_idx_float = (sample_idx + random.random()) / (args.point_num - 1) while True: try: point = path.point(sample_idx_float) normal = path.normal(sample_idx_float) break except: sample_idx_float = random.random() continue points_array[sample_idx] = (point.real, sample_idx_float, point.imag) normals_array[sample_idx] = (normal.real, random.random() * 1e-6, normal.imag) points_min = np.amin(points_array, axis=0) points_max = np.amax(points_array, axis=0) points_center = (points_min + points_max) / 2 scale = np.amax(points_max - points_min) / 2 points_array = (points_array - points_center) * (0.8 / scale, 0.4, 0.8 / scale) if args.save_ply: tag_aug_idx = tag_aug + '_' + str(path_idx) if args.augment else tag_aug filename_pts = os.path.join(folder_pts, filename[:-4] + tag_aug_idx + '.ply') data_utils.save_ply(points_array, filename_pts, normals=normals_array) idx_in_batch = idx % batch_size data[idx_in_batch, ...] = np.concatenate((points_array, normals_array), axis=-1).astype(np.float32) label[idx_in_batch] = category_label[os.path.split(filename)[0]] if ((idx + 1) % batch_size == 0) \ or (idx_file == len(filelist_svg_fold) - 1 and path_idx == len(path_list) - 1): item_num = idx_in_batch + 1 filename_h5 = 'fold_%d_%d%s.h5' % (idx_fold, idx_h5, tag_aug) print('{}-Saving {}...'.format(datetime.now(), os.path.join(root_folder, filename_h5))) filelist_h5_file.write('./%s\n' % (filename_h5)) file = h5py.File(os.path.join(root_folder, filename_h5), 'w') file.create_dataset('data', data=data[0:item_num, ...]) file.create_dataset('label', data=label[0:item_num, ...]) file.close() idx_h5 = idx_h5 + 1 idx = idx + 1 if len(filelist_svg_failed) != 0: print('{}-Failed to parse {} sketches!'.format(datetime.now(), len(filelist_svg_failed)))
def cropPath(path, T0, T1): ###TOL uses isclose # path = parse_path(path2str(path)) ###DEBUG (maybe can remove if speed demands it) if T1 == 1: seg1 = path[-1] t_seg1 = 1 i1 = len(path) - 1 else: (t_seg1, seg1) = pathT2tseg(path, T1) if isclose(t_seg1, 0): i1 = (seg_index(path, seg1) - 1) % len(path) seg1 = path[i1] t_seg1 = 1 else: i1 = seg_index(path, seg1) if T0 == 0: seg0 = path[0] t_seg0 = 0 i0 = 0 else: (t_seg0, seg0) = pathT2tseg(path, T0) if isclose(t_seg0, 1): i0 = (seg_index(path, seg0) + 1) % len(path) seg0 = path[i0] t_seg0 = 0 else: i0 = seg_index(path, seg0) if T0 < T1 and i0 == i1: new_path = Path(trimSeg(seg0, t_seg0, t_seg1)) else: new_path = Path(trimSeg(seg0, t_seg0, 1)) if T1 == T0: raise Exception("T0=T1 in cropPath.") elif T1 < T0: # T1<T0 must cross discontinuity case if not path.isclosed(): raise Exception( "T1<T0 and path is open. I think that means you put in the wrong T values.") else: for i in range(i0 + 1, len(path)): new_path.append(path[i]) for i in range(0, i1): new_path.append(path[i]) else: # T0<T1 straight-forward case for i in range(i0 + 1, i1): new_path.append(path[i]) if t_seg1 != 0: new_path.append(trimSeg(seg1, 0, t_seg1)) # ####check this path is put together properly DEBUG ONLY # #check end # path_at_T1 = path.point(T1) # if new_path[-1].end != path_at_T1: # if isNear(new_path[-1].end,path_at_T1): # new_path[-1].end = path_at_T1 # else: # raise Exception("Cropped path doesn't end where it should.") # #check start # path_at_T0 = path.point(T0) # if new_path[0].start != path_at_T0: # if isNear(new_path[0].start, path_at_T0): # new_path[0].start = path_at_T0 # else: # raise Exception("Cropped path doesn't start where it should.") # #check inner joints # for i in range(len(new_path)-1): # if new_path[i].end != new_path[i+1].start: # if isNear(new_path[i].end, new_path[i+1].start): # new_path[i].end = new_path[i+1].start # else: # raise Exception("Cropped path doesn't start where it should.") return new_path
from svgpathtools import svg2paths, Path, Line, wsvg input_file = 'PixelScaled.svg' # input file paths, attributes = svg2paths(input_file) path = paths[0] reduce_factor = int( input("enter optimization_factor:")) # has to be a positive integer > 0 reduced_path = path[:: reduce_factor] # this reduces the line segments in the path by the 'reduce_factor' orig_start = [] new_start = [] end = [] for lines in reduced_path: orig_start.append(lines[0]) end.append(lines[1]) new_start.append(orig_start[0]) new_start.extend(end) end.append(new_start[0]) line_segments = list(zip(new_start, end)) optimized_path = Path() for points in line_segments: segments = Line(points[0], points[1]) optimized_path.append(segments) wsvg(optimized_path, filename='Optimized_SVG.svg', openinbrowser=True) # output file