- in SVG parser: made sure that the minimum number of steps to approximate an arc/circle/bezier is 10
This commit is contained in:
parent
d3ebb08d1f
commit
1d46b43c4f
|
@ -10,6 +10,7 @@ CHANGELOG for FlatCAM beta
|
|||
20.09.2020
|
||||
|
||||
- in CNCJob UI Autolevelling: on manual add of probe points, only voronoi diagram is calculated
|
||||
- in SVG parser: made sure that the minimum number of steps to approximate an arc/circle/bezier is 10
|
||||
|
||||
19.09.2020
|
||||
|
||||
|
|
|
@ -1786,16 +1786,16 @@ class Gerber(Geometry):
|
|||
self.scale(factor, factor)
|
||||
return factor
|
||||
|
||||
def import_svg(self, filename, object_type='gerber', flip=True, units='MM'):
|
||||
def import_svg(self, filename, object_type='gerber', flip=True, units=None):
|
||||
"""
|
||||
Imports shapes from an SVG file into the object's geometry.
|
||||
|
||||
:param filename: Path to the SVG file.
|
||||
:type filename: str
|
||||
:param object_type: parameter passed further along
|
||||
:param flip: Flip the vertically.
|
||||
:type flip: bool
|
||||
:param units: FlatCAM units
|
||||
:param filename: Path to the SVG file.
|
||||
:type filename: str
|
||||
:param object_type: parameter passed further along
|
||||
:param flip: Flip the vertically.
|
||||
:type flip: bool
|
||||
:param units: FlatCAM units
|
||||
:return: None
|
||||
"""
|
||||
|
||||
|
@ -1810,7 +1810,9 @@ class Gerber(Geometry):
|
|||
# w = float(svg_root.get('width'))
|
||||
h = svgparselength(svg_root.get('height'))[0] # TODO: No units support yet
|
||||
|
||||
geos = getsvggeo(svg_root, 'gerber')
|
||||
units = self.app.defaults['units'] if units is None else units
|
||||
res = self.app.defaults['gerber_circle_steps']
|
||||
geos = getsvggeo(svg_root, 'gerber', units=units, res=res)
|
||||
if flip:
|
||||
geos = [translate(scale(g, 1.0, -1.0, origin=(0, 0)), yoff=h) for g in geos]
|
||||
|
||||
|
@ -1837,7 +1839,7 @@ class Gerber(Geometry):
|
|||
geo_qrcode = []
|
||||
geo_qrcode.append(Polygon(geos[0].exterior))
|
||||
for i_el in geos[0].interiors:
|
||||
geo_qrcode.append(Polygon(i_el).buffer(0))
|
||||
geo_qrcode.append(Polygon(i_el).buffer(0, resolution=res))
|
||||
for poly in geo_qrcode:
|
||||
geos.append(poly)
|
||||
|
||||
|
|
|
@ -38,9 +38,9 @@ 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)
|
||||
:param lengthstr: SVG length string.
|
||||
:return: Number and units pair.
|
||||
:rtype: tuple(float, str|None)
|
||||
"""
|
||||
|
||||
integer_re_str = r'[+-]?[0-9]+'
|
||||
|
@ -58,7 +58,7 @@ def svgparselength(lengthstr):
|
|||
return
|
||||
|
||||
|
||||
def path2shapely(path, object_type, res=1.0):
|
||||
def path2shapely(path, object_type, res=1.0, units='MM'):
|
||||
"""
|
||||
Converts an svg.path.Path into a Shapely
|
||||
Polygon or LinearString.
|
||||
|
@ -66,6 +66,8 @@ def path2shapely(path, object_type, res=1.0):
|
|||
:param path: svg.path.Path instance
|
||||
:param object_type:
|
||||
:param res: Resolution (minimum step along path)
|
||||
:param units: FlatCAM units
|
||||
:type units: str
|
||||
:return: Shapely geometry object
|
||||
:rtype : Polygon
|
||||
:rtype : LineString
|
||||
|
@ -98,10 +100,14 @@ def path2shapely(path, object_type, res=1.0):
|
|||
# steps = int(length / res + 0.5)
|
||||
steps = int(length) * 2
|
||||
|
||||
if units == 'IN':
|
||||
steps *= 25
|
||||
|
||||
# solve error when step is below 1,
|
||||
# it may cause other problems, but LineString needs at least two points
|
||||
if steps == 0:
|
||||
steps = 1
|
||||
# it may cause other problems, but LineString needs at least two points
|
||||
# later edit: made the minimum nr of steps to be 10; left it like that to see that steps can be 0
|
||||
if steps == 0 or steps < 10:
|
||||
steps = 10
|
||||
|
||||
frac = 1.0 / steps
|
||||
|
||||
|
@ -183,7 +189,7 @@ def svgrect2shapely(rect, n_points=32):
|
|||
|
||||
:param rect: Rect Element
|
||||
:type rect: xml.etree.ElementTree.Element
|
||||
:param n_points: number of points to approximate circles
|
||||
:param n_points: number of points to approximate rectangles corners when having rounded corners
|
||||
:type n_points: int
|
||||
:return: shapely.geometry.polygon.LinearRing
|
||||
"""
|
||||
|
@ -247,14 +253,16 @@ def svgrect2shapely(rect, n_points=32):
|
|||
# return LinearRing(pts)
|
||||
|
||||
|
||||
def svgcircle2shapely(circle):
|
||||
def svgcircle2shapely(circle, n_points=64):
|
||||
"""
|
||||
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
|
||||
:param circle: Circle Element
|
||||
:type circle: xml.etree.ElementTree.Element
|
||||
:param n_points: circle resolution; nr of points to b e used to approximate a circle
|
||||
:type n_points: int
|
||||
:return: Shapely representation of the circle.
|
||||
:rtype: shapely.geometry.polygon.LinearRing
|
||||
"""
|
||||
# cx = float(circle.get('cx'))
|
||||
# cy = float(circle.get('cy'))
|
||||
|
@ -263,8 +271,7 @@ def svgcircle2shapely(circle):
|
|||
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)
|
||||
return Point(cx, cy).buffer(r, resolution=n_points)
|
||||
|
||||
|
||||
def svgellipse2shapely(ellipse, n_points=64):
|
||||
|
@ -318,22 +325,35 @@ def svgpolyline2shapely(polyline):
|
|||
return LineString(points)
|
||||
|
||||
|
||||
def svgpolygon2shapely(polygon):
|
||||
def svgpolygon2shapely(polygon, n_points=64):
|
||||
"""
|
||||
Convert a SVG polygon to a Shapely Polygon.
|
||||
|
||||
:param polygon:
|
||||
:type polygon:
|
||||
:param n_points: circle resolution; nr of points to b e used to approximate a circle
|
||||
:type n_points: int
|
||||
:return: Shapely Polygon
|
||||
"""
|
||||
|
||||
ptliststr = polygon.get('points')
|
||||
points = parse_svg_point_list(ptliststr)
|
||||
|
||||
return Polygon(points).buffer(0)
|
||||
return Polygon(points).buffer(0, resolution=n_points)
|
||||
# return LinearRing(points)
|
||||
|
||||
|
||||
def getsvggeo(node, object_type, root=None):
|
||||
def getsvggeo(node, object_type, root=None, units='MM', res=64):
|
||||
"""
|
||||
Extracts and flattens all geometry from an SVG node
|
||||
into a list of Shapely geometry.
|
||||
|
||||
:param node: xml.etree.ElementTree.Element
|
||||
:param object_type:
|
||||
:param root:
|
||||
:param units: FlatCAM units
|
||||
:param res: resolution to be used for circles bufferring
|
||||
|
||||
:return: List of Shapely geometry
|
||||
:rtype: list
|
||||
"""
|
||||
|
@ -346,7 +366,7 @@ def getsvggeo(node, object_type, root=None):
|
|||
# Recurse
|
||||
if len(node) > 0:
|
||||
for child in node:
|
||||
subgeo = getsvggeo(child, object_type, root)
|
||||
subgeo = getsvggeo(child, object_type, root=root, units=units, res=res)
|
||||
if subgeo is not None:
|
||||
geo += subgeo
|
||||
|
||||
|
@ -354,28 +374,28 @@ def getsvggeo(node, object_type, root=None):
|
|||
elif kind == 'path':
|
||||
log.debug("***PATH***")
|
||||
P = parse_path(node.get('d'))
|
||||
P = path2shapely(P, object_type)
|
||||
P = path2shapely(P, object_type, units=units)
|
||||
# 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)
|
||||
R = svgrect2shapely(node, n_points=res)
|
||||
geo = [R]
|
||||
|
||||
elif kind == 'circle':
|
||||
log.debug("***CIRCLE***")
|
||||
C = svgcircle2shapely(node)
|
||||
C = svgcircle2shapely(node, n_points=res)
|
||||
geo = [C]
|
||||
|
||||
elif kind == 'ellipse':
|
||||
log.debug("***ELLIPSE***")
|
||||
E = svgellipse2shapely(node)
|
||||
E = svgellipse2shapely(node, n_points=res)
|
||||
geo = [E]
|
||||
|
||||
elif kind == 'polygon':
|
||||
log.debug("***POLYGON***")
|
||||
poly = svgpolygon2shapely(node)
|
||||
poly = svgpolygon2shapely(node, n_points=res)
|
||||
geo = [poly]
|
||||
|
||||
elif kind == 'line':
|
||||
|
@ -395,7 +415,7 @@ def getsvggeo(node, object_type, root=None):
|
|||
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)
|
||||
geo = getsvggeo(ref, object_type, root=root, units=units, res=res)
|
||||
|
||||
else:
|
||||
log.warning("Unknown kind: " + kind)
|
||||
|
@ -437,6 +457,7 @@ def getsvgtext(node, object_type, units='MM'):
|
|||
|
||||
:param node: xml.etree.ElementTree.Element
|
||||
:param object_type:
|
||||
:param units: FlatCAM units
|
||||
:return: List of Shapely geometry
|
||||
:rtype: list
|
||||
"""
|
||||
|
|
|
@ -427,7 +427,9 @@ class QRCode(AppTool):
|
|||
# h = float(svg_root.get('height'))
|
||||
# w = float(svg_root.get('width'))
|
||||
h = svgparselength(svg_root.get('height'))[0] # TODO: No units support yet
|
||||
geos = getsvggeo(svg_root, object_type)
|
||||
units = self.app.defaults['units'] if units is None else units
|
||||
res = self.app.defaults['geometry_circle_steps']
|
||||
geos = getsvggeo(svg_root, object_type, units=units, res=res)
|
||||
|
||||
if flip:
|
||||
geos = [translate(scale(g, 1.0, -1.0, origin=(0, 0)), yoff=h) for g in geos]
|
||||
|
|
|
@ -8873,12 +8873,9 @@ class App(QtCore.QObject):
|
|||
units = self.defaults['units'].upper()
|
||||
|
||||
def obj_init(geo_obj, app_obj):
|
||||
if obj_type == "geometry":
|
||||
geo_obj.import_svg(filename, obj_type, units=units)
|
||||
elif obj_type == "gerber":
|
||||
geo_obj.import_svg(filename, obj_type, units=units)
|
||||
|
||||
geo_obj.import_svg(filename, obj_type, units=units)
|
||||
geo_obj.multigeo = True
|
||||
|
||||
with open(filename) as f:
|
||||
file_content = f.read()
|
||||
geo_obj.source_file = file_content
|
||||
|
|
|
@ -1090,7 +1090,7 @@ class Geometry(object):
|
|||
else:
|
||||
yield item
|
||||
|
||||
def import_svg(self, filename, object_type=None, flip=True, units='MM'):
|
||||
def import_svg(self, filename, object_type=None, flip=True, units=None):
|
||||
"""
|
||||
Imports shapes from an SVG file into the object's geometry.
|
||||
|
||||
|
@ -1114,7 +1114,9 @@ class Geometry(object):
|
|||
# w = float(svg_root.get('width'))
|
||||
h = svgparselength(svg_root.get('height'))[0] # TODO: No units support yet
|
||||
|
||||
geos = getsvggeo(svg_root, object_type)
|
||||
units = self.app.defaults['units'] if units is None else units
|
||||
res = self.app.defaults['geometry_circle_steps']
|
||||
geos = getsvggeo(svg_root, object_type, units=units, res=res)
|
||||
if flip:
|
||||
geos = [translate(scale(g, 1.0, -1.0, origin=(0, 0)), yoff=h) for g in geos]
|
||||
|
||||
|
|
Loading…
Reference in New Issue