Pārlūkot izejas kodu

- in SVG parser: made sure that the minimum number of steps to approximate an arc/circle/bezier is 10

Marius Stanciu 5 gadi atpakaļ
vecāks
revīzija
1d46b43c4f
6 mainītis faili ar 67 papildinājumiem un 42 dzēšanām
  1. 1 0
      CHANGELOG.md
  2. 11 9
      appParsers/ParseGerber.py
  3. 46 25
      appParsers/ParseSVG.py
  4. 3 1
      appTools/ToolQRCode.py
  5. 2 5
      app_Main.py
  6. 4 2
      camlib.py

+ 1 - 0
CHANGELOG.md

@@ -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
 

+ 11 - 9
appParsers/ParseGerber.py

@@ -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)
 

+ 46 - 25
appParsers/ParseSVG.py

@@ -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
     """

+ 3 - 1
appTools/ToolQRCode.py

@@ -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]

+ 2 - 5
app_Main.py

@@ -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

+ 4 - 2
camlib.py

@@ -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]