def __generate(self): logger.debug("Starting generate...") upper_curve = svgp.parse_path(self.upper_curve.d) upper_curve_start = (upper_curve.start.real, upper_curve.start.imag) upper_curve_end = (upper_curve.end.real, upper_curve.end.imag) lower_curve = svgp.parse_path(self.lower_curve.d) lower_curve_start = (lower_curve.start.real, lower_curve.start.imag) lower_curve_end = (lower_curve.end.real, lower_curve.end.imag) r1 = spatial.distance.euclidean(upper_curve_end, lower_curve_start) / 2 r2 = spatial.distance.euclidean(lower_curve_end, upper_curve_start) / 2 p = svgwrite.path.Path() p.push(self.upper_curve.d) p.push("A", r1, r1, 0, 1, 1, lower_curve_start[0], lower_curve_start[1]) p.push(self.lower_curve.d_c) p.push("A", r2, r2, 0, 1, 1, upper_curve_start[0], upper_curve_start[1]) p.push("Z") # Call to either tostring or to_xml() is needed to create the dict 'd' attribute. p.tostring() self.d = p.attribs['d']
def plot_lengths(a): """ :param a: [{'color':'','length':5}] :return: [{'color': 'red', 'path': Path()}] """ # Turn left 90 degrees each time behavior_ref = ['h -', 'v ', 'h ', 'v -'] path_store = [] path_str = 'M50 20j' count = 0 color = a[0].get('color', 'black') for obj in a: if obj['color'] != color: # print 'Changed colors from %s to %s' % (color, obj['color']) last_point = fix_coordinate(str(parse_path(path_str).point(1.0))) new_path_start = 'M%s' % strip_parens(last_point) res = {'color': color, 'path': parse_path(path_str)} path_store.append(res) # Add the Path to the line_store path_str = new_path_start # Start the new path color = obj['color'] # With the new color move = behavior_ref[count] + str(obj['length']) path_str = ' '.join([path_str, move]) count = 0 if count == 3 else count + 1 # Add the last entry path_store.append({'color': color, 'path': parse_path(path_str)}) return path_store
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 effect(self): if len(self.options.ids) < 2: inkex.errormsg(_("This extension requires two selected paths.")) exit() obj = self.selected[self.options.ids[0]] envelope = self.selected[self.options.ids[1]] if obj.get(inkex.addNS('type', 'sodipodi')): inkex.errormsg( _("The first selected object is of type '%s'.\nTry using the procedure Path->Object to Path." % obj.get(inkex.addNS('type', 'sodipodi')))) exit() if obj.tag == inkex.addNS('path', 'svg') or obj.tag == inkex.addNS( 'g', 'svg'): if envelope.tag == inkex.addNS('path', 'svg'): absolute_envelope_path = svgpathtools.parse_path( envelope.get("d")).d() coords_to_project = self.envelope2coords( absolute_envelope_path) if obj.tag == inkex.addNS('path', 'svg'): absolute_object_path = svgpathtools.parse_path( obj.get("d")).d() if obj.tag == inkex.addNS('g', 'svg'): absolute_object_path = "" for p in obj.iterfind( ".//{http://www.w3.org/2000/svg}path"): absolute_object_path += svgpathtools.parse_path( p.get("d")).d() #inkex.debug(absolute_object_path) new_path = projection(absolute_object_path, coords_to_project) attributes = {'d': new_path} new_element = inkex.etree.SubElement( self.current_layer, inkex.addNS('path', 'svg'), attributes) else: if envelope.tag == inkex.addNS('g', 'svg'): inkex.errormsg( _("The second selected object is a group, not a path.\nTry using the procedure Object->Ungroup." )) else: inkex.errormsg( _("The second selected object is not a path.\nTry using the procedure Path->Object to Path." )) exit() else: inkex.errormsg( _("The first selected object is not a path.\nTry using the procedure Path->Object to Path." )) exit()
def detect_indeterminate_line(glyph_info, lines): top_line = glyph_info[1][0] bottom_line = glyph_info[1][1] glyph_path = glyph_info[0] glyph_box = glyph_path.bbox() glyph_min = glyph_box[0] - search_width glyph_max = glyph_box[1] + search_width if top_line != 1 or bottom_line != 2 or glyph_min < 250: return None, None def within_bounds(comparison_path, x_min, x_max): comp = comparison_path.bbox() left_check = x_min <= comp[0] <= x_max right_check = x_min <= comp[1] <= x_max overlap_check = comp[0] <= x_min and comp[1] >= x_max return left_check or right_check or overlap_check top_paths = lines[top_line].d().split("M") top_paths = filter(lambda x: len(x.strip()) > 0, top_paths) top_paths = map(lambda x: svgpathtools.parse_path(f"M{x}"), top_paths) top_paths = filter(lambda x: within_bounds(x, glyph_min, glyph_max), top_paths) top_paths = list(top_paths) bottom_paths = lines[bottom_line].d().split("M") bottom_paths = filter(lambda x: len(x.strip()) > 0, bottom_paths) bottom_paths = map(lambda x: svgpathtools.parse_path(f"M{x}"), bottom_paths) bottom_paths = filter(lambda x: within_bounds(x, glyph_min, glyph_max), bottom_paths) bottom_paths = list(bottom_paths) def min_distance(undetermined_path, comparison_paths): minimum_distance = 999999999 nearest_point = None for _path in comparison_paths: distance, t = path_distance(undetermined_path, _path) if distance < minimum_distance: minimum_distance = distance nearest_point = _path.point(t) return minimum_distance, nearest_point top_distance, nearest_top_point = min_distance(glyph_path, top_paths) bottom_distance, nearest_bottom_point = min_distance( glyph_path, bottom_paths) if top_distance < bottom_distance: return top_line, nearest_top_point else: return bottom_line, nearest_bottom_point
def load_curves_from_svg(self, file): svg_tree = ET.parse(file) svg_root = svg_tree.getroot() self.width = int(float(svg_root.attrib['viewBox'].split()[2])) self.height = int(float(svg_root.attrib['viewBox'].split()[3])) for xml_path in svg_root.iter('{http://www.w3.org/2000/svg}path'): path = parse_path(xml_path.attrib['d']) path_len = path.length(error=0.00001) path_points = [] d = xml_path.attrib['d'][1:].split(',') num_points = int(path_len * self.point_density) for i in range(num_points): path_points.append(path.point(i / num_points)) css_class = '.' + xml_path.attrib['class'] style_str = svg_root[0].text splited_styles = style_str.split(';}') color = list( substr for substr in splited_styles if substr.find(css_class) != -1)[0].split('stroke:#')[1][:6] color = tuple(int(color[i:i + 2], 16) for i in (4, 2, 0)) #FORMAT: (B, G, R) self.curves.append(Curve(path_points, color))
def SVGPathToTrackPoints(fname, spacing=TRACK_SPACING): path = svgpathtools.parse_path(open(fname).read()) # input: svg based on a 100px / m image def next_t(path, t, dist): p = path.point(t) L = path.length() # t += 1.0 / np.abs(path.derivative(t)) itr = 0 while itr < 20: itr += 1 p1 = path.point(t) err = np.abs(p1 - p) - dist d1 = path.derivative(t) if np.abs(err) < 1e-5: return t, p1, d1 / np.abs(d1) derr = np.abs(d1) * L # do a step in Newton's method (clipped because some of the # gradients in the curve are really small) t -= np.clip(err / derr, -1e-2, 1e-2) t = np.clip(t, 0, 1) return t, p, d1 / np.abs(d1) d0 = path.derivative(0) pts = [[path.point(0), d0 / np.abs(d0)]] t = 0 while t < 1: t, p, d = next_t(path, t, spacing) pts.append([p, d]) return pts
def get_symbol_geometries(svg_paths): symbol_geometries = [] for svg_path in svg_paths: # split svg path into it's continuous parts # split: without the M's in front, the first one is an empty string pathSegs_strs_split = svg_path.d().split("M")[1:] # with the M's back in front pathSegs_strs = ["M " + s for s in pathSegs_strs_split] cont_paths = [svgpathtools.parse_path(x) for x in pathSegs_strs] # 2 different cases are handled here for now: 0 holes, 1 hole if len(cont_paths) > 2: print("This case of nested polygons isn't handled yet") exit(1) symbol_geometry = [] for cp in cont_paths: contour_points = get_points_continuous_path_part(cp) symbol_geometry.append(contour_points) symbol_geometries.append(symbol_geometry) return symbol_geometries
def writeGcode(gcode_file, svg_path): gcodeString = "" doc = minidom.parse(svg_path) path_strings = [ path.getAttribute('d') for path in doc.getElementsByTagName('path') ] doc.unlink() # print the line draw commands for path_string in path_strings: p = parse_path(path_string) for i in range(0, int(p.length()), 2): div = (i / p.length()) point = p.point(div) x = point.real y = point.imag gcodeString += ("G00 X" + str(x) + " Y" + str(y)) + "\n" if (len(path_strings) > 1): gcodeString += "\nM00" gcodeString += "\nM30" f = open(gcode_file, "w") f.write(gcodeString) f.close()
def displaySVGPaths(pathList,*colors): #creates and saves an svf file displaying the input paths show_closed_discont=True import svgwrite from svgpathtools import Path, Line, CubicBezier # from time import strftime # from re import sub # dwg = svgwrite.Drawing('temporary_displaySVGPaths%s.svg'%time_date, size= ("%spx"%(int(xmax-xmin+1)), "%spx"%(int(ymax-ymin+1)))) # dwg = svgwrite.Drawing('temporary_displaySVGPaths%s.svg'%time_date) dwg = svgwrite.Drawing('temporary_displaySVGPaths.svg') dwg.add(dwg.rect(insert=(0, 0), size=('100%', '100%'), rx=None, ry=None, fill='white')) #add white background dc = 100/len(pathList) for i,p in enumerate(pathList): if isinstance(p,Path): startpt = p[0].start ps = path2str(p) elif isinstance(p,Line) or isinstance(p,CubicBezier): startpt = p.start ps = path2str(p) else: startpt = parse_path(p)[0].start ps = p if colors !=tuple(): dwg.add(dwg.path(ps, stroke=colors[0][i], fill='none')) else: dwg.add(dwg.path(ps, stroke=svgwrite.rgb(0+dc, 0+dc, 16, '%'), fill='none')) if show_closed_discont and isApproxClosedPath(p): startpt = (startpt.real,startpt.imag) dwg.add(dwg.circle(startpt,1, stroke='gray', fill='gray')) dwg.save()
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
def effect(self): namedView = self.document.getroot().find( inkex.addNS('namedview', 'sodipodi')) doc_units = namedView.get(inkex.addNS('document-units', 'inkscape')) #inkex.utils.debug("document unit is " + doc_units) self.options.threshold = self.svg.unittouu( str(self.options.threshold) + doc_units) unit_factor = 1.0 / self.svg.uutounit(1.0, self.options.unit) #inkex.utils.debug("unit_factor is " + str(unit_factor)) if self.options.threshold == 0: return for path in self.document.xpath("//svg:path", namespaces=inkex.NSS): try: parsed_path = svgpathtools.parse_path(path.attrib["d"]) #if not isclosedac(parsed_path): # continue if self.options.measure == "area": calc = parsed_path.area() #inkex.utils.debug(calc) #print calculated area with document units #inkex.utils.debug(str(self.options.threshold * (unit_factor * unit_factor))) #print threshold area with selected units if calc < (self.options.threshold * (unit_factor * unit_factor)): path.getparent().remove(path) else: #length calc = parsed_path.length() #inkex.utils.debug(calc) #print calculated area with document units #inkex.utils.debug(str(self.options.threshold * (unit_factor * unit_factor))) #print threshold area with selected units if calc < (self.options.threshold * unit_factor): path.getparent().remove(path) except: pass
def safe_parse_path(d): p = None try: p = parse_path(d) except: logger.debug('Parse d string {}... failed.'.format(d[:100])) return p
def __init__(self, **kwargs): super(byA_PDFGrid, self).__init__(self) self._onePDFSize = kwargs.get('PDFSize', None) if (self._onePDFSize is None): onePDFWidth = kwargs.get('PDFWidth', 21) onePDFHeight = kwargs.get('PDFHeight', 29.7) self._onePDFSize = [onePDFWidth, onePDFHeight] w = self._onePDFSize[0] h = self._onePDFSize[1] paths = list() rectpath = ("M0,0v{}v{}h{}h{}v{}v{}h{}z" "".format(0.5 * h, 0.5 * h, 0.5 * w, 0.5 * w, -0.5 * h, -0.5 * h, -0.5 * w)) paths.append(rectpath) for id, x in enumerate([1, 1.1]): tickpath = ("M0,{}l{},{}M0,{}l{},{}M{},0l{},{}M{},{}0l{},{}" "".format(x, x, -x, h - x, x, x, w - x, x, x, w - x, h, x, -x)) paths.append(tickpath) parsedPath = parse_path(paths[0] + paths[1] + paths[2]) self._onePDFSheetPath = byA_Path() for p in parsedPath: assert (isinstance(p, Line)) p1 = byA_Point(c=p.start) p2 = byA_Point(c=p.end) self._onePDFSheetPath.append(byA_Line(P1=p1, P2=p2)) self._allPDFSheetPaths = None self._freeze("byA_PDFGrid")
def fillSvg(svg_path, svg_d, width=750, height=750): p = parse_path(svg_d) intersections = [] for i in range(height, 0, -5): newline = Line(complex(0, i), complex(100000, i)) intersect = p.intersect(newline) # print(intersect) indiv_sections = [] if (intersect): for (T1, seg1, t1), (T2, seg2, t2) in intersect: point = p.point(T1) if point: point_tuple = (point.real, point.imag) # print(point_tuple) indiv_sections.append(point_tuple) # p.append(newline) print(indiv_sections) pairs = list( zip(indiv_sections, indiv_sections[1:] + indiv_sections[:1])) del pairs[1::2] for pair in pairs: x0 = pair[0][0] x1 = pair[1][0] y = pair[0][1] # print("( "+ x0 + ", " + y + "), (" + x1 + ", " + y + ")") betweenLine = Line(complex(x0, y), complex(x1, y)) p.append(betweenLine) disvg(p)
def slide(which, dur, along): # Now we know that 'along' is being used as a motion path, rewrite the # path so that it starts at the origin. # XX: for debugging, might be useful to wrap it in a <g> with a transform # that reverses this, so it still appears in the same place in the # document? along_elem = GLOBAL_STATE.by_id[along] assert along_elem.tag == "{http://www.w3.org/2000/svg}path" path = svgpathtools.parse_path(along_elem.attrib["d"]) new_path = path.translated(-path[0].start) assert new_path[0].start == 0 + 0j along_elem.attrib["d"] = new_path.d() # Then add the actual animateMotion tag return add_SMIL_tag( "animateMotion", E("mpath", href="#" + along), href="#" + which, dur=dur, # Easing calcMode="spline", keyPoints="0; 1", keyTimes="0; 1", keySplines="0.43 0.02 0.72 0.93", # Start from whereever it is right now, not the initial position additive="sum", # And after it arrives, it should stay there fill="freeze", )
def get_verts_and_codes(svg_list): ''' parse into x,y coordinates and output list of lists of coordinates ''' lines = [] Verts = [] Codes = [] for stroke_ind, stroke in enumerate(svg_list): x = [] y = [] parsed = parse_path(stroke) for i, p in enumerate(parsed): if i != len(parsed) - 1: # last line segment x.append(p.start.real) y.append(p.start.imag) else: x.append(p.start.real) y.append(p.start.imag) x.append(p.end.real) y.append(p.end.imag) lines.append(zip(x, y)) verts, codes = polyline_pathmaker(lines) Verts.append(verts) Codes.append(codes) return Verts, Codes
def writeGcode(gcode_file, svg_path, repetitions, fill): gcodeString = "" returnString = "" skip_paths = [] if fill: try: skip_paths = toolPathSVG(svg_path, repetitions) except: returnString = "Couldn't fill svg. Try breaking it into multiple paths." print(returnString) # print(returnString) doc = minidom.parse(svg_path) path_strings = [ path.getAttribute('d') for path in doc.getElementsByTagName('path') ] print(skip_paths) doc.unlink() # try : # fillSvg(svg_path, path_strings[0]) # doc = minidom.parse(svg_path) # path_strings = [path.getAttribute('d') for path # in doc.getElementsByTagName('path')] # doc.unlink() # except : # print("filling failed") vertices = [] # print the line draw commands idx = 0 for path_string in path_strings: p = parse_path(path_string) for i in range(0, int(p.length()), 2): div = (i / p.length()) point = p.point(div) x = point.real y = point.imag v = (x, y) vertices.append(v) gcodeString += ("G00 X" + str(x) + " Y" + str(y)) + "\n" if len(path_strings) > 1: if (fill and idx in skip_paths) or not fill: gcodeString += "M00\n" idx += 1 gcodeString += "\nM30" f = open(gcode_file, "w") f.write(gcodeString) f.close() return returnString
def find_paths(doc, convert_circles_to_paths=True, convert_ellipses_to_paths=True, convert_lines_to_paths=True, convert_polylines_to_paths=True, convert_polygons_to_paths=True, convert_rectangles_to_paths=True): # Use minidom to extract path strings from input SVG pathnodes = doc.getElementsByTagName('path') paths = [dom2dict(el) for el in pathnodes] d_strings = [el['d'] for el in paths] # Use minidom to extract polyline strings from input SVG, convert to # path strings, add to list if convert_polylines_to_paths: plinnodes = doc.getElementsByTagName('polyline') plins = [dom2dict(el) for el in plinnodes] d_strings += [polyline2pathd(pl['points']) for pl in plins] pathnodes.extend(plinnodes) # Use minidom to extract polygon strings from input SVG, convert to # path strings, add to list if convert_polygons_to_paths: pgonnodes = doc.getElementsByTagName('polygon') pgons = [dom2dict(el) for el in pgonnodes] d_strings += [polygon2pathd(pg['points']) for pg in pgons] pathnodes.extend(pgonnodes) if convert_lines_to_paths: linenodes = doc.getElementsByTagName('line') lines = [dom2dict(el) for el in linenodes] d_strings += [ ('M' + l['x1'] + ' ' + l['y1'] + 'L' + l['x2'] + ' ' + l['y2']) for l in lines ] pathnodes.extend(linenodes) if convert_ellipses_to_paths: ellipsenodes = doc.getElementsByTagName('ellipse') ellipses = [dom2dict(el) for el in ellipsenodes] d_strings += [ellipse2pathd(e) for e in ellipses] pathnodes.extend(ellipsenodes) if convert_circles_to_paths: circlenodes = doc.getElementsByTagName('circle') circles = [dom2dict(el) for el in circlenodes] d_strings += [ellipse2pathd(c) for c in circles] pathnodes.extend(circlenodes) if convert_rectangles_to_paths: rectanglenodes = doc.getElementsByTagName('rect') rectangles = [dom2dict(el) for el in rectanglenodes] d_strings += [rect2pathd(r) for r in rectangles] pathnodes.extend(rectanglenodes) path_list = [parse_path(d) for d in d_strings] return pathnodes, path_list
def envelope2coords(self, path_envelope): pp_envelope = svgpathtools.parse_path(path_envelope) c0 = complex2tulpe(pp_envelope[0].start) c1 = complex2tulpe(pp_envelope[1].start) c2 = complex2tulpe(pp_envelope[2].start) c3 = complex2tulpe(pp_envelope[3].start) return [c0, c1, c2, c3]
def get_simple_paths(text): """ :param text: :return: """ sentence_lengths = get_sentence_lengths(text) path_str = plot_lengths(sentence_lengths) return parse_path(path_str)
def transform_paths(stage_svg): """Inkscapes deep ungroup doesn't handle paths with inline matrix transforms well. Transform them here instead""" root = ET.fromstring(stage_svg.read_bytes(), parser=XML_PARSER) for path in root.findall(".//path[@transform]", root.nsmap): trans_matrix = parse_transform(path.attrib.pop("transform")) svg_path = parse_path(path.attrib["d"]) path.attrib["d"] = transform(svg_path, trans_matrix).d() stage_svg.write_bytes(ET.tostring(root))
def __init__(self, p_id: str, p_attrs: Dict[str, str]): ''' ''' # the 'id' of a svg <path> definition self.p_id = p_id # and the attributes of the <path> self.p_attrs = p_attrs # the transformation of the svg_path_d to a svgpathtools 'path' self.svg_path = svgpathtools.parse_path(self.p_attrs['d'])
def debug_overlay_path(page_offset, line_height, line): line_y = page_offset + (line * line_height) mid = page_offset + ((line - 0.5) * line_height) buffer = line_height / determinate_ratio top = page_offset if line == 1 else mid - buffer bottom = mid + (line_height / 2) if line == 15 else mid + buffer p = f"M 0,{top} L 345,{top} L 345,{bottom} L 0,{bottom} L 0,{top} M 0,{line_y} L 345,{line_y}" return svgpathtools.parse_path(p)
def parseCCW(self): orig_path = parse_path(self.string) #fix degenerate segments here for i,seg in enumerate(orig_path): if abs(seg.start-seg.end) < 1: del orig_path[i] orig_path[i].start = orig_path[i-1].end if isCCW(orig_path,self.center): return orig_path else: return reversePath(orig_path)
def compoundPath2simplePath(srcDir, desDir): svgFiles = sorted(glob.glob(srcDir + "*.svg")) svg_attribute = {u'height': u'256px', u'width': u'256px'} for svgFileName in svgFiles: svgFile = get_path_strings(svgFileName) svgMList = svgFile[0].split('M') svgMList = svgMList[1:] svgMList = ['M' + MEle for MEle in svgMList] svgPathList = [parse_path(MEle) for MEle in svgMList] wsvg(svgPathList, svg_attributes=svg_attribute, filename=desDir + '/' + svgFileName)
def splitSingleLine(start_end, unitLength, toPoint=False): ''' return strings :param start_end: :param unitLength: :return: ''' if UB.pointEquals(start_end[0], start_end[1]): return [] pathStr = getStraightPath(start_end) path = parse_path(pathStr) try: l = path.length() if l > unitLength: paths = [] t = unitLength / l ts = 0 te = t for i in range(int(math.ceil(l / unitLength))): seg = Line(path.point(ts), path.point(te)) p = Path(seg) if toPoint: paths.append([ UB.getPointFromComplex(path.point(ts)), UB.getPointFromComplex(path.point(te)) ]) else: paths.append(p.d()) ts += t te += t te = min(1, te) if toPoint: paths.append([ UB.getPointFromComplex(path.point(te)), UB.getPointFromComplex(path.point(1)) ]) else: paths.append( getStraightPath([ UB.getPointFromComplex(path.point(te)), UB.getPointFromComplex(path.point(1)) ])) return paths if toPoint: return [start_end] else: return [pathStr] except Exception as e: print(start_end, "something wrong with the splitLine", e) return []
def writeDxf(svgName, dxfName, kerfWidth=None, chordHeight=None, globalRadius=None): svgRoot = xml.etree.ElementTree.parse(svgName).getroot() svgHeight = toFloat(svgRoot.attrib['height']) kerfWidth = 0.0 if kerfWidth is None else kerfWidth blueRects = [] dxfDoc = ezdxf.new('R12') dxfSpace = dxfDoc.modelspace() for elem in svgRoot.iter(): attr = elem.attrib style = toStyleDict(attr.get('style', '')) radius = attr.get('stroke-width', '1.0') radius = toFloat(style.get('stroke-width', radius)) stroke = attr.get('stroke', '') stroke = style.get('stroke', stroke).lower() isRed = stroke in ('#f00', '#ff0000', 'red') isGreen = stroke in ('#0f0', '#00ff00', 'lime') isBlue = stroke in ('#00f', '#0000ff', 'blue') isRect = elem.tag == '{http://www.w3.org/2000/svg}rect' isPath = elem.tag == '{http://www.w3.org/2000/svg}path' isCircle = elem.tag == '{http://www.w3.org/2000/svg}circle' if isBlue and isRect: w = toFloat(attr['width']) h = toFloat(attr['height']) x = toFloat(attr['x']) y = svgHeight - toFloat(attr['y']) - h blueRects.append((x, y, w, h, radius)) if isRed or isGreen: offset = kerfWidth / (-2.0 if isRed else 2.0) if isPath: d = svgpathtools.parse_path(attr.get('d')) shape = ezdxf.math.Shape2d() radii = [] for segment in d: x = segment.start.real y = svgHeight - segment.start.imag shape.append((x, y)) radii.append(getRadius((x, y), globalRadius, radius, blueRects)) writeRoundedPolygon(dxfSpace, shape, radii, offset, chordHeight) if isCircle: x = toFloat(attr['cx']) y = svgHeight - toFloat(attr['cy']) r = toFloat(attr['r']) writeCircle(dxfSpace, (x,y), r+offset, chordHeight) dxfDoc.saveas(dxfName)
def convert_path(path, x0, xfactor, y0, yfactor, steps): p = svgpath.parse_path(path) res = [] xlast = -np.inf for i in range(steps): t = i / (steps - 1) pt = p.point(t) x = (pt.real - x0) * xfactor y = (pt.imag - y0) * yfactor # only add points that advance forward on x axis if x > xlast: res.append((x, y)) xlast = x return np.asarray(res, dtype="float32")
def process_svg(fname, step_size=0.2, preview=False): strokes = [] title, _ = os.path.basename(fname).rsplit('.', 1) svg = etree.parse(fname).getroot() # convert to absolute points for ch in svg.findall('.//{}path'.format(ns)): data = ch.attrib['d'] path = parse_path(data) for segment in path: points = [to_coord(segment.point(x)) for x in np.arange(0, 1 + step_size, step_size)] # merge strokes that are essentially one stroke if strokes and approx_eq(strokes[-1][-1], points[0]): strokes[-1].extend(points[1:]) else: strokes.append(points) # draw it out if preview: im = Image.new('RGB', (2000, 2000)) draw = ImageDraw.Draw(im) for stroke in strokes: for start, end in zip(stroke, stroke[1:]): if start == end: continue draw.line([start, end], fill=(255, 255, 255)) im.save('preview/{}.png'.format(title), 'PNG') # convert to offsets and end-of-stroke # where 1 -> the last point in the stroke data = [] while strokes: stroke = strokes.pop(0) for start, end in zip(stroke, stroke[1:]): if start == end: continue sx, sy = start ex, ey = end delta_x, delta_y = ex - sx, ey - sy data.append((delta_x, delta_y, 0)) if strokes: stroke_end = stroke[-1] stroke_start = strokes[0][0] sx, sy = stroke_end ex, ey = stroke_start delta_x, delta_y = ex - sx, ey - sy data.append((delta_x, delta_y, 1)) return data
def showBoundingBoxes(self): style = { 'stroke-width': 2, 'stroke': '#0000ff', 'fill': 'none', 'opacity': .5 } for key, part in self.inventory: path = parse_path(part['d']) x0, x1, y0, y1 = path.bbox() s = 'M{0},{1} L{0},{3} L{2},{3} L{2},{1} Z'.format(x0, y0, x1, y1) self._add(s, style) return self
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 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 svg2pathlist(SVGfileLocation): doc = minidom.parse(SVGfileLocation) # parseString also exists # Use minidom to extract path strings from input SVG path_strings = [p.getAttribute('d') for p in doc.getElementsByTagName('path')] # Use minidom to extract polyline strings from input SVG, convert to path strings, add to list path_strings += [polylineStr2pathStr(p.getAttribute('points')) for p in doc.getElementsByTagName('polyline')] # Use minidom to extract polygon strings from input SVG, convert to path strings, add to list path_strings += [polylineStr2pathStr(p.getAttribute('points')) + 'z' for p in doc.getElementsByTagName('polygon')] # currently choosing to ignore line objects (assuming... all lines are fixes for non-overlapping mergers?) # Use minidom to extract line strings from input SVG, convert to path strings, and add them to list path_strings += ['M' + p.getAttribute('x1') + ' ' + p.getAttribute( 'y1') + 'L' + p.getAttribute('x2') + ' ' + p.getAttribute('y2') for p in doc.getElementsByTagName('line')] doc.unlink() return [parse_path(ps) for ps in path_strings]
def svg2rings(SVGfileLocation): global already_warned_having_trouble_extracting_ring_colors already_warned_having_trouble_extracting_ring_colors = False def getStroke(elem): #get 'stroke' attribute fom xml object troubleFlag=False stroke = elem.getAttribute('stroke') #sometimes this works if stroke=='': style = elem.getAttribute('style') hexstart = style.find('stroke') if hexstart==-1: troubleFlag=True else: temp = style[hexstart:] try: stroke = re.search(re.compile('\#[a-fA-F0-9]*'),temp).group() except: troubleFlag=True stroke='' if troubleFlag: global already_warned_having_trouble_extracting_ring_colors if not already_warned_having_trouble_extracting_ring_colors: already_warned_having_trouble_extracting_ring_colors = True opt.warnings_output_on.dprint("Warning: Having trouble extracting hex colors from svg. Hopefully this will not matter as the palette check will fix the colors.") return stroke.upper() example_center = r'<line fill="none" stroke="#0000FF" stroke-width="0.15" x1="246.143" y1="380.017" x2="246.765" y2="380.856"/>' doc = minidom.parse(SVGfileLocation) # parseString also exists #Find the center counter = 0 centerFound = False for elem in doc.getElementsByTagName('line'): if getStroke(elem) == colordict['center']: center = 0.5*float(elem.getAttribute('x1'))+0.5*float(elem.getAttribute('x2')) + 0.5*float(elem.getAttribute('y1'))*1j +0.5*float(elem.getAttribute('y2'))*1j rad = Radius(center) centerFound = True break else: counter += 1 if counter>0 and not centerFound: opt.warnings_output_on.dprint("[Warning:] No line objects in the svg were found matching the center color (%s). Now searching for lines of a color closer to center color than other colors."%counter) for elem in doc.getElementsByTagName('line'): if closestColor(getStroke(elem),colordict) == colordict['center']: center = 0.5*float(elem.getAttribute('x1'))+0.5*float(elem.getAttribute('x2')) + 0.5*float(elem.getAttribute('y1'))*1j +0.5*float(elem.getAttribute('y2'))*1j rad = Radius(center) centerFound = True counter -=1 break if counter>0: #center found but counter>0 opt.warnings_output_on.dprint("[Warning:] There are %s disconnected lines in this SVG not matching the center color. They will be ignored."%counter) try: center.real #test if center exists (should be a complex number object) except: try: if counter == 0: #Is there a path with the center color? for elem in doc.getElementsByTagName('path')+doc.getElementsByTagName('polyline')+doc.getElementsByTagName('polygon'): if getStroke(elem) == colordict['center']: if elem in doc.getElementsByTagName('path'): obtype = 'path'; pathstr = elem.getAttribute('d') elif elem in doc.getElementsByTagName('polyline'): obtype = 'polyline'; pathstr = polylineStr2pathStr(elem.getAttribute('points')) else: obtype = 'polygon'; pathstr = polylineStr2pathStr(elem.getAttribute('points')) + 'z' centerpath = parse_path(pathstr) start,end = centerpath.point(0.25),centerpath.point(0.75) x1,x2,y1,y2 = start.real,end.real,start.imag,end.imag newelem = r'<line fill="none" stroke="%s" stroke-width="0.05" stroke-miterlimit="10" x1="%s" y1="%s" x2="%s" y2="%s"/>'%(colordict['center'],x1,y1,x2,y2) raise Exception("Center of sample should be marked by line of color %s, but no lines are present in svg. There is a %s with the center color, however. Open the svg file in a text editor and you should be able to find '%s' somewhere... replace it with '%s'"%(colordict['center'],obtype,elem,newelem)) else: for elem in doc.getElementsByTagName('path')+doc.getElementsByTagName('polyline')+doc.getElementsByTagName('polygon'): if closestColor(getStroke(elem),colordict) == colordict['center']: if elem in doc.getElementsByTagName('path'): obtype = 'path'; pathstr = elem.getAttribute('d') elif elem in doc.getElementsByTagName('polyline'): obtype = 'polyline'; pathstr = polylineStr2pathStr(elem.getAttribute('points')) else: obtype = 'polygon'; pathstr = polylineStr2pathStr(elem.getAttribute('points')) + 'z' centerpath = parse_path(pathstr) start,end = centerpath.point(0.25),centerpath.point(0.75) x1,x2,y1,y2 = start.real,end.real,start.imag,end.imag newelem = r'<line fill="none" stroke="%s" stroke-width="0.05" stroke-miterlimit="10" x1="%s" y1="%s" x2="%s" y2="%s"/>'%(colordict['center'],x1,y1,x2,y2) raise Exception("Center of sample should be marked by line of color %s, but no lines are present in svg. There is a path with color close to the center color, however. Open the svg file in a text editor and you should be able to find '%s' somewhere... replace it with '%s'"%(colordict['center'],obtype,elem,newelem)) else: raise Exception('Center of sample should be marked by line of color %s, but no lines are present in svg. There were no paths or polylines or polygons of a similar color either. Looks like you did not mark the center. Open your svg in a text editor and search for something that looks like (with different x1, x2, y1, y2 values) \n%s\n'%(colordict['center'],example_center)) except: raise Exception('No center found searching line element with (color) stroke = %s. Open your svg in a text editor and search for something that looks like (with different x1, x2, y1, y2 values) \n%s\n'%(colordict['center'],example_center)) #Use minidom to extract path strings from input SVG opt.basic_output_on.dprint("Extracting path_strings from SVG... ",'nr') path_strings = [(p.getAttribute('d'),getStroke(p),p.parentNode.getAttribute('id'),p.toxml()) for p in doc.getElementsByTagName('path')] #Use minidom to extract polyline strings from input SVG, convert to path strings, add to list path_strings += [(polylineStr2pathStr(p.getAttribute('points')),getStroke(p),p.parentNode.getAttribute('id'),p.toxml()) for p in doc.getElementsByTagName('polyline')] #Use minidom to extract polygon strings from input SVG, convert to path strings, add to list path_strings += [(polylineStr2pathStr(p.getAttribute('points'))+'z',getStroke(p),p.parentNode.getAttribute('id'),p.toxml()) for p in doc.getElementsByTagName('polygon')] #currently choosing to ignore line objects (assuming... all lines are fixes for non-overlapping mergers?) ##Use minidom to extract line strings from input SVG, convert to path strings, and add them to list #line_strings = [('M' + p.getAttribute('x1') + ' ' +p.getAttribute('y1') + 'L'+p.getAttribute('x2') + ' ' + p.getAttribute('y2'),getStroke(p), p.parentNode.getAttribute('id')) for p in doc.getElementsByTagName('line')] doc.unlink() opt.basic_output_on.dprint("Done.") # #(first attempt to) Check for stray points, if any found, delete them # i=0 # count_popped_points = 0 # while i < len(path_strings): # if path_strings[i][0].count(',')<2: # path_strings.pop(i) # count_popped_points+=1 # opt.full_output_on.dprint("Removed a stray point: path_string[%s][0] = %s"%(i,path_strings[i][0])) # i +=1 # opt.basic_output_on.dprint("Removed %s stray points in path_string stage. Continuing..."%count_popped_points) #Convert path_strings to ring objects opt.basic_output_on.dprint("Converting path strings to Ring objects. This could take a minute... ",'nr') path2ring_start_time = current_time() ring_list = [] paths_of_unknown_orientation = [] for i in range(len(path_strings)): orig_path = parse_path(path_strings[i][0]) try: ### DEBUG ONLY (REMOVE ALL OF TRY/EXCEPT) orig_path[0] except: if len(path_strings[i][0].split(','))<3: opt.full_output_on.dprint("Found (and skipped) single point path: %s"%path_strings[i][0]) continue else: raise #fix degenerate segments here for index,seg in enumerate(orig_path): if abs(seg.start-seg.end) < 1: old_end = seg.end old_start = seg.start opt.full_output_on.dprint("Found degenerate seg in path %s: %s"%(i,seg)) del orig_path[index] if index == len(orig_path): #deleted last path orig_path[-1].end = old_end elif index == 0: orig_path[0].start=old_start else: orig_path[index].start = orig_path[index-1].end opt.full_output_on.dprint("Deleted above degenerate segment and fixed gap.") #check for doubled over segments nostupidsfound = False while not nostupidsfound and len(orig_path)>1: for indst in range(len(orig_path)-1): if (orig_path[indst] == orig_path[indst+1] or orig_path[indst] == orig_path[indst+1].reversed()): del orig_path[indst+1] opt.warnings_output_on.dprint("[Warning:] "+"stupidsfound"*50) # raise Exception() #you should remove this Exception and everything will run smoothly else: nostupidsfound = True #Now fix the orientation if path is not CCW (w.r.t. center) try: path_is_ccw = isCCW(orig_path,center) except: if opt.manually_fix_orientations: print("\n[Manually Fix Orientations:] As currently drawn, the " "path starts at the green node/segment and ends at the " "red (if you don't see one of these nodes, it's likely " "cause the path is very short and thus they are on top " "of each other). Does the path in " "'temporary_4manualOrientation.svg' appear to be drawn " "in a clockwise fashion?") if len(orig_path) == 1: disp_paths = [orig_path] disp_path_colors = ['blue'] elif len(orig_path) == 2: disp_paths = [Path(orig_path[0]),Path(orig_path[1])] disp_path_colors = ['green','red'] elif len(orig_path) > 2: disp_paths = [Path(orig_path[0]),Path(orig_path[1:-1]),Path(orig_path[-1])] disp_path_colors = ['green','blue','red'] else: raise Exception("This path is empty... this should never happen. Tell Andy.") for ring in ring_list: disp_paths.append(ring.path) disp_path_colors.append('black') nodes = [orig_path[0].start,orig_path[-1].end]+[center] node_colors = ['green','red']+[colordict['center']] disvg(disp_paths, disp_path_colors, nodes=nodes, node_colors=node_colors, filename='temporary_4manualOrientation.svg') path_is_ccw = askUserOrientation() #svg display reverses orientation so a respose of 'yes' means path is actually ccw and thus sets path_is_ccw = True if path_is_ccw == 'remove': print("OK, this path will be ignored... moving onto the rest.") continue # raise Exception("The manually_fix_orientations feature is not yet setup. If you need this, ask Andy; it shouldn't take him long.") else: path_is_ccw = opt.when_orientation_cannot_be_determined_assume_CCW paths_of_unknown_orientation.append(path_strings[i]) if not path_is_ccw: path2record = orig_path.reversed() opt.full_output_on.dprint("Path %s was not oriented CCW, but is now."%i) else: path2record = orig_path ring_list.append(Ring(path_strings[i][0],path_strings[i][1],path_strings[i][2],rad,path2record,xml=path_strings[i][3])) opt.full_output_on.dprint("Ring %s ok"%i) if len(paths_of_unknown_orientation)>0: from andysmod import ifelse fashion = ifelse(opt.when_orientation_cannot_be_determined_assume_CCW,'Counterclockwise','Clockwise') ccw_warning = "[Warning:] Unable to determine orientation of %s paths. This is likely because some paths in this sample are far from being convex. I assumed that these paths were traced in a %s fashion (to change this assumption, set 'when_orientation_cannot_be_determined_assume_CCW = %s' in options. If this assumption is false, either the program will crash or the transect will be visibly messed up in the output 'xxx_transects.svg' (where xxx is the input svg's filename sans extension)."%(len(paths_of_unknown_orientation),fashion,not opt.when_orientation_cannot_be_determined_assume_CCW) opt.warnings_output_on.dprint(ccw_warning) if len(paths_of_unknown_orientation)>1: opt.warnings_output_on.dprint("If think you were not consistent tracing in either CCW or CW fashion (or don't get good output from this file) then set 'manually_fix_orientations = True' in options.") #done extracting rings from svg opt.basic_output_on.dprint("Done (in %s)."%format_time(current_time()-path2ring_start_time)) opt.basic_output_on.dprint("Completed extracting rings from SVG. %s rings detected."%len(ring_list)) return center, ring_list
def find_ring_areas(sorted_ring_list, center, svgfile): # This codeblock creates a one pixel by one pixel square Ring object to # act as the core - it is recorded in CP. note: perimeter should be found # as a path and treated at a ring already csd = centerSquare(center) csd_path = parse_path(csd) if not isCCW(csd_path, center): csd_path = reversePath(csd_path) # path_string, color, brooke_tag, center center_square = Ring(csd, colordict['center'], 'not recorded', Radius(center), csd_path) # Converts the sorted_ring_list into a CP_Boolset of # complete rings each containing their IRs completeRing_CPB = CP_BoolSet() innerRing = center_square innerRing_index = -1 for ring_index, ring in enumerate(sorted_ring_list): # when next closed ring found create CompleteRing object, # then set all inbetween rings to be IRs if ring.isClosed(): completeRing_CPB.append(CompleteRing(innerRing, ring)) for inc_ring in sorted_ring_list[innerRing_index+1:ring_index]: ir = IncompleteRing(inc_ring) ir.set_inner(innerRing) ir.set_outer(ring) completeRing_CPB.cpUpdate(CompleteRing(ir.innerCR_ring, ir.outerCR_ring, ir)) innerRing = ring innerRing_index = ring_index # Check (once again) that the last sort-suggested # boundary is closed and correctly colored bdry_ring = sorted_ring_list[-1] if bdry_ring.color != colordict['boundary'] or not bdry_ring.isClosed(): ###DEBUG Why is this necessary? Isn't this fixed earlier? if sorted_ring_list[-1] == max(sorted_ring_list, key=lambda r: r.maxR): sorted_ring_list[-1].color = colordict['boundary'] else: raise Exception("Last ring in sorted sorted_ring_list was not " "closed... this should be outer perimeter.") # identify the center square created earlier as the core completeRing_CPB[0].isCore = True basic_output_on.dprint("All complete_ring objects created and all " "incomple_ring objects created (and stored inside " "the appropriate complete_ring object).") # complete the incomplete rings CP_start_time = start_time_ring_completion = current_time() for count,cp in enumerate(completeRing_CPB): if count: CP_start_time = current_time() try: cp.completeIncompleteRings() except: if outputTroubledCPs: paths = ([cp.inner.path, cp.outer.path] + [ir.ring.path for ir in cp.ir_boolset] + [sorted_ring_list[-1].path]) path_colors = ([cp.inner.color, cp.outer.color] + [ir.ring.color for ir in cp.ir_boolset] + [colordict['boundary']]) center_line = Line(cp.inner.center-1,cp.inner.center+1) svgname = os_path.join(output_directory_debug,"trouble_"+svgfile) disvg(paths,path_colors,lines=[center_line],filename=svgname) print("Simplified SVG created containing troublesome section " "(troublesome incomplete ring colored {}) and saved " "to:\n{}".format(colordict['safe1'], svgname)) raise mes = ("{}/{} complete rings finished. This CP = {} | Total ET = {}" "".format(count + 1, len(completeRing_CPB), format_time(current_time()-CP_start_time), format_time(current_time()-start_time_ring_completion))) showCurrentFilesProgress.dprint(mes) outputFile = os_path.join(output_directory, svgfile + '_completeRing_info.csv') with open(outputFile, "wt") as out_file: out_file.write("complete ring index, type, # of IRs contained, minR, " "maxR, aveR, area, area Ignoring IRs\n") cp_index = 0 for cp in completeRing_CPB: cp_index += 1 out_file.write(cp.info(cp_index,colordict) + '\n') # Create SVG showing areas (i.e. showing completed paths) if create_SVG_showing_area_paths: basic_output_on.dprint("Attempting to create SVG showing completed " "paths used for area computation...", 'nr') svgpaths = [] svgcolors = [] for cp in completeRing_CPB: svgpaths.append(cp.inner.path) svgcolors.append(cp.inner.color) for ir in cp.ir_boolset: svgpaths.append(ir.completed_path) svgcolors.append(ir.ring.color) if cp.outer.color == colordict['boundary']: svgpaths.append(cp.outer.path) svgcolors.append(cp.outer.color) tmp = svgfile[0:len(svgfile)-4] + "_area_paths" + ".svg" svgname = os_path.join(output_directory_debug, tmp) wsvg(svgpaths, svgcolors, filename=svgname) basic_output_on.dprint("Done.")
def fix_svg(ring_list, center, svgfile): # Discard inappropriately short rings from options4rings import appropriate_ring_length_minimum opt.basic_output_on.dprint("\nChecking for inappropriately short " "rings...",'nr') tmp_len = len(ring_list) short_rings = [idx for idx, ring in enumerate(ring_list) if ring.path.length() < appropriate_ring_length_minimum] opt.basic_output_on.dprint("Done (%s inappropriately short rings " "found)."%len(short_rings)) if short_rings: if opt.create_svg_highlighting_inappropriately_short_rings: opt.basic_output_on.dprint("\nCreating svg highlighting " "inappropriately short rings...",'nr') paths = [parse_path(r.string) for r in ring_list] colors = [r.color for r in ring_list] nodes = [ring_list[idx].path.point(0.5) for idx in short_rings] center_line = [Line(center-1,center+1)] tmp = svgfile[0:len(svgfile)-4] + "_short-rings.svg" shortrings_svg_filename = os_path.join(opt.output_directory, tmp) disvg(paths + [center_line], colors + [opt.colordict['center']], nodes=nodes, filename=shortrings_svg_filename) args = appropriate_ring_length_minimum, shortrings_svg_filename mes = ("Done. SVG created highlighting short rings by placing a " "node at each short ring's midpoint. Note: since these " "rings are all under {} pixels in length, they may be hard " "to see and may even be completely covered by the node. " "SVG file saved to:\n{}").format(*args) opt.basic_output_on.dprint(mes) if opt.dont_remove_closed_inappropriately_short_rings: shortest_ring_length = min([r.path.length() for r in [ring_list[k] for k in short_rings if ring_list[k].isClosed()]]) open_short_rings = [idx for idx in short_rings if not ring_list[idx].isClosed()] num_short_and_closed = len(short_rings)-len(open_short_rings) if num_short_and_closed: sug_tol = (opt.tol_isNear * shortest_ring_length / opt.appropriate_ring_length_minimum) warn("{} inappropriately short closed rings detected (and not " "removed as " "dont_remove_closed_inappropriately_short_rings = True). " " You should probably decrease tol_isNear to something " "less than {} and restart this file." "".format(num_short_and_closed, sug_tol)) short_rings = open_short_rings if opt.remove_inappropriately_short_rings: opt.basic_output_on.dprint("\nRemoving inappropriately short " "rings...",'nr') ring_list = [ring for idx,ring in enumerate(ring_list) if idx not in short_rings] opt.basic_output_on.dprint("Done (%s inappropriately short rings " "removed)."%(tmp_len - len(ring_list))) else: warn("{} inappropriately short rings were found, but " "remove_inappropriately_short_rings is set to False." "".format(len(ring_list))) print("") # Remove very short segments from rings def _remove_seg(path, _seg_idx, _newjoint): _new_path = [x for x in path] pathisclosed = path[-1].end == path[0].start # stretch next segment if _seg_idx != len(path) - 1 or pathisclosed: old_bpoints = _new_path[(_seg_idx + 1) % len(path)].bpoints() new_bpoints = (_newjoint,) + old_bpoints[1:] _new_path[(_seg_idx + 1) % len(path)] = bezier_segment(*new_bpoints) # stretch previous segment if _seg_idx != 0 or pathisclosed: old_bpoints = _new_path[(_seg_idx - 1) % len(path)].bpoints() new_bpoints = old_bpoints[:-1] + (_newjoint,) _new_path[(_seg_idx - 1) % len(path)] = bezier_segment(*new_bpoints) # delete the path to be removed del _new_path[_seg_idx] return _new_path if opt.min_relative_segment_length: for r_idx, r in enumerate(ring_list): min_seg_length = r.path.length() * opt.min_relative_segment_length new_path = [s for s in r.path] its = 0 flag = False while its < len(r.path): its += 1 for seg_idx, seg in enumerate(new_path): if seg.length() < min_seg_length: flag = True if seg == new_path[-1] and not r.path.isclosed(): newjoint = seg.end elif seg == new_path[0].start and not r.path.isclosed(): newjoint = seg.start else: newjoint = seg.point(0.5) new_path = _remove_seg(new_path, seg_idx, newjoint) break else: break if flag: ring_list[r_idx].path = Path(*new_path) # Close approximately closed rings for r in ring_list: r.fixClosure() # Palette check from svg2rings import palette_check ring_list = palette_check(ring_list) # Check for and fix inconsistencies in closedness of rings from svg2rings import closedness_consistency_check ring_list = closedness_consistency_check(ring_list) # Remove self-intersections in open rings if opt.remove_self_intersections: rsi_start_time = current_time() fixable_count = 0 print("Checking for self-intersections...") bad_rings = [] for r_idx, r in enumerate(ring_list): if r.path.end == r.path.start: continue first_half = r.path.cropped(0, 0.4) second_half = r.path.cropped(0.6, 1) middle_peice = r.path.cropped(0.4, 0.6) inters = first_half.intersect(second_half) if inters: if len(inters) > 1: Ts = [info1[0] for info1, info2 in inters] bad_rings.append((r_idx, Ts)) continue else: fixable_count += 1 T1, seg1, t1 = inters[0][0] T2, seg2, t2 = inters[0][1] if not opt.force_remove_self_intersections: print("Self-intersection detected!") greenpart = first_half.cropped(0, T1) redpart = second_half.cropped(T2, 1) new_path = [seg for seg in first_half.cropped(T1, 1)] new_path += [seg for seg in middle_peice] new_path += [seg for seg in second_half.cropped(0, T2)] new_path = Path(*new_path) if opt.force_remove_self_intersections: dec = True else: print("Should I remove the red and green sections?") disvg([greenpart, new_path, redpart], ['green', 'blue', 'red'], nodes=[seg1.point(t1)]) dec = inputyn() if dec: r.path = new_path print("Path cropped.") else: print("OK... I hope things work out for you.") if bad_rings: paths = [r.path for r in ring_list] colors = [r.color for r in ring_list] center_line = Line(center-1, center+1) nodes = [] for r_idx, Ts in bad_rings: for T in Ts: nodes.append(ring_list[r_idx].path.point(T)) colors[r_idx] = opt.colordict['safe2'] node_colors = [opt.colordict['safe1']] * len(nodes) tmp = svgfile[0:len(svgfile)-4] + "_SelfIntersections.svg" fixed_svg_filename = os_path.join(opt.output_directory, tmp) disvg(paths + [center_line], colors + [opt.colordict['center']], nodes=nodes, node_colors=node_colors, filename=fixed_svg_filename) tmp_mes = ( "Some rings contained multiple self-intersections, you better " "take a look. They must be fixed manually (in Inkscape or " "Adobe Illustrator). An svg has been output highlighting the " "rings which must be fixed manually (and the points where the " "self-intersections occur). Fix the highlighted rings and " "replace your old svg with the fixed one (the colors/circles " "used to highlight the intersections will be fixed/removed " "automatically).\n Output svg saved to:\n" "{}".format(fixed_svg_filename)) raise Exception(tmp_mes) et = format_time(current_time()-rsi_start_time) print("Done fixing self-intersections ({} detected in {})." "".format(fixable_count, et)) # Check that all rings are smooth (search for kinks and round them) if opt.smooth_rings: print("Smoothing paths...") bad_rings = [] for r_idx, r in enumerate(ring_list): args = (r.path, opt.maxjointsize, opt.tightness, True) r.path = smoothed_path(*args) still_kinky_list = kinks(r.path) if still_kinky_list: bad_rings.append((r_idx, still_kinky_list)) # If unremovable kinks exist, tell user to remove them manually if opt.ignore_unremovable_kinks or not bad_rings: opt.rings_may_contain_unremoved_kinks = False else: paths = [r.path for r in ring_list] colors = [r.color for r in ring_list] center_line = Line(center-1, center+1) nodes = [] for r_idx, kink_indices in bad_rings: for idx in kink_indices: kink = ring_list[r_idx].path[idx].start nodes.append(kink) colors[r_idx] = opt.colordict['safe2'] node_colors = [opt.colordict['safe1']] * len(nodes) tmp = svgfile[0:len(svgfile)-4] + "_kinks.svg" fixed_svg_filename = os_path.join(opt.output_directory, tmp) disvg(paths + [center_line], colors + [opt.colordict['center']], nodes=nodes, node_colors=node_colors, filename=fixed_svg_filename) raise Exception("Some rings contained kinks which could not be " "removed automatically. " "They must be fixed manually (in inkscape or " "adobe illustrator). An svg has been output " "highlighting the rings which must be fixed " "manually (and the points where the " "kinks occur). Fix the highlighted " "rings and replace your old svg with the fixed " "one (the colors/circles used to highlight the " "kinks will be fixed/removed automatically).\n" "Output svg saved to:\n" "%s" % fixed_svg_filename) print("Done smoothing paths.") # Check for overlapping ends in open rings if opt.check4overlappingends: print("Checking for overlapping ends (that do not intersect)...") bad_rings = [] for r_idx, r in enumerate(ring_list): if r.path.isclosed(): continue startpt = r.path.start endpt = r.path.end path_wo_start = r.path.cropped(.1, 1) path_wo_end = r.path.cropped(0, .9) start_is_outwards = isPointOutwardOfPath(startpt, path_wo_start) end_is_outwards = isPointOutwardOfPath(endpt, path_wo_end) if start_is_outwards: bad_rings.append((r_idx, 0, start_is_outwards)) if end_is_outwards: bad_rings.append((r_idx, 1, end_is_outwards)) if bad_rings: paths = [r.path for r in ring_list] colors = [r.color for r in ring_list] center_line = Line(center-1, center+1) for r_idx, endbin, segts in bad_rings: colors[r_idx] = opt.colordict['safe2'] # indicator lines indicator_lines = [] for r_idx, endbin, segts in bad_rings: bad_path = ring_list[r_idx].path endpt = bad_path.point(endbin) for bad_seg_idx, bad_t in segts: bad_pt = bad_path[bad_seg_idx].point(bad_t) indicator_lines.append(Line(bad_pt, endpt)) indicator_cols = [opt.colordict['safe1']] * len(indicator_lines) tmp = svgfile[0:len(svgfile)-4] + "_OverlappingEnds.svg" fixed_svg_filename = os_path.join(opt.output_directory, tmp) disvg(paths + [center_line] + indicator_lines, colors + [opt.colordict['center']] + indicator_cols, filename=fixed_svg_filename) bad_ring_count = len(set(x[0] for x in bad_rings)) tmp_mes = ( "Detected {} rings with overlapping (but not intersecting) " "ends. They must be fixed manually (e.g. in Inkscape or " "Adobe Illustrator). An svg has been output highlighting the " "rings which must be fixed manually. Fix the highlighted " "rings, remove the,indicator lines added, and replace your " "old svg with the fixed one (the colors used to highlight the " "intersections will be fixed automatically).\nIf the " "indicator lines do not appear to be normal to the ring, this " "is possibly caused by a very short path segment. In this " "case, you may want to try increasing " "min_relative_segment_length in options and running again.\n" "Output svg saved to:\n" "{}".format(bad_ring_count, fixed_svg_filename)) raise Exception(tmp_mes) print("Done checking for overlapping ends.") # Trim paths with high curvature (i.e. curly) ends if opt.remove_curly_ends: print("Trimming high curvature ends...") for ring in ring_list: if ring.isClosed(): continue # 90 degree turn in distance of opt.tol_isNear tol_curvature = 2**.5 / opt.tol_isNear #####Tolerance # Find any points within tol_isNear of start and end that have # curvature equal to tol_curvature, later we'll crop them off from svgpathtools import real, imag from svgpathtools.polytools import polyroots01 def icurvature(seg, kappa): """returns a list of t-values such that 0 <= t<= 1 and seg.curvature(t) = kappa.""" z = seg.poly() x, y = real(z), imag(z) dx, dy = x.deriv(), y.deriv() ddx, ddy = dx.deriv(), dy.deriv() p = kappa**2*(dx**2 + dy**2)**3 - (dx*ddy - ddx*dy)**2 return polyroots01(p) # For first segment startseg = ring.path[0] ts = icurvature(startseg, tol_curvature) ts = [t for t in ts if startseg.length(t1=t) < opt.tol_isNear] if ts: T0 = ring.path.t2T(0, max(ts)) else: T0 = 0 # For last segment endseg = ring.path[-1] ts = icurvature(endseg, tol_curvature) ts = [t for t in ts if endseg.length(t0=t) < opt.tol_isNear] if ts: T1 = ring.path.t2T(-1, min(ts)) else: T1 = 1 # crop (if necessary) if T0 != 0 or T1 != 1: ring.path = ring.path.cropped(T0, T1) print("Done trimming.") # Check that there are no rings end outside the boundary ring (note # intersection removal in next step makes this sufficient) print("Checking for rings outside boundary ring...") boundary_ring = max([r for r in ring_list if r.isClosed()], key=lambda rgn: rgn.maxR) outside_mark_indices = [] for idx, r in enumerate(ring_list): if r is not boundary_ring: pt_outside_bdry = center + 2*boundary_ring.maxR if not ptInsideClosedPath(r.path[0].start, pt_outside_bdry, boundary_ring.path): outside_mark_indices.append(idx) if outside_mark_indices: ring_list = [r for i,r in enumerate(ring_list) if i not in outside_mark_indices] warn("%s paths were found outside the boundary path and will be " "ignored." % len(outside_mark_indices)) print("Done removing rings outside of boundary ring.") # Remove intersections (between distinct rings) if opt.rings_may_contain_intersections: print("Removing intersections (between distinct rings)...") from noIntersections4rings import remove_intersections_from_rings opt.basic_output_on.dprint("Now attempting to find and remove all " "intersections from rings (this will take a " "long time)...") intersection_removal_start_time = current_time() ring_list, intersection_count, overlappingClosedRingPairs = \ remove_intersections_from_rings(ring_list) if not overlappingClosedRingPairs: tot_ov_time = format_time(current_time() - intersection_removal_start_time) opt.basic_output_on.dprint("Done (in just %s). Found and removed %s " "intersections." % (tot_ov_time, intersection_count)) else: # fixed_paths = [parse_path(r.string) for r in ring_list] fixed_paths = [r.path for r in ring_list] fixed_colors = [r.color for r in ring_list] center_line = Line(center-1, center+1) nodes = [] for i, j in overlappingClosedRingPairs: fixed_colors[i] = opt.colordict['safe1'] fixed_colors[j] = opt.colordict['safe2'] inters = pathXpathIntersections(ring_list[i].path,ring_list[j].path) nodes += [inter[0].point(inter[2]) for inter in inters] tmp = svgfile[0:len(svgfile)-4] + "_ClosedRingsOverlap.svg" fixed_svg_filename = os_path.join(opt.output_directory, tmp) disvg(fixed_paths + [center_line], fixed_colors + [opt.colordict['center']], nodes=nodes, filename=fixed_svg_filename) raise Exception("Found %s pair(s) over overlapping closed rings. " "They must be fixed manually (in inkscape or " "adobe illustrator). An svg has been output " "highlighting the rings which must be separated " "manually (and the points where they intersect). " "Fix the highlighted rings and replace your old " "svg with the fixed one (the colors/circles used " "to highlight the intersections will be " "fixed/removed automatically).\n" "Output svg saved to:\n" "%s" % (len(overlappingClosedRingPairs), fixed_svg_filename)) # Output a fixed SVG that is (hopefully) how this SVG would be if humans # were perfect from options4rings import create_fixed_svg if create_fixed_svg: opt.basic_output_on.dprint("Now creating a fixed svg file...", 'nr') fixed_paths = [r.path for r in ring_list] fixed_colors = [r.color for r in ring_list] center_line = Line(center - 1, center + 1) tmp = svgfile[0:len(svgfile)-4] + "_fixed.svg" fixed_svg_filename = os_path.join(opt.output_directory, tmp) wsvg(fixed_paths + [center_line], fixed_colors + [opt.colordict['center']], filename=fixed_svg_filename) opt.basic_output_on.dprint("Done. SVG file saved to:\n" "%s" % fixed_svg_filename)