svg linearization
authorTomas Mudrunka <tomas@mudrunka.cz>
Tue, 21 Aug 2018 02:10:20 +0000 (04:10 +0200)
committerTomas Mudrunka <tomas@mudrunka.cz>
Tue, 21 Aug 2018 02:10:20 +0000 (04:10 +0200)
python/svg_linear/svg_gcode.py [new file with mode: 0644]
python/svg_linear/test.py [new file with mode: 0644]

diff --git a/python/svg_linear/svg_gcode.py b/python/svg_linear/svg_gcode.py
new file mode 100644 (file)
index 0000000..c1a168f
--- /dev/null
@@ -0,0 +1,103 @@
+import numpy
+import svg.path
+import lxml.etree as ET
+
+'''
+SVG path linearization library (eg. for SVG2gcode use)
+       Made by Tomas 'Harvie' Mudrunka 2018
+
+heavily based on SVG compression library
+       released under GPLv3?
+       by Gen Del Raye (July 22, 2014): https://pypi.org/project/SVGCompress/
+'''
+
+class SVGcode:
+
+       def __init__(self, svg_file):
+               '''
+               Read svg from path 'svg_file' (e.g. 'test/test_vector.svg')
+               '''
+               #self.filename, self.extension = svg_file.split('.') # Get filename and extension
+               #assert self.extension == 'svg', 'File must be an svg'
+               try:
+                       self.figure_data = ET.parse(svg_file) # Parse svg data as xml
+               except ET.XMLSyntaxError, e:
+                       '''
+                       Large svgs may trigger lxml's 'excessive depth in document' exception
+                       '''
+                       warnings.warn('lxml error: %s - Trying huge xml parser' %(e.message))
+                       huge_parser = ET.XMLParser(huge_tree = True)
+                       self.figure_data = ET.parse(svg_file, parser = huge_parser)
+               self.root = self.figure_data.getroot() # Root object in svg
+
+       def find_paths(self):
+               '''
+               Find and parse nodes in the xml that correspond to paths in the svg
+               '''
+               tag_prefix = '{*}'
+               self.path_nodes = self.root.findall('.//%spath' %(tag_prefix))
+               self.paths = list()
+               self.paths = [svg.path.parse_path(p.attrib.get('d', 'M 0,0 z')) for p in self.path_nodes]
+
+       def linearize_paths(self, curve_fidelity = 10):
+               '''
+               Turn svg paths into discrete lines
+               Inputs:
+                       curve_fidelity(int) - number of lines with which to approximate curves
+                               in svg. Higher values necessitates longer computation time.
+               '''
+               self.linear_coords = [self.linearize(p, curve_fidelity) for p in self.paths]
+
+
+       def linearize_line(self, segment, n_interpolate = None):
+               '''
+               Turn svg line into set of coordinates by returning
+               start and end coordinates of the line segment.
+               n_interpolate is only used for consistency of use
+               with linearize_curve()
+               '''
+               return numpy.array([segment.start, segment.end])
+
+       def linearize_curve(self, segment, n_interpolate = 10):
+               '''
+               Estimate svg curve (e.g. Bezier, Arc, etc.) using
+               a set of n discrete lines. n_interpolate sets the
+               number of discrete lines per curve.
+               '''
+               interpolation_pts = numpy.linspace(0, 1, n_interpolate, endpoint = False)[1:]
+               interpolated = numpy.zeros(n_interpolate + 1, dtype = complex)
+               interpolated[0] = segment.start
+               interpolated[-1] = segment.end
+               for i, pt in enumerate(interpolation_pts):
+                               interpolated[i + 1] = segment.point(pt)
+               return interpolated
+
+       def complex2coord(self, complexnum):
+               return (complexnum.real, complexnum.imag)
+
+       def linearize(self, path, n_interpolate = 10):
+               segmenttype2func = {'CubicBezier': self.linearize_curve,
+                                   'Line': self.linearize_line,
+                                   'QuadraticBezier': self.linearize_curve,
+                                   'Arc': self.linearize_curve}
+               '''
+               More sophisticated linearization option
+               compared to endpts2line().
+               Turn svg path into discrete coordinates
+               with number of coordinates per curve set
+               by n_interpolate. i.e. if n_interpolate
+               is 100, each curve is approximated by
+               100 discrete lines.
+               '''
+               segments = path._segments
+               complex_coords = list()
+               for segment in segments:
+                       # Output coordinates for each segment, minus last point (because
+                       # point is same as first point of next segment)
+                       segment_type = type(segment).__name__
+                       segment_linearize = segmenttype2func[segment_type]
+                       linearized = segment_linearize(segment, n_interpolate)
+                       complex_coords.extend(linearized[:-1])
+               # Append last point of final segment to close the polygon
+               complex_coords.append(linearized[-1])
+               return [self.complex2coord(complexnum) for complexnum in complex_coords] # Output list of (x, y) tuples
diff --git a/python/svg_linear/test.py b/python/svg_linear/test.py
new file mode 100644 (file)
index 0000000..727279a
--- /dev/null
@@ -0,0 +1,15 @@
+from svg_gcode import *
+file="test/test_vector.svg"
+file="/home/harvie/Work/Designs/cnc/loga/mardera.svg"
+file="/home/harvie/Work/Designs/cnc/loga/logo_spoje.svg"
+comp_instance = SVGcode(file)
+comp_instance.find_paths()                        # Extract path coordinates
+comp_instance.linearize_paths(curve_fidelity = 10)     # Convert paths into polygons
+#comp_instance.write(outputfile = "hrv.svg")
+#print(comp_instance.linear_coords[0])
+for path in comp_instance.linear_coords:
+       p = path[0]
+       print("g0 x%s y%s"%(str(p[0]),str(p[1])))
+       for p in path:
+               print("g1 x%s y%s"%(str(p[0]),str(p[1])))
+       print "( ---------- cut-here ---------- )"
This page took 0.21627 seconds and 4 git commands to generate.