- Tool Isolation - new feature that allow to isolate interiors of polygons (holes in polygons). It is possible that the isolation to be reported as successful (internal limitations) but some interiors to not be isolated. This way the user get to fix the isolation by doing an extra isolation.
700 lines
23 KiB
Python
700 lines
23 KiB
Python
# ##########################################################
|
|
# FlatCAM: 2D Post-processing for Manufacturing #
|
|
# http://flatcam.org #
|
|
# Author: Juan Pablo Caram (c) #
|
|
# Date: 12/18/2015 #
|
|
# MIT Licence #
|
|
# #
|
|
# SVG Features supported: #
|
|
# * Groups #
|
|
# * Rectangles (w/ rounded corners) #
|
|
# * Circles #
|
|
# * Ellipses #
|
|
# * Polygons #
|
|
# * Polylines #
|
|
# * Lines #
|
|
# * Paths #
|
|
# * All transformations #
|
|
# #
|
|
# Reference: www.w3.org/TR/SVG/Overview.html #
|
|
# ##########################################################
|
|
|
|
# import xml.etree.ElementTree as ET
|
|
from svg.path import Line, Arc, CubicBezier, QuadraticBezier, parse_path
|
|
# from svg.path.path import Move
|
|
# from svg.path.path import Close
|
|
import svg.path
|
|
from shapely.geometry import LineString, MultiLineString
|
|
from shapely.affinity import skew, affine_transform, rotate
|
|
import numpy as np
|
|
|
|
from appParsers.ParseFont import *
|
|
|
|
log = logging.getLogger('base2')
|
|
|
|
|
|
def svgparselength(lengthstr):
|
|
"""
|
|
Parse an SVG length string into a float and a units
|
|
string, if any.
|
|
|
|
:param lengthstr: SVG length string.
|
|
:return: Number and units pair.
|
|
:rtype: tuple(float, str|None)
|
|
"""
|
|
|
|
integer_re_str = r'[+-]?[0-9]+'
|
|
number_re_str = r'(?:[+-]?[0-9]*\.[0-9]+(?:[Ee]' + integer_re_str + ')?' + r')|' + \
|
|
r'(?:' + integer_re_str + r'(?:[Ee]' + integer_re_str + r')?)'
|
|
length_re_str = r'(' + number_re_str + r')(em|ex|px|in|cm|mm|pt|pc|%)?'
|
|
|
|
match = re.search(length_re_str, lengthstr)
|
|
if match:
|
|
return float(match.group(1)), match.group(2)
|
|
|
|
return
|
|
|
|
|
|
def path2shapely(path, object_type, res=1.0):
|
|
"""
|
|
Converts an svg.path.Path into a Shapely
|
|
Polygon or LinearString.
|
|
|
|
:param path: svg.path.Path instance
|
|
:param object_type:
|
|
:param res: Resolution (minimum step along path)
|
|
:return: Shapely geometry object
|
|
:rtype : Polygon
|
|
:rtype : LineString
|
|
"""
|
|
|
|
points = []
|
|
geometry = []
|
|
|
|
rings = []
|
|
closed = False
|
|
|
|
for component in path:
|
|
# Line
|
|
if isinstance(component, Line):
|
|
start = component.start
|
|
x, y = start.real, start.imag
|
|
if len(points) == 0 or points[-1] != (x, y):
|
|
points.append((x, y))
|
|
end = component.end
|
|
points.append((end.real, end.imag))
|
|
continue
|
|
|
|
# Arc, CubicBezier or QuadraticBezier
|
|
if isinstance(component, Arc) or \
|
|
isinstance(component, CubicBezier) or \
|
|
isinstance(component, QuadraticBezier):
|
|
|
|
# How many points to use in the discrete representation.
|
|
length = component.length(res / 10.0)
|
|
# steps = int(length / res + 0.5)
|
|
steps = int(length) * 2
|
|
|
|
# solve error when step is below 1,
|
|
# it may cause other problems, but LineString needs at least two points
|
|
if steps == 0:
|
|
steps = 1
|
|
|
|
frac = 1.0 / steps
|
|
|
|
# print length, steps, frac
|
|
for i in range(steps):
|
|
point = component.point(i * frac)
|
|
x, y = point.real, point.imag
|
|
if len(points) == 0 or points[-1] != (x, y):
|
|
points.append((x, y))
|
|
end = component.point(1.0)
|
|
points.append((end.real, end.imag))
|
|
continue
|
|
|
|
# Move
|
|
if isinstance(component, svg.path.Move):
|
|
if not points:
|
|
continue
|
|
else:
|
|
rings.append(points)
|
|
if closed is False:
|
|
points = []
|
|
else:
|
|
closed = False
|
|
start = component.start
|
|
x, y = start.real, start.imag
|
|
points = [(x, y)]
|
|
continue
|
|
|
|
closed = False
|
|
|
|
# Close
|
|
if isinstance(component, svg.path.Close):
|
|
if not points:
|
|
continue
|
|
else:
|
|
rings.append(points)
|
|
points = []
|
|
closed = True
|
|
continue
|
|
log.warning("I don't know what this is: %s" % str(component))
|
|
continue
|
|
|
|
# if there are still points in points then add them to the last ring
|
|
|
|
if points:
|
|
rings.append(points)
|
|
try:
|
|
rings = MultiLineString(rings)
|
|
except Exception as e:
|
|
log.debug("ParseSVG.path2shapely() MString --> %s" % str(e))
|
|
return None
|
|
|
|
if len(rings) > 0:
|
|
if len(rings) == 1 and not isinstance(rings, MultiLineString):
|
|
# Polygons are closed and require more than 2 points
|
|
if Point(rings[0][0]).almost_equals(Point(rings[0][-1])) and len(rings[0]) > 2:
|
|
geo_element = Polygon(rings[0])
|
|
else:
|
|
geo_element = LineString(rings[0])
|
|
else:
|
|
try:
|
|
geo_element = Polygon(rings[0], rings[1:])
|
|
except Exception:
|
|
coords = []
|
|
for line in rings:
|
|
coords.append(line.coords[0])
|
|
coords.append(line.coords[1])
|
|
try:
|
|
geo_element = Polygon(coords)
|
|
except Exception:
|
|
geo_element = LineString(coords)
|
|
geometry.append(geo_element)
|
|
return geometry
|
|
|
|
|
|
def svgrect2shapely(rect, n_points=32):
|
|
"""
|
|
Converts an SVG rect into Shapely geometry.
|
|
|
|
:param rect: Rect Element
|
|
:type rect: xml.etree.ElementTree.Element
|
|
:param n_points: number of points to approximate circles
|
|
:type n_points: int
|
|
:return: shapely.geometry.polygon.LinearRing
|
|
"""
|
|
w = svgparselength(rect.get('width'))[0]
|
|
h = svgparselength(rect.get('height'))[0]
|
|
x_obj = rect.get('x')
|
|
if x_obj is not None:
|
|
x = svgparselength(x_obj)[0]
|
|
else:
|
|
x = 0
|
|
y_obj = rect.get('y')
|
|
if y_obj is not None:
|
|
y = svgparselength(y_obj)[0]
|
|
else:
|
|
y = 0
|
|
rxstr = rect.get('rx')
|
|
rystr = rect.get('ry')
|
|
|
|
if rxstr is None and rystr is None: # Sharp corners
|
|
pts = [
|
|
(x, y), (x + w, y), (x + w, y + h), (x, y + h), (x, y)
|
|
]
|
|
|
|
else: # Rounded corners
|
|
rx = 0.0 if rxstr is None else svgparselength(rxstr)[0]
|
|
ry = 0.0 if rystr is None else svgparselength(rystr)[0]
|
|
|
|
n_points = int(n_points / 4 + 0.5)
|
|
t = np.arange(n_points, dtype=float) / n_points / 4
|
|
|
|
x_ = (x + w - rx) + rx * np.cos(2 * np.pi * (t + 0.75))
|
|
y_ = (y + ry) + ry * np.sin(2 * np.pi * (t + 0.75))
|
|
|
|
lower_right = [(x_[i], y_[i]) for i in range(n_points)]
|
|
|
|
x_ = (x + w - rx) + rx * np.cos(2 * np.pi * t)
|
|
y_ = (y + h - ry) + ry * np.sin(2 * np.pi * t)
|
|
|
|
upper_right = [(x_[i], y_[i]) for i in range(n_points)]
|
|
|
|
x_ = (x + rx) + rx * np.cos(2 * np.pi * (t + 0.25))
|
|
y_ = (y + h - ry) + ry * np.sin(2 * np.pi * (t + 0.25))
|
|
|
|
upper_left = [(x_[i], y_[i]) for i in range(n_points)]
|
|
|
|
x_ = (x + rx) + rx * np.cos(2 * np.pi * (t + 0.5))
|
|
y_ = (y + ry) + ry * np.sin(2 * np.pi * (t + 0.5))
|
|
|
|
lower_left = [(x_[i], y_[i]) for i in range(n_points)]
|
|
|
|
pts = [(x + rx, y), (x - rx + w, y)] + \
|
|
lower_right + \
|
|
[(x + w, y + ry), (x + w, y + h - ry)] + \
|
|
upper_right + \
|
|
[(x + w - rx, y + h), (x + rx, y + h)] + \
|
|
upper_left + \
|
|
[(x, y + h - ry), (x, y + ry)] + \
|
|
lower_left
|
|
|
|
return Polygon(pts).buffer(0)
|
|
# return LinearRing(pts)
|
|
|
|
|
|
def svgcircle2shapely(circle):
|
|
"""
|
|
Converts an SVG circle into Shapely geometry.
|
|
|
|
:param circle: Circle Element
|
|
:type circle: xml.etree.ElementTree.Element
|
|
:return: Shapely representation of the circle.
|
|
:rtype: shapely.geometry.polygon.LinearRing
|
|
"""
|
|
# cx = float(circle.get('cx'))
|
|
# cy = float(circle.get('cy'))
|
|
# r = float(circle.get('r'))
|
|
cx = svgparselength(circle.get('cx'))[0] # TODO: No units support yet
|
|
cy = svgparselength(circle.get('cy'))[0] # TODO: No units support yet
|
|
r = svgparselength(circle.get('r'))[0] # TODO: No units support yet
|
|
|
|
# TODO: No resolution specified.
|
|
return Point(cx, cy).buffer(r)
|
|
|
|
|
|
def svgellipse2shapely(ellipse, n_points=64):
|
|
"""
|
|
Converts an SVG ellipse into Shapely geometry
|
|
|
|
:param ellipse: Ellipse Element
|
|
:type ellipse: xml.etree.ElementTree.Element
|
|
:param n_points: Number of discrete points in output.
|
|
:return: Shapely representation of the ellipse.
|
|
:rtype: shapely.geometry.polygon.LinearRing
|
|
"""
|
|
|
|
cx = svgparselength(ellipse.get('cx'))[0] # TODO: No units support yet
|
|
cy = svgparselength(ellipse.get('cy'))[0] # TODO: No units support yet
|
|
|
|
rx = svgparselength(ellipse.get('rx'))[0] # TODO: No units support yet
|
|
ry = svgparselength(ellipse.get('ry'))[0] # TODO: No units support yet
|
|
|
|
t = np.arange(n_points, dtype=float) / n_points
|
|
x = cx + rx * np.cos(2 * np.pi * t)
|
|
y = cy + ry * np.sin(2 * np.pi * t)
|
|
pts = [(x[i], y[i]) for i in range(n_points)]
|
|
|
|
return Polygon(pts).buffer(0)
|
|
# return LinearRing(pts)
|
|
|
|
|
|
def svgline2shapely(line):
|
|
"""
|
|
|
|
:param line: Line element
|
|
:type line: xml.etree.ElementTree.Element
|
|
:return: Shapely representation on the line.
|
|
:rtype: shapely.geometry.polygon.LinearRing
|
|
"""
|
|
|
|
x1 = svgparselength(line.get('x1'))[0]
|
|
y1 = svgparselength(line.get('y1'))[0]
|
|
x2 = svgparselength(line.get('x2'))[0]
|
|
y2 = svgparselength(line.get('y2'))[0]
|
|
|
|
return LineString([(x1, y1), (x2, y2)])
|
|
|
|
|
|
def svgpolyline2shapely(polyline):
|
|
|
|
ptliststr = polyline.get('points')
|
|
points = parse_svg_point_list(ptliststr)
|
|
|
|
return LineString(points)
|
|
|
|
|
|
def svgpolygon2shapely(polygon):
|
|
|
|
ptliststr = polygon.get('points')
|
|
points = parse_svg_point_list(ptliststr)
|
|
|
|
return Polygon(points).buffer(0)
|
|
# return LinearRing(points)
|
|
|
|
|
|
def getsvggeo(node, object_type, root=None):
|
|
"""
|
|
Extracts and flattens all geometry from an SVG node
|
|
into a list of Shapely geometry.
|
|
|
|
:param node: xml.etree.ElementTree.Element
|
|
:param object_type:
|
|
:return: List of Shapely geometry
|
|
:rtype: list
|
|
"""
|
|
if root is None:
|
|
root = node
|
|
|
|
kind = re.search('(?:\{.*\})?(.*)$', node.tag).group(1)
|
|
geo = []
|
|
|
|
# Recurse
|
|
if len(node) > 0:
|
|
for child in node:
|
|
subgeo = getsvggeo(child, object_type, root)
|
|
if subgeo is not None:
|
|
geo += subgeo
|
|
|
|
# Parse
|
|
elif kind == 'path':
|
|
log.debug("***PATH***")
|
|
P = parse_path(node.get('d'))
|
|
P = path2shapely(P, object_type)
|
|
# for path, the resulting geometry is already a list so no need to create a new one
|
|
geo = P
|
|
|
|
elif kind == 'rect':
|
|
log.debug("***RECT***")
|
|
R = svgrect2shapely(node)
|
|
geo = [R]
|
|
|
|
elif kind == 'circle':
|
|
log.debug("***CIRCLE***")
|
|
C = svgcircle2shapely(node)
|
|
geo = [C]
|
|
|
|
elif kind == 'ellipse':
|
|
log.debug("***ELLIPSE***")
|
|
E = svgellipse2shapely(node)
|
|
geo = [E]
|
|
|
|
elif kind == 'polygon':
|
|
log.debug("***POLYGON***")
|
|
poly = svgpolygon2shapely(node)
|
|
geo = [poly]
|
|
|
|
elif kind == 'line':
|
|
log.debug("***LINE***")
|
|
line = svgline2shapely(node)
|
|
geo = [line]
|
|
|
|
elif kind == 'polyline':
|
|
log.debug("***POLYLINE***")
|
|
pline = svgpolyline2shapely(node)
|
|
geo = [pline]
|
|
|
|
elif kind == 'use':
|
|
log.debug('***USE***')
|
|
# href= is the preferred name for this[1], but inkscape still generates xlink:href=.
|
|
# [1] https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use#Attributes
|
|
href = node.attrib['href'] if 'href' in node.attrib else node.attrib['{http://www.w3.org/1999/xlink}href']
|
|
ref = root.find(".//*[@id='%s']" % href.replace('#', ''))
|
|
if ref is not None:
|
|
geo = getsvggeo(ref, object_type, root)
|
|
|
|
else:
|
|
log.warning("Unknown kind: " + kind)
|
|
geo = None
|
|
|
|
# ignore transformation for unknown kind
|
|
if geo is not None:
|
|
# Transformations
|
|
if 'transform' in node.attrib:
|
|
trstr = node.get('transform')
|
|
trlist = parse_svg_transform(trstr)
|
|
# log.debug(trlist)
|
|
|
|
# Transformations are applied in reverse order
|
|
for tr in trlist[::-1]:
|
|
if tr[0] == 'translate':
|
|
geo = [translate(geoi, tr[1], tr[2]) for geoi in geo]
|
|
elif tr[0] == 'scale':
|
|
geo = [scale(geoi, tr[1], tr[2], origin=(0, 0))
|
|
for geoi in geo]
|
|
elif tr[0] == 'rotate':
|
|
geo = [rotate(geoi, tr[1], origin=(tr[2], tr[3]))
|
|
for geoi in geo]
|
|
elif tr[0] == 'skew':
|
|
geo = [skew(geoi, tr[1], tr[2], origin=(0, 0))
|
|
for geoi in geo]
|
|
elif tr[0] == 'matrix':
|
|
geo = [affine_transform(geoi, tr[1:]) for geoi in geo]
|
|
else:
|
|
raise Exception('Unknown transformation: %s', tr)
|
|
|
|
return geo
|
|
|
|
|
|
def getsvgtext(node, object_type, units='MM'):
|
|
"""
|
|
Extracts and flattens all geometry from an SVG node
|
|
into a list of Shapely geometry.
|
|
|
|
:param node: xml.etree.ElementTree.Element
|
|
:param object_type:
|
|
:return: List of Shapely geometry
|
|
:rtype: list
|
|
"""
|
|
kind = re.search('(?:\{.*\})?(.*)$', node.tag).group(1)
|
|
geo = []
|
|
|
|
# Recurse
|
|
if len(node) > 0:
|
|
for child in node:
|
|
subgeo = getsvgtext(child, object_type, units=units)
|
|
if subgeo is not None:
|
|
geo += subgeo
|
|
|
|
# Parse
|
|
elif kind == 'tspan':
|
|
current_attrib = node.attrib
|
|
txt = node.text
|
|
style_dict = {}
|
|
parrent_attrib = node.getparent().attrib
|
|
style = parrent_attrib['style']
|
|
|
|
try:
|
|
style_list = style.split(';')
|
|
for css in style_list:
|
|
style_dict[css.rpartition(':')[0]] = css.rpartition(':')[-1]
|
|
|
|
pos_x = float(current_attrib['x'])
|
|
pos_y = float(current_attrib['y'])
|
|
|
|
# should have used the instance from FlatCAMApp.App but how? without reworking everything ...
|
|
pf = ParseFont()
|
|
pf.get_fonts_by_types()
|
|
font_name = style_dict['font-family'].replace("'", '')
|
|
|
|
if style_dict['font-style'] == 'italic' and style_dict['font-weight'] == 'bold':
|
|
font_type = 'bi'
|
|
elif style_dict['font-weight'] == 'bold':
|
|
font_type = 'bold'
|
|
elif style_dict['font-style'] == 'italic':
|
|
font_type = 'italic'
|
|
else:
|
|
font_type = 'regular'
|
|
|
|
# value of 2.2 should have been 2.83 (conversion value from pixels to points)
|
|
# but the dimensions from Inkscape did not corelate with the ones after importing in FlatCAM
|
|
# so I adjusted this
|
|
font_size = svgparselength(style_dict['font-size'])[0] * 2.2
|
|
geo = [pf.font_to_geometry(txt,
|
|
font_name=font_name,
|
|
font_size=font_size,
|
|
font_type=font_type,
|
|
units=units,
|
|
coordx=pos_x,
|
|
coordy=pos_y)
|
|
]
|
|
|
|
geo = [(scale(g, 1.0, -1.0)) for g in geo]
|
|
except Exception as e:
|
|
log.debug(str(e))
|
|
else:
|
|
geo = None
|
|
|
|
# ignore transformation for unknown kind
|
|
if geo is not None:
|
|
# Transformations
|
|
if 'transform' in node.attrib:
|
|
trstr = node.get('transform')
|
|
trlist = parse_svg_transform(trstr)
|
|
# log.debug(trlist)
|
|
|
|
# Transformations are applied in reverse order
|
|
for tr in trlist[::-1]:
|
|
if tr[0] == 'translate':
|
|
geo = [translate(geoi, tr[1], tr[2]) for geoi in geo]
|
|
elif tr[0] == 'scale':
|
|
geo = [scale(geoi, tr[1], tr[2], origin=(0, 0))
|
|
for geoi in geo]
|
|
elif tr[0] == 'rotate':
|
|
geo = [rotate(geoi, tr[1], origin=(tr[2], tr[3]))
|
|
for geoi in geo]
|
|
elif tr[0] == 'skew':
|
|
geo = [skew(geoi, tr[1], tr[2], origin=(0, 0))
|
|
for geoi in geo]
|
|
elif tr[0] == 'matrix':
|
|
geo = [affine_transform(geoi, tr[1:]) for geoi in geo]
|
|
else:
|
|
raise Exception('Unknown transformation: %s', tr)
|
|
|
|
return geo
|
|
|
|
|
|
def parse_svg_point_list(ptliststr):
|
|
"""
|
|
Returns a list of coordinate pairs extracted from the "points"
|
|
attribute in SVG polygons and polyline's.
|
|
|
|
:param ptliststr: "points" attribute string in polygon or polyline.
|
|
:return: List of tuples with coordinates.
|
|
"""
|
|
|
|
pairs = []
|
|
last = None
|
|
pos = 0
|
|
i = 0
|
|
|
|
for match in re.finditer(r'(\s*,\s*)|(\s+)', ptliststr.strip(' ')):
|
|
|
|
val = float(ptliststr[pos:match.start()])
|
|
|
|
if i % 2 == 1:
|
|
pairs.append((last, val))
|
|
else:
|
|
last = val
|
|
|
|
pos = match.end()
|
|
i += 1
|
|
|
|
# Check for last element
|
|
val = float(ptliststr[pos:])
|
|
if i % 2 == 1:
|
|
pairs.append((last, val))
|
|
else:
|
|
log.warning("Incomplete coordinates.")
|
|
|
|
return pairs
|
|
|
|
|
|
def parse_svg_transform(trstr):
|
|
"""
|
|
Parses an SVG transform string into a list
|
|
of transform names and their parameters.
|
|
|
|
Possible transformations are:
|
|
|
|
* Translate: translate(<tx> [<ty>]), which specifies
|
|
a translation by tx and ty. If <ty> is not provided,
|
|
it is assumed to be zero. Result is
|
|
['translate', tx, ty]
|
|
|
|
* Scale: scale(<sx> [<sy>]), which specifies a scale operation
|
|
by sx and sy. If <sy> is not provided, it is assumed to be
|
|
equal to <sx>. Result is: ['scale', sx, sy]
|
|
|
|
* Rotate: rotate(<rotate-angle> [<cx> <cy>]), which specifies
|
|
a rotation by <rotate-angle> degrees about a given point.
|
|
If optional parameters <cx> and <cy> are not supplied,
|
|
the rotate is about the origin of the current user coordinate
|
|
system. Result is: ['rotate', rotate-angle, cx, cy]
|
|
|
|
* Skew: skewX(<skew-angle>), which specifies a skew
|
|
transformation along the x-axis. skewY(<skew-angle>), which
|
|
specifies a skew transformation along the y-axis.
|
|
Result is ['skew', angle-x, angle-y]
|
|
|
|
* Matrix: matrix(<a> <b> <c> <d> <e> <f>), which specifies a
|
|
transformation in the form of a transformation matrix of six
|
|
values. matrix(a,b,c,d,e,f) is equivalent to applying the
|
|
transformation matrix [a b c d e f]. Result is
|
|
['matrix', a, b, c, d, e, f]
|
|
|
|
Note: All parameters to the transformations are "numbers",
|
|
i.e. no units present.
|
|
|
|
:param trstr: SVG transform string.
|
|
:type trstr: str
|
|
:return: List of transforms.
|
|
:rtype: list
|
|
"""
|
|
trlist = []
|
|
|
|
assert isinstance(trstr, str)
|
|
trstr = trstr.strip(' ')
|
|
|
|
integer_re_str = r'[+-]?[0-9]+'
|
|
number_re_str = r'(?:[+-]?[0-9]*\.[0-9]+(?:[Ee]' + integer_re_str + ')?' + r')|' + \
|
|
r'(?:' + integer_re_str + r'(?:[Ee]' + integer_re_str + r')?)'
|
|
|
|
# num_re_str = r'[\+\-]?[0-9\.e]+' # TODO: Negative exponents missing
|
|
comma_or_space_re_str = r'(?:(?:\s+)|(?:\s*,\s*))'
|
|
translate_re_str = r'translate\s*\(\s*(' + \
|
|
number_re_str + r')(?:' + \
|
|
comma_or_space_re_str + \
|
|
r'(' + number_re_str + r'))?\s*\)'
|
|
scale_re_str = r'scale\s*\(\s*(' + \
|
|
number_re_str + r')' + \
|
|
r'(?:' + comma_or_space_re_str + \
|
|
r'(' + number_re_str + r'))?\s*\)'
|
|
skew_re_str = r'skew([XY])\s*\(\s*(' + \
|
|
number_re_str + r')\s*\)'
|
|
rotate_re_str = r'rotate\s*\(\s*(' + \
|
|
number_re_str + r')' + \
|
|
r'(?:' + comma_or_space_re_str + \
|
|
r'(' + number_re_str + r')' + \
|
|
comma_or_space_re_str + \
|
|
r'(' + number_re_str + r'))?\s*\)'
|
|
matrix_re_str = r'matrix\s*\(\s*' + \
|
|
r'(' + number_re_str + r')' + comma_or_space_re_str + \
|
|
r'(' + number_re_str + r')' + comma_or_space_re_str + \
|
|
r'(' + number_re_str + r')' + comma_or_space_re_str + \
|
|
r'(' + number_re_str + r')' + comma_or_space_re_str + \
|
|
r'(' + number_re_str + r')' + comma_or_space_re_str + \
|
|
r'(' + number_re_str + r')\s*\)'
|
|
|
|
while len(trstr) > 0:
|
|
match = re.search(r'^' + translate_re_str, trstr)
|
|
if match:
|
|
trlist.append([
|
|
'translate',
|
|
float(match.group(1)),
|
|
float(match.group(2)) if (match.group(2) is not None) else 0.0
|
|
])
|
|
trstr = trstr[len(match.group(0)):].strip(' ')
|
|
continue
|
|
|
|
match = re.search(r'^' + scale_re_str, trstr)
|
|
if match:
|
|
trlist.append([
|
|
'scale',
|
|
float(match.group(1)),
|
|
float(match.group(2)) if (match.group(2) is not None) else float(match.group(1))
|
|
])
|
|
trstr = trstr[len(match.group(0)):].strip(' ')
|
|
continue
|
|
|
|
match = re.search(r'^' + skew_re_str, trstr)
|
|
if match:
|
|
trlist.append([
|
|
'skew',
|
|
float(match.group(2)) if match.group(1) == 'X' else 0.0,
|
|
float(match.group(2)) if match.group(1) == 'Y' else 0.0
|
|
])
|
|
trstr = trstr[len(match.group(0)):].strip(' ')
|
|
continue
|
|
|
|
match = re.search(r'^' + rotate_re_str, trstr)
|
|
if match:
|
|
trlist.append([
|
|
'rotate',
|
|
float(match.group(1)),
|
|
float(match.group(2)) if match.group(2) else 0.0,
|
|
float(match.group(3)) if match.group(3) else 0.0
|
|
])
|
|
trstr = trstr[len(match.group(0)):].strip(' ')
|
|
continue
|
|
|
|
match = re.search(r'^' + matrix_re_str, trstr)
|
|
if match:
|
|
trlist.append(['matrix'] + [float(x) for x in match.groups()])
|
|
trstr = trstr[len(match.group(0)):].strip(' ')
|
|
continue
|
|
|
|
# raise Exception("Don't know how to parse: %s" % trstr)
|
|
log.error("[ERROR] Don't know how to parse: %s" % trstr)
|
|
|
|
return trlist
|
|
|
|
# if __name__ == "__main__":
|
|
# tree = ET.parse('tests/svg/drawing.svg')
|
|
# root = tree.getroot()
|
|
# ns = re.search(r'\{(.*)\}', root.tag).group(1)
|
|
# print(ns)
|
|
# for geo in getsvggeo(root):
|
|
# print(geo)
|