svg linearization
[mirrors/Programs.git] / python / svg_linear / svg_gcode.py
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
This page took 0.343554 seconds and 4 git commands to generate.