Przeglądaj źródła

Optimized Gerber parser. Some minor improvements to Excellon parser.

Juan Pablo Caram 11 lat temu
rodzic
commit
e0d2daca6c
6 zmienionych plików z 326 dodań i 237 usunięć
  1. 20 34
      FlatCAM.py
  2. 7 0
      FlatCAMException.py
  3. 17 12
      FlatCAMObj.py
  4. 273 190
      camlib.py
  5. 1 1
      recent.json
  6. 8 0
      tests/profile_gerber_parser.py

+ 20 - 34
FlatCAM.py

@@ -7,38 +7,22 @@
 ############################################################
 ############################################################
 
 
 import threading
 import threading
-
-# TODO: Bundle together. This is just for debugging.
-from docutils.nodes import image
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GdkPixbuf
-from gi.repository import GLib
-from gi.repository import GObject
-import simplejson as json
 import traceback
 import traceback
-
-import matplotlib
-from matplotlib.figure import Figure
-from numpy import arange, sin, pi
-from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
-#from mpl_toolkits.axes_grid.anchored_artists import AnchoredText
-
-
 import sys
 import sys
 import urllib
 import urllib
 import copy
 import copy
 import random
 import random
 
 
+from gi.repository import Gtk, GdkPixbuf
+from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
 from shapely import speedups
 from shapely import speedups
 
 
+
 ########################################
 ########################################
 ##      Imports part of FlatCAM       ##
 ##      Imports part of FlatCAM       ##
 ########################################
 ########################################
-from camlib import *
 from FlatCAMObj import *
 from FlatCAMObj import *
 from FlatCAMWorker import Worker
 from FlatCAMWorker import Worker
-from FlatCAMException import *
 
 
 
 
 ########################################
 ########################################
@@ -909,26 +893,28 @@ class App:
             # Further parsing
             # Further parsing
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Creating Geometry ..."))
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Creating Geometry ..."))
             #gerber_obj.create_geometry()
             #gerber_obj.create_geometry()
-            gerber_obj.solid_geometry = gerber_obj.otf_geometry
+            #gerber_obj.solid_geometry = gerber_obj.otf_geometry
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
 
 
         # Object name
         # Object name
         name = filename.split('/')[-1].split('\\')[-1]
         name = filename.split('/')[-1].split('\\')[-1]
 
 
+        self.new_object("gerber", name, obj_init)
+
         # New object creation and file processing
         # New object creation and file processing
-        try:
-            self.new_object("gerber", name, obj_init)
-        except:
-            e = sys.exc_info()
-            print "ERROR:", e[0]
-            traceback.print_exc()
-            self.message_dialog("Failed to create Gerber Object",
-                                "Attempting to create a FlatCAM Gerber Object from " +
-                                "Gerber file failed during processing:\n" +
-                                str(e[0]) + " " + str(e[1]), kind="error")
-            GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, "Idle"))
-            self.collection.delete_active()
-            return
+        # try:
+        #     self.new_object("gerber", name, obj_init)
+        # except:
+        #     e = sys.exc_info()
+        #     print "ERROR:", e[0]
+        #     traceback.print_exc()
+        #     self.message_dialog("Failed to create Gerber Object",
+        #                         "Attempting to create a FlatCAM Gerber Object from " +
+        #                         "Gerber file failed during processing:\n" +
+        #                         str(e[0]) + " " + str(e[1]), kind="error")
+        #     GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, "Idle"))
+        #     self.collection.delete_active()
+        #     return
 
 
         # Register recent file
         # Register recent file
         self.register_recent("gerber", filename)
         self.register_recent("gerber", filename)
@@ -2848,7 +2834,7 @@ class ObjectCollection:
         try:
         try:
             model, treeiter = self.tree_selection.get_selected()
             model, treeiter = self.tree_selection.get_selected()
             return model[treeiter][0]
             return model[treeiter][0]
-        except ValueError:
+        except (TypeError, ValueError):
             return None
             return None
 
 
     def set_list_selection(self, name):
     def set_list_selection(self, name):

+ 7 - 0
FlatCAMException.py

@@ -0,0 +1,7 @@
+class FlatCAMException(Exception):
+    def __init__(self, message="An error occurred", detail=""):
+        self.message = message
+        self.detail = detail
+
+    def __str__(self):
+        return "FlatCAM ERROR:", self.message

+ 17 - 12
FlatCAMObj.py

@@ -297,12 +297,13 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         if not FlatCAMObj.plot(self):
         if not FlatCAMObj.plot(self):
             return
             return
 
 
-        if self.options["mergepolys"]:
-            geometry = self.solid_geometry
-        else:
-            geometry = self.buffered_paths + \
-                        [poly['polygon'] for poly in self.regions] + \
-                        self.flash_geometry
+        # if self.options["mergepolys"]:
+        #     geometry = self.solid_geometry
+        # else:
+        #     geometry = self.buffered_paths + \
+        #                 [poly['polygon'] for poly in self.regions] + \
+        #                 self.flash_geometry
+        geometry = self.solid_geometry
 
 
         # Make sure geometry is iterable.
         # Make sure geometry is iterable.
         try:
         try:
@@ -318,12 +319,16 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         if self.options["solid"]:
         if self.options["solid"]:
             for poly in geometry:
             for poly in geometry:
                 # TODO: Too many things hardcoded.
                 # TODO: Too many things hardcoded.
-                patch = PolygonPatch(poly,
-                                     facecolor="#BBF268",
-                                     edgecolor="#006E20",
-                                     alpha=0.75,
-                                     zorder=2)
-                self.axes.add_patch(patch)
+                try:
+                    patch = PolygonPatch(poly,
+                                         facecolor="#BBF268",
+                                         edgecolor="#006E20",
+                                         alpha=0.75,
+                                         zorder=2)
+                    self.axes.add_patch(patch)
+                except AssertionError:
+                    print "WARNING: A geometry component was not a polygon:"
+                    print poly
         else:
         else:
             for poly in geometry:
             for poly in geometry:
                 x, y = poly.exterior.xy
                 x, y = poly.exterior.xy

+ 273 - 190
camlib.py

@@ -620,7 +620,9 @@ class Gerber (Geometry):
 
 
         # Initialize parent
         # Initialize parent
         Geometry.__init__(self)        
         Geometry.__init__(self)        
-        
+
+        self.solid_geometry = Polygon()
+
         # Number format
         # Number format
         self.int_digits = 3
         self.int_digits = 3
         """Number of integer digits in Gerber numbers. Used during parsing."""
         """Number of integer digits in Gerber numbers. Used during parsing."""
@@ -635,27 +637,26 @@ class Gerber (Geometry):
         self.apertures = {}
         self.apertures = {}
         
         
         # Paths [{'linestring':LineString, 'aperture':str}]
         # Paths [{'linestring':LineString, 'aperture':str}]
-        self.paths = []
+        # self.paths = []
         
         
         # Buffered Paths [Polygon]
         # Buffered Paths [Polygon]
         # Paths transformed into Polygons by
         # Paths transformed into Polygons by
         # offsetting the aperture size/2
         # offsetting the aperture size/2
-        self.buffered_paths = []
+        # self.buffered_paths = []
         
         
         # Polygon regions [{'polygon':Polygon, 'aperture':str}]
         # Polygon regions [{'polygon':Polygon, 'aperture':str}]
-        self.regions = []
+        # self.regions = []
         
         
         # Flashes [{'loc':[float,float], 'aperture':str}]
         # Flashes [{'loc':[float,float], 'aperture':str}]
-        self.flashes = []
+        # self.flashes = []
         
         
         # Geometry from flashes
         # Geometry from flashes
-        self.flash_geometry = []
+        # self.flash_geometry = []
 
 
         # On-the-fly geometry. Initialized to an empty polygon
         # On-the-fly geometry. Initialized to an empty polygon
         self.otf_geometry = Polygon()
         self.otf_geometry = Polygon()
 
 
         # Aperture Macros
         # Aperture Macros
-        # TODO: Make sure these can be serialized
         self.aperture_macros = {}
         self.aperture_macros = {}
 
 
         # Attributes to be included in serialization
         # Attributes to be included in serialization
@@ -693,7 +694,7 @@ class Gerber (Geometry):
         # Operation code (D0x) missing is deprecated... oh well I will support it.
         # Operation code (D0x) missing is deprecated... oh well I will support it.
         self.lin_re = re.compile(r'^(?:G0?(1))?(?=.*X(-?\d+))?(?=.*Y(-?\d+))?[XY][^DIJ]*(?:D0?([123]))?\*$')
         self.lin_re = re.compile(r'^(?:G0?(1))?(?=.*X(-?\d+))?(?=.*Y(-?\d+))?[XY][^DIJ]*(?:D0?([123]))?\*$')
 
 
-        #
+        # Operation code alone, usually just D03 (Flash)
         self.opcode_re = re.compile(r'^D0?([123])\*$')
         self.opcode_re = re.compile(r'^D0?([123])\*$')
 
 
         # G02/3... - Circular interpolation with coordinates
         # G02/3... - Circular interpolation with coordinates
@@ -782,18 +783,18 @@ class Gerber (Geometry):
         # for fl in self.flashes:
         # for fl in self.flashes:
         #     fl['loc'] = affinity.scale(fl['loc'], factor, factor, origin=(0, 0))
         #     fl['loc'] = affinity.scale(fl['loc'], factor, factor, origin=(0, 0))
 
 
-        ## Regions
-        for reg in self.regions:
-            reg['polygon'] = affinity.scale(reg['polygon'], factor, factor,
-                                            origin=(0, 0))
-
-        ## Flashes
-        for flash in self.flash_geometry:
-            flash = affinity.scale(flash, factor, factor, origin=(0, 0))
-
-        ## Buffered paths
-        for bp in self.buffered_paths:
-            bp = affinity.scale(bp, factor, factor, origin=(0, 0))
+        # ## Regions
+        # for reg in self.regions:
+        #     reg['polygon'] = affinity.scale(reg['polygon'], factor, factor,
+        #                                     origin=(0, 0))
+        #
+        # ## Flashes
+        # for flash in self.flash_geometry:
+        #     flash = affinity.scale(flash, factor, factor, origin=(0, 0))
+        #
+        # ## Buffered paths
+        # for bp in self.buffered_paths:
+        #     bp = affinity.scale(bp, factor, factor, origin=(0, 0))
 
 
         ## solid_geometry ???
         ## solid_geometry ???
         #  It's a cascaded union of objects.
         #  It's a cascaded union of objects.
@@ -834,18 +835,18 @@ class Gerber (Geometry):
         # for fl in self.flashes:
         # for fl in self.flashes:
         #     fl['loc'] = affinity.translate(fl['loc'], xoff=dx, yoff=dy)
         #     fl['loc'] = affinity.translate(fl['loc'], xoff=dx, yoff=dy)
 
 
-        ## Regions
-        for reg in self.regions:
-            reg['polygon'] = affinity.translate(reg['polygon'],
-                                                xoff=dx, yoff=dy)
-
-        ## Buffered paths
-        for bp in self.buffered_paths:
-            bp = affinity.translate(bp, xoff=dx, yoff=dy)
-
-        ## Flash geometry
-        for fl in self.flash_geometry:
-            fl = affinity.translate(fl, xoff=dx, yoff=dy)
+        # ## Regions
+        # for reg in self.regions:
+        #     reg['polygon'] = affinity.translate(reg['polygon'],
+        #                                         xoff=dx, yoff=dy)
+        #
+        # ## Buffered paths
+        # for bp in self.buffered_paths:
+        #     bp = affinity.translate(bp, xoff=dx, yoff=dy)
+        #
+        # ## Flash geometry
+        # for fl in self.flash_geometry:
+        #     fl = affinity.translate(fl, xoff=dx, yoff=dy)
 
 
         ## Solid geometry
         ## Solid geometry
         self.solid_geometry = affinity.translate(self.solid_geometry, xoff=dx, yoff=dy)
         self.solid_geometry = affinity.translate(self.solid_geometry, xoff=dx, yoff=dy)
@@ -887,18 +888,18 @@ class Gerber (Geometry):
         # for fl in self.flashes:
         # for fl in self.flashes:
         #     fl['loc'] = affinity.scale(fl['loc'], xscale, yscale, origin=(px, py))
         #     fl['loc'] = affinity.scale(fl['loc'], xscale, yscale, origin=(px, py))
 
 
-        ## Regions
-        for reg in self.regions:
-            reg['polygon'] = affinity.scale(reg['polygon'], xscale, yscale,
-                                            origin=(px, py))
-
-        ## Flashes
-        for flash in self.flash_geometry:
-            flash = affinity.scale(flash, xscale, yscale, origin=(px, py))
-
-        ## Buffered paths
-        for bp in self.buffered_paths:
-            bp = affinity.scale(bp, xscale, yscale, origin=(px, py))
+        # ## Regions
+        # for reg in self.regions:
+        #     reg['polygon'] = affinity.scale(reg['polygon'], xscale, yscale,
+        #                                     origin=(px, py))
+        #
+        # ## Flashes
+        # for flash in self.flash_geometry:
+        #     flash = affinity.scale(flash, xscale, yscale, origin=(px, py))
+        #
+        # ## Buffered paths
+        # for bp in self.buffered_paths:
+        #     bp = affinity.scale(bp, xscale, yscale, origin=(px, py))
 
 
         ## solid_geometry ???
         ## solid_geometry ???
         #  It's a cascaded union of objects.
         #  It's a cascaded union of objects.
@@ -908,34 +909,34 @@ class Gerber (Geometry):
         # # Now buffered_paths, flash_geometry and solid_geometry
         # # Now buffered_paths, flash_geometry and solid_geometry
         # self.create_geometry()
         # self.create_geometry()
 
 
-    def fix_regions(self):
-        """
-        Overwrites the region polygons with fixed
-        versions if found to be invalid (according to Shapely).
-
-        :return: None
-        """
-
-        for region in self.regions:
-            if not region['polygon'].is_valid:
-                region['polygon'] = region['polygon'].buffer(0)
+    # def fix_regions(self):
+    #     """
+    #     Overwrites the region polygons with fixed
+    #     versions if found to be invalid (according to Shapely).
+    #
+    #     :return: None
+    #     """
+    #
+    #     for region in self.regions:
+    #         if not region['polygon'].is_valid:
+    #             region['polygon'] = region['polygon'].buffer(0)
     
     
-    def buffer_paths(self):
-        """
-        This is part of the parsing process. "Thickens" the paths
-        by their appertures. This will only work for circular appertures.
-
-        :return: None
-        """
-
-        self.buffered_paths = []
-        for path in self.paths:
-            try:
-                width = self.apertures[path["aperture"]]["size"]
-                self.buffered_paths.append(path["linestring"].buffer(width/2))
-            except KeyError:
-                print "ERROR: Failed to buffer path: ", path
-                print "Apertures: ", self.apertures
+    # def buffer_paths(self):
+    #     """
+    #     This is part of the parsing process. "Thickens" the paths
+    #     by their appertures. This will only work for circular appertures.
+    #
+    #     :return: None
+    #     """
+    #
+    #     self.buffered_paths = []
+    #     for path in self.paths:
+    #         try:
+    #             width = self.apertures[path["aperture"]]["size"]
+    #             self.buffered_paths.append(path["linestring"].buffer(width/2))
+    #         except KeyError:
+    #             print "ERROR: Failed to buffer path: ", path
+    #             print "Apertures: ", self.apertures
     
     
     def aperture_parse(self, apertureId, apertureType, apParameters):
     def aperture_parse(self, apertureId, apertureType, apParameters):
         """
         """
@@ -1014,7 +1015,7 @@ class Gerber (Geometry):
         gstr = gfile.readlines()
         gstr = gfile.readlines()
         gfile.close()
         gfile.close()
         self.parse_lines(gstr)
         self.parse_lines(gstr)
-        
+
     def parse_lines(self, glines):
     def parse_lines(self, glines):
         """
         """
         Main Gerber parser. Reads Gerber and populates ``self.paths``, ``self.apertures``,
         Main Gerber parser. Reads Gerber and populates ``self.paths``, ``self.apertures``,
@@ -1027,7 +1028,14 @@ class Gerber (Geometry):
         :rtype: None
         :rtype: None
         """
         """
 
 
-        path = []  # Coordinates of the current path, each is [x, y]
+        # Coordinates of the current path, each is [x, y]
+        path = []
+
+        # Polygons are stored here until there is a change in polarity.
+        # Only then they are combined via cascaded_union and added or
+        # subtracted from solid_geometry. This is ~100 times faster than
+        # applyng a union for every new polygon.
+        poly_buffer = []
 
 
         last_path_aperture = None
         last_path_aperture = None
         current_aperture = None
         current_aperture = None
@@ -1065,6 +1073,9 @@ class Gerber (Geometry):
         for gline in glines:
         for gline in glines:
             line_num += 1
             line_num += 1
 
 
+            ### Cleanup
+            gline = gline.strip(' \r\n')
+
             ### Aperture Macros
             ### Aperture Macros
             # Having this at the beggining will slow things down
             # Having this at the beggining will slow things down
             # but macros can have complicated statements than could
             # but macros can have complicated statements than could
@@ -1128,6 +1139,19 @@ class Gerber (Geometry):
                         #                    "aperture": last_path_aperture})
                         #                    "aperture": last_path_aperture})
 
 
                         # --- OTF ---
                         # --- OTF ---
+                        # if making_region:
+                        #     geo = Polygon(path)
+                        # else:
+                        #     if last_path_aperture is None:
+                        #         print "Warning: No aperture defined for curent path. (%d)" % line_num
+                        #     width = self.apertures[last_path_aperture]["size"]
+                        #     geo = LineString(path).buffer(width/2)
+                        # if current_polarity == 'D':
+                        #     self.otf_geometry = self.otf_geometry.union(geo)
+                        # else:
+                        #     self.otf_geometry = self.otf_geometry.difference(geo)
+
+                        ## --- BUFFERED ---
                         if making_region:
                         if making_region:
                             geo = Polygon(path)
                             geo = Polygon(path)
                         else:
                         else:
@@ -1135,10 +1159,7 @@ class Gerber (Geometry):
                                 print "Warning: No aperture defined for curent path. (%d)" % line_num
                                 print "Warning: No aperture defined for curent path. (%d)" % line_num
                             width = self.apertures[last_path_aperture]["size"]
                             width = self.apertures[last_path_aperture]["size"]
                             geo = LineString(path).buffer(width/2)
                             geo = LineString(path).buffer(width/2)
-                        if current_polarity == 'D':
-                            self.otf_geometry = self.otf_geometry.union(geo)
-                        else:
-                            self.otf_geometry = self.otf_geometry.difference(geo)
+                        poly_buffer.append(geo)
 
 
                     path = [[current_x, current_y]]  # Start new path
                     path = [[current_x, current_y]]  # Start new path
 
 
@@ -1148,12 +1169,17 @@ class Gerber (Geometry):
                     #                      "aperture": current_aperture})
                     #                      "aperture": current_aperture})
 
 
                     # --- OTF ---
                     # --- OTF ---
+                    # flash = Gerber.create_flash_geometry(Point([current_x, current_y]),
+                    #                                      self.apertures[current_aperture])
+                    # if current_polarity == 'D':
+                    #     self.otf_geometry = self.otf_geometry.union(flash)
+                    # else:
+                    #     self.otf_geometry = self.otf_geometry.difference(flash)
+
+                    # --- BUFFERED ---
                     flash = Gerber.create_flash_geometry(Point([current_x, current_y]),
                     flash = Gerber.create_flash_geometry(Point([current_x, current_y]),
                                                          self.apertures[current_aperture])
                                                          self.apertures[current_aperture])
-                    if current_polarity == 'D':
-                        self.otf_geometry = self.otf_geometry.union(flash)
-                    else:
-                        self.otf_geometry = self.otf_geometry.difference(flash)
+                    poly_buffer.append(flash)
 
 
                 continue
                 continue
 
 
@@ -1206,12 +1232,17 @@ class Gerber (Geometry):
                         #                    "aperture": last_path_aperture})
                         #                    "aperture": last_path_aperture})
 
 
                         # --- OTF ---
                         # --- OTF ---
+                        # width = self.apertures[last_path_aperture]["size"]
+                        # buffered = LineString(path).buffer(width/2)
+                        # if current_polarity == 'D':
+                        #     self.otf_geometry = self.otf_geometry.union(buffered)
+                        # else:
+                        #     self.otf_geometry = self.otf_geometry.difference(buffered)
+
+                        # --- BUFFERED ---
                         width = self.apertures[last_path_aperture]["size"]
                         width = self.apertures[last_path_aperture]["size"]
                         buffered = LineString(path).buffer(width/2)
                         buffered = LineString(path).buffer(width/2)
-                        if current_polarity == 'D':
-                            self.otf_geometry = self.otf_geometry.union(buffered)
-                        else:
-                            self.otf_geometry = self.otf_geometry.difference(buffered)
+                        poly_buffer.append(buffered)
 
 
                     current_x = x
                     current_x = x
                     current_y = y
                     current_y = y
@@ -1252,12 +1283,20 @@ class Gerber (Geometry):
             if match:
             if match:
                 current_operation_code = int(match.group(1))
                 current_operation_code = int(match.group(1))
                 if current_operation_code == 3:
                 if current_operation_code == 3:
+
+                    ## --- OTF ---
+                    # flash = Gerber.create_flash_geometry(Point(path[-1]),
+                    #                                      self.apertures[current_aperture])
+                    # if current_polarity == 'D':
+                    #     self.otf_geometry = self.otf_geometry.union(flash)
+                    # else:
+                    #     self.otf_geometry = self.otf_geometry.difference(flash)
+
+                    ## --- Buffered ---
                     flash = Gerber.create_flash_geometry(Point(path[-1]),
                     flash = Gerber.create_flash_geometry(Point(path[-1]),
                                                          self.apertures[current_aperture])
                                                          self.apertures[current_aperture])
-                    if current_polarity == 'D':
-                        self.otf_geometry = self.otf_geometry.union(flash)
-                    else:
-                        self.otf_geometry = self.otf_geometry.difference(flash)
+                    poly_buffer.append(flash)
+
                 continue
                 continue
 
 
             ### G74/75* - Single or multiple quadrant arcs
             ### G74/75* - Single or multiple quadrant arcs
@@ -1273,12 +1312,20 @@ class Gerber (Geometry):
             if self.regionon_re.search(gline):
             if self.regionon_re.search(gline):
                 if len(path) > 1:
                 if len(path) > 1:
                     # Take care of what is left in the path
                     # Take care of what is left in the path
+
+                    ## --- OTF ---
+                    # width = self.apertures[last_path_aperture]["size"]
+                    # geo = LineString(path).buffer(width/2)
+                    # if current_polarity == 'D':
+                    #     self.otf_geometry = self.otf_geometry.union(geo)
+                    # else:
+                    #     self.otf_geometry = self.otf_geometry.difference(geo)
+
+                    ## --- Buffered ---
                     width = self.apertures[last_path_aperture]["size"]
                     width = self.apertures[last_path_aperture]["size"]
                     geo = LineString(path).buffer(width/2)
                     geo = LineString(path).buffer(width/2)
-                    if current_polarity == 'D':
-                        self.otf_geometry = self.otf_geometry.union(geo)
-                    else:
-                        self.otf_geometry = self.otf_geometry.difference(geo)
+                    poly_buffer.append(geo)
+
                     path = [path[-1]]
                     path = [path[-1]]
 
 
                 making_region = True
                 making_region = True
@@ -1304,13 +1351,19 @@ class Gerber (Geometry):
                 #                      "aperture": last_path_aperture})
                 #                      "aperture": last_path_aperture})
 
 
                 # --- OTF ---
                 # --- OTF ---
+                # region = Polygon(path)
+                # if not region.is_valid:
+                #     region = region.buffer(0)
+                # if current_polarity == 'D':
+                #     self.otf_geometry = self.otf_geometry.union(region)
+                # else:
+                #     self.otf_geometry = self.otf_geometry.difference(region)
+
+                # --- Buffered ---
                 region = Polygon(path)
                 region = Polygon(path)
                 if not region.is_valid:
                 if not region.is_valid:
                     region = region.buffer(0)
                     region = region.buffer(0)
-                if current_polarity == 'D':
-                    self.otf_geometry = self.otf_geometry.union(region)
-                else:
-                    self.otf_geometry = self.otf_geometry.difference(region)
+                poly_buffer.append(region)
 
 
                 path = [[current_x, current_y]]  # Start new path
                 path = [[current_x, current_y]]  # Start new path
                 continue
                 continue
@@ -1343,13 +1396,28 @@ class Gerber (Geometry):
             if match:
             if match:
                 if len(path) > 1 and current_polarity != match.group(1):
                 if len(path) > 1 and current_polarity != match.group(1):
 
 
+                    # --- OTF ---
+                    # width = self.apertures[last_path_aperture]["size"]
+                    # geo = LineString(path).buffer(width/2)
+                    # if current_polarity == 'D':
+                    #     self.otf_geometry = self.otf_geometry.union(geo)
+                    # else:
+                    #     self.otf_geometry = self.otf_geometry.difference(geo)
+
+                    # --- Buffered ----
                     width = self.apertures[last_path_aperture]["size"]
                     width = self.apertures[last_path_aperture]["size"]
                     geo = LineString(path).buffer(width/2)
                     geo = LineString(path).buffer(width/2)
-                    if current_polarity == 'D':
-                        self.otf_geometry = self.otf_geometry.union(geo)
-                    else:
-                        self.otf_geometry = self.otf_geometry.difference(geo)
+                    poly_buffer.append(geo)
+
                     path = [path[-1]]
                     path = [path[-1]]
+
+                # --- Apply buffer ---
+                if current_polarity == 'D':
+                    self.solid_geometry = self.solid_geometry.union(cascaded_union(poly_buffer))
+                else:
+                    self.solid_geometry = self.solid_geometry.difference(cascaded_union(poly_buffer))
+                poly_buffer = []
+
                 current_polarity = match.group(1)
                 current_polarity = match.group(1)
                 continue
                 continue
 
 
@@ -1401,12 +1469,24 @@ class Gerber (Geometry):
             # self.paths.append({"linestring": LineString(path),
             # self.paths.append({"linestring": LineString(path),
             #                    "aperture": last_path_aperture})
             #                    "aperture": last_path_aperture})
 
 
+            ## --- OTF ---
+            # width = self.apertures[last_path_aperture]["size"]
+            # geo = LineString(path).buffer(width/2)
+            # if current_polarity == 'D':
+            #     self.otf_geometry = self.otf_geometry.union(geo)
+            # else:
+            #     self.otf_geometry = self.otf_geometry.difference(geo)
+
+            ## --- Buffered ---
             width = self.apertures[last_path_aperture]["size"]
             width = self.apertures[last_path_aperture]["size"]
             geo = LineString(path).buffer(width/2)
             geo = LineString(path).buffer(width/2)
-            if current_polarity == 'D':
-                self.otf_geometry = self.otf_geometry.union(geo)
-            else:
-                self.otf_geometry = self.otf_geometry.difference(geo)
+            poly_buffer.append(geo)
+
+        # --- Apply buffer ---
+        if current_polarity == 'D':
+            self.solid_geometry = self.solid_geometry.union(cascaded_union(poly_buffer))
+        else:
+            self.solid_geometry = self.solid_geometry.difference(cascaded_union(poly_buffer))
 
 
     @staticmethod
     @staticmethod
     def create_flash_geometry(location, aperture):
     def create_flash_geometry(location, aperture):
@@ -1464,79 +1544,79 @@ class Gerber (Geometry):
 
 
         return None
         return None
 
 
-    def do_flashes(self):
-        """
-        Creates geometry for Gerber flashes (aperture on a single point).
-        """
-
-        self.flash_geometry = []
-        for flash in self.flashes:
-
-            try:
-                aperture = self.apertures[flash['aperture']]
-            except KeyError:
-                print "ERROR: Trying to flash with unknown aperture: ", flash['aperture']
-                continue
-
-            if aperture['type'] == 'C':  # Circles
-                #circle = Point(flash['loc']).buffer(aperture['size']/2)
-                circle = flash['loc'].buffer(aperture['size']/2)
-                self.flash_geometry.append(circle)
-                continue
-
-            if aperture['type'] == 'R':  # Rectangles
-                loc = flash['loc'].coords[0]
-                width = aperture['width']
-                height = aperture['height']
-                minx = loc[0] - width/2
-                maxx = loc[0] + width/2
-                miny = loc[1] - height/2
-                maxy = loc[1] + height/2
-                rectangle = shply_box(minx, miny, maxx, maxy)
-                self.flash_geometry.append(rectangle)
-                continue
-
-            if aperture['type'] == 'O':  # Obround
-                loc = flash['loc'].coords[0]
-                width = aperture['width']
-                height = aperture['height']
-                if width > height:
-                    p1 = Point(loc[0] + 0.5*(width-height), loc[1])
-                    p2 = Point(loc[0] - 0.5*(width-height), loc[1])
-                    c1 = p1.buffer(height*0.5)
-                    c2 = p2.buffer(height*0.5)
-                else:
-                    p1 = Point(loc[0], loc[1] + 0.5*(height-width))
-                    p2 = Point(loc[0], loc[1] - 0.5*(height-width))
-                    c1 = p1.buffer(width*0.5)
-                    c2 = p2.buffer(width*0.5)
-                obround = cascaded_union([c1, c2]).convex_hull
-                self.flash_geometry.append(obround)
-                continue
-
-            if aperture['type'] == 'P':  # Regular polygon
-                loc = flash['loc'].coords[0]
-                diam = aperture['diam']
-                n_vertices = aperture['nVertices']
-                points = []
-                for i in range(0, n_vertices):
-                    x = loc[0] + diam * (cos(2 * pi * i / n_vertices))
-                    y = loc[1] + diam * (sin(2 * pi * i / n_vertices))
-                    points.append((x, y))
-                ply = Polygon(points)
-                if 'rotation' in aperture:
-                    ply = affinity.rotate(ply, aperture['rotation'])
-                self.flash_geometry.append(ply)
-                continue
-
-            if aperture['type'] == 'AM':  # Aperture Macro
-                loc = flash['loc'].coords[0]
-                flash_geo = aperture['macro'].make_geometry(aperture['modifiers'])
-                flash_geo_final = affinity.translate(flash_geo, xoff=loc[0], yoff=loc[1])
-                self.flash_geometry.append(flash_geo_final)
-                continue
-
-            print "WARNING: Aperture type %s not implemented" % (aperture['type'])
+    # def do_flashes(self):
+    #     """
+    #     Creates geometry for Gerber flashes (aperture on a single point).
+    #     """
+    #
+    #     self.flash_geometry = []
+    #     for flash in self.flashes:
+    #
+    #         try:
+    #             aperture = self.apertures[flash['aperture']]
+    #         except KeyError:
+    #             print "ERROR: Trying to flash with unknown aperture: ", flash['aperture']
+    #             continue
+    #
+    #         if aperture['type'] == 'C':  # Circles
+    #             #circle = Point(flash['loc']).buffer(aperture['size']/2)
+    #             circle = flash['loc'].buffer(aperture['size']/2)
+    #             self.flash_geometry.append(circle)
+    #             continue
+    #
+    #         if aperture['type'] == 'R':  # Rectangles
+    #             loc = flash['loc'].coords[0]
+    #             width = aperture['width']
+    #             height = aperture['height']
+    #             minx = loc[0] - width/2
+    #             maxx = loc[0] + width/2
+    #             miny = loc[1] - height/2
+    #             maxy = loc[1] + height/2
+    #             rectangle = shply_box(minx, miny, maxx, maxy)
+    #             self.flash_geometry.append(rectangle)
+    #             continue
+    #
+    #         if aperture['type'] == 'O':  # Obround
+    #             loc = flash['loc'].coords[0]
+    #             width = aperture['width']
+    #             height = aperture['height']
+    #             if width > height:
+    #                 p1 = Point(loc[0] + 0.5*(width-height), loc[1])
+    #                 p2 = Point(loc[0] - 0.5*(width-height), loc[1])
+    #                 c1 = p1.buffer(height*0.5)
+    #                 c2 = p2.buffer(height*0.5)
+    #             else:
+    #                 p1 = Point(loc[0], loc[1] + 0.5*(height-width))
+    #                 p2 = Point(loc[0], loc[1] - 0.5*(height-width))
+    #                 c1 = p1.buffer(width*0.5)
+    #                 c2 = p2.buffer(width*0.5)
+    #             obround = cascaded_union([c1, c2]).convex_hull
+    #             self.flash_geometry.append(obround)
+    #             continue
+    #
+    #         if aperture['type'] == 'P':  # Regular polygon
+    #             loc = flash['loc'].coords[0]
+    #             diam = aperture['diam']
+    #             n_vertices = aperture['nVertices']
+    #             points = []
+    #             for i in range(0, n_vertices):
+    #                 x = loc[0] + diam * (cos(2 * pi * i / n_vertices))
+    #                 y = loc[1] + diam * (sin(2 * pi * i / n_vertices))
+    #                 points.append((x, y))
+    #             ply = Polygon(points)
+    #             if 'rotation' in aperture:
+    #                 ply = affinity.rotate(ply, aperture['rotation'])
+    #             self.flash_geometry.append(ply)
+    #             continue
+    #
+    #         if aperture['type'] == 'AM':  # Aperture Macro
+    #             loc = flash['loc'].coords[0]
+    #             flash_geo = aperture['macro'].make_geometry(aperture['modifiers'])
+    #             flash_geo_final = affinity.translate(flash_geo, xoff=loc[0], yoff=loc[1])
+    #             self.flash_geometry.append(flash_geo_final)
+    #             continue
+    #
+    #         print "WARNING: Aperture type %s not implemented" % (aperture['type'])
     
     
     def create_geometry(self):
     def create_geometry(self):
         """
         """
@@ -1549,15 +1629,15 @@ class Gerber (Geometry):
         :return: None
         :return: None
         """
         """
 
 
-        self.buffer_paths()
-
-        self.fix_regions()
-
-        self.do_flashes()
-
-        self.solid_geometry = cascaded_union(self.buffered_paths +
-                                             [poly['polygon'] for poly in self.regions] +
-                                             self.flash_geometry)
+        # self.buffer_paths()
+        #
+        # self.fix_regions()
+        #
+        # self.do_flashes()
+        #
+        # self.solid_geometry = cascaded_union(self.buffered_paths +
+        #                                      [poly['polygon'] for poly in self.regions] +
+        #                                      self.flash_geometry)
 
 
     def get_bounding_box(self, margin=0.0, rounded=False):
     def get_bounding_box(self, margin=0.0, rounded=False):
         """
         """
@@ -1704,7 +1784,7 @@ class Excellon(Geometry):
         estr = efile.readlines()
         estr = efile.readlines()
         efile.close()
         efile.close()
         self.parse_lines(estr)
         self.parse_lines(estr)
-        
+
     def parse_lines(self, elines):
     def parse_lines(self, elines):
         """
         """
         Main Excellon parser.
         Main Excellon parser.
@@ -1720,9 +1800,12 @@ class Excellon(Geometry):
         current_x = None
         current_x = None
         current_y = None
         current_y = None
 
 
-        i = 0  # Line number
+        line_num = 0  # Line number
         for eline in elines:
         for eline in elines:
-            i += 1
+            line_num += 1
+
+            ### Cleanup
+            eline = eline.strip(' \r\n')
 
 
             ## Header Begin/End ##
             ## Header Begin/End ##
             if self.hbegin_re.search(eline):
             if self.hbegin_re.search(eline):

+ 1 - 1
recent.json

@@ -1 +1 @@
-[{"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom_Gndplane_modified.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\Kenney\\Project Outputs for AnalogPredistortion1\\apd.GTL"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\WindMills - Bottom Copper 2.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\CC_LOAD_7000164-00_REV_A_copper_top.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom_Gndplane.gbr"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\TFTadapter.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles-F_Cu.gtl"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles.drl"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\BLDC2003Through.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.GTL"}]
+[{"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\WindMills - Bottom Copper 2.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom_Gndplane_modified.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom_Gndplane.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\CC_LOAD_7000164-00_REV_A_copper_top.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\bedini 7 coils capacitor discharge.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Gerbers\\AVR_Transistor_Tester_copper_bottom.GBL"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Gerbers\\AVR_Transistor_Tester_copper_top.GTL"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles-F_Cu.gtl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\maitest.gtl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom.gbr"}]

+ 8 - 0
tests/profile_gerber_parser.py

@@ -0,0 +1,8 @@
+import os
+os.chdir('../')
+
+from camlib import *
+
+g = Gerber()
+g.parse_file(r'C:\Users\jpcaram\Dropbox\CNC\pcbcam\test_files\PlacaReles-F_Cu.gtl')
+