Commit | Line | Data |
---|---|---|
7f1b0236 TM |
1 | import numpy |
2 | import svg.path | |
3 | import lxml.etree as ET | |
4 | ||
5 | ''' | |
6 | SVG path linearization library (eg. for SVG2gcode use) | |
7 | Made by Tomas 'Harvie' Mudrunka 2018 | |
8 | ||
9 | heavily based on SVG compression library | |
10 | released under GPLv3? | |
11 | by Gen Del Raye (July 22, 2014): https://pypi.org/project/SVGCompress/ | |
12 | ''' | |
13 | ||
14 | class SVGcode: | |
15 | ||
16 | def __init__(self, svg_file): | |
17 | ''' | |
18 | Read svg from path 'svg_file' (e.g. 'test/test_vector.svg') | |
19 | ''' | |
20 | #self.filename, self.extension = svg_file.split('.') # Get filename and extension | |
21 | #assert self.extension == 'svg', 'File must be an svg' | |
22 | try: | |
23 | self.figure_data = ET.parse(svg_file) # Parse svg data as xml | |
24 | except ET.XMLSyntaxError, e: | |
25 | ''' | |
26 | Large svgs may trigger lxml's 'excessive depth in document' exception | |
27 | ''' | |
28 | warnings.warn('lxml error: %s - Trying huge xml parser' %(e.message)) | |
29 | huge_parser = ET.XMLParser(huge_tree = True) | |
30 | self.figure_data = ET.parse(svg_file, parser = huge_parser) | |
31 | self.root = self.figure_data.getroot() # Root object in svg | |
32 | ||
33 | def find_paths(self): | |
34 | ''' | |
35 | Find and parse nodes in the xml that correspond to paths in the svg | |
36 | ''' | |
37 | tag_prefix = '{*}' | |
38 | self.path_nodes = self.root.findall('.//%spath' %(tag_prefix)) | |
39 | self.paths = list() | |
40 | self.paths = [svg.path.parse_path(p.attrib.get('d', 'M 0,0 z')) for p in self.path_nodes] | |
41 | ||
42 | def linearize_paths(self, curve_fidelity = 10): | |
43 | ''' | |
44 | Turn svg paths into discrete lines | |
45 | Inputs: | |
46 | curve_fidelity(int) - number of lines with which to approximate curves | |
47 | in svg. Higher values necessitates longer computation time. | |
48 | ''' | |
49 | self.linear_coords = [self.linearize(p, curve_fidelity) for p in self.paths] | |
50 | ||
51 | ||
52 | def linearize_line(self, segment, n_interpolate = None): | |
53 | ''' | |
54 | Turn svg line into set of coordinates by returning | |
55 | start and end coordinates of the line segment. | |
56 | n_interpolate is only used for consistency of use | |
57 | with linearize_curve() | |
58 | ''' | |
59 | return numpy.array([segment.start, segment.end]) | |
60 | ||
61 | def linearize_curve(self, segment, n_interpolate = 10): | |
62 | ''' | |
63 | Estimate svg curve (e.g. Bezier, Arc, etc.) using | |
64 | a set of n discrete lines. n_interpolate sets the | |
65 | number of discrete lines per curve. | |
66 | ''' | |
67 | interpolation_pts = numpy.linspace(0, 1, n_interpolate, endpoint = False)[1:] | |
68 | interpolated = numpy.zeros(n_interpolate + 1, dtype = complex) | |
69 | interpolated[0] = segment.start | |
70 | interpolated[-1] = segment.end | |
71 | for i, pt in enumerate(interpolation_pts): | |
72 | interpolated[i + 1] = segment.point(pt) | |
73 | return interpolated | |
74 | ||
75 | def complex2coord(self, complexnum): | |
76 | return (complexnum.real, complexnum.imag) | |
77 | ||
78 | def linearize(self, path, n_interpolate = 10): | |
79 | segmenttype2func = {'CubicBezier': self.linearize_curve, | |
80 | 'Line': self.linearize_line, | |
81 | 'QuadraticBezier': self.linearize_curve, | |
82 | 'Arc': self.linearize_curve} | |
83 | ''' | |
84 | More sophisticated linearization option | |
85 | compared to endpts2line(). | |
86 | Turn svg path into discrete coordinates | |
87 | with number of coordinates per curve set | |
88 | by n_interpolate. i.e. if n_interpolate | |
89 | is 100, each curve is approximated by | |
90 | 100 discrete lines. | |
91 | ''' | |
92 | segments = path._segments | |
93 | complex_coords = list() | |
94 | for segment in segments: | |
95 | # Output coordinates for each segment, minus last point (because | |
96 | # point is same as first point of next segment) | |
97 | segment_type = type(segment).__name__ | |
98 | segment_linearize = segmenttype2func[segment_type] | |
99 | linearized = segment_linearize(segment, n_interpolate) | |
100 | complex_coords.extend(linearized[:-1]) | |
101 | # Append last point of final segment to close the polygon | |
102 | complex_coords.append(linearized[-1]) | |
103 | return [self.complex2coord(complexnum) for complexnum in complex_coords] # Output list of (x, y) tuples |