Просмотр исходного кода

Support for LPD and LPC in Gerber. Major changes in Gerber parser.

Juan Pablo Caram 11 лет назад
Родитель
Сommit
2ed0f73f87
5 измененных файлов с 362 добавлено и 175 удалено
  1. 167 79
      FlatCAM.py
  2. 0 71
      FlatCAM.ui
  3. 6 0
      FlatCAMObj.py
  4. 188 24
      camlib.py
  5. 1 1
      recent.json

+ 167 - 79
FlatCAM.py

@@ -16,6 +16,7 @@ from gi.repository import GdkPixbuf
 from gi.repository import GLib
 from gi.repository import GObject
 import simplejson as json
+import traceback
 
 import matplotlib
 from matplotlib.figure import Figure
@@ -29,12 +30,15 @@ import urllib
 import copy
 import random
 
+from shapely import speedups
+
 ########################################
 ##      Imports part of FlatCAM       ##
 ########################################
 from camlib import *
 from FlatCAMObj import *
 from FlatCAMWorker import Worker
+from FlatCAMException import *
 
 
 ########################################
@@ -55,6 +59,9 @@ class App:
         :rtype: App
         """
 
+        if speedups.available:
+            speedups.enable()
+
         # Needed to interact with the GUI from other threads.
         GObject.threads_init()
 
@@ -185,13 +192,10 @@ class App:
         def somethreadfunc(app_obj):
             print "Hello World!"
 
-        self.message_dialog("Starting", "The best program is starting")
-
         t = threading.Thread(target=somethreadfunc, args=(self,))
         t.daemon = True
         t.start()
 
-
         ########################################
         ##              START                 ##
         ########################################
@@ -203,14 +207,18 @@ class App:
         self.window.set_default_size(900, 600)
         self.window.show_all()
 
-    def message_dialog(self, title, message, type="info"):
+    def message_dialog(self, title, message, kind="info"):
         types = {"info": Gtk.MessageType.INFO,
                  "warn": Gtk.MessageType.WARNING,
                  "error": Gtk.MessageType.ERROR}
-        dlg = Gtk.MessageDialog(self.window, 0, types[type], Gtk.ButtonsType.OK, title)
+        dlg = Gtk.MessageDialog(self.window, 0, types[kind], Gtk.ButtonsType.OK, title)
         dlg.format_secondary_text(message)
-        dlg.run()
-        dlg.destroy()
+
+        def lifecycle():
+            dlg.run()
+            dlg.destroy()
+
+        GLib.idle_add(lifecycle)
 
     def question_dialog(self, title, message):
         label = Gtk.Label(message)
@@ -879,6 +887,158 @@ class App:
 
         f.close()
 
+    def open_gerber(self, filename):
+        """
+        Opens a Gerber file, parses it and creates a new object for
+        it in the program. Thread-safe.
+
+        :param filename: Gerber file filename
+        :type filename: str
+        :return: None
+        """
+        GLib.idle_add(lambda: self.set_progress_bar(0.1, "Opening Gerber ..."))
+
+        # How the object should be initialized
+        def obj_init(gerber_obj, app_obj):
+            assert isinstance(gerber_obj, FlatCAMGerber)
+
+            # Opening the file happens here
+            GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
+            gerber_obj.parse_file(filename)
+
+            # Further parsing
+            GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Creating Geometry ..."))
+            #gerber_obj.create_geometry()
+            gerber_obj.solid_geometry = gerber_obj.otf_geometry
+            GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
+
+        # Object name
+        name = filename.split('/')[-1].split('\\')[-1]
+
+        # 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
+
+        # Register recent file
+        self.register_recent("gerber", filename)
+
+        # GUI feedback
+        self.info("Opened: " + filename)
+        GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
+        GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, "Idle"))
+
+    def open_excellon(self, filename):
+        """
+        Opens an Excellon file, parses it and creates a new object for
+        it in the program. Thread-safe.
+
+        :param filename: Excellon file filename
+        :type filename: str
+        :return: None
+        """
+        GLib.idle_add(lambda: self.set_progress_bar(0.1, "Opening Excellon ..."))
+
+        # How the object should be initialized
+        def obj_init(excellon_obj, app_obj):
+            GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
+            excellon_obj.parse_file(filename)
+            excellon_obj.create_geometry()
+            GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
+
+        # Object name
+        name = filename.split('/')[-1].split('\\')[-1]
+
+        # New object creation and file processing
+        try:
+            self.new_object("excellon", name, obj_init)
+        except:
+            e = sys.exc_info()
+            print "ERROR:", e[0]
+            self.message_dialog("Failed to create Excellon Object",
+                                "Attempting to create a FlatCAM Excellon Object from " +
+                                "Excellon 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
+        self.register_recent("excellon", filename)
+
+        # GUI feedback
+        self.info("Opened: " + filename)
+        GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
+        GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
+
+    def open_gcode(self, filename):
+        """
+        Opens a G-gcode file, parses it and creates a new object for
+        it in the program. Thread-safe.
+
+        :param filename: G-code file filename
+        :type filename: str
+        :return: None
+        """
+
+        # How the object should be initialized
+        def obj_init(job_obj, app_obj_):
+            """
+
+            :type app_obj_: App
+            """
+            assert isinstance(app_obj_, App)
+            GLib.idle_add(lambda: app_obj_.set_progress_bar(0.1, "Opening G-Code ..."))
+
+            f = open(filename)
+            gcode = f.read()
+            f.close()
+
+            job_obj.gcode = gcode
+
+            GLib.idle_add(lambda: app_obj_.set_progress_bar(0.2, "Parsing ..."))
+            job_obj.gcode_parse()
+
+            GLib.idle_add(lambda: app_obj_.set_progress_bar(0.6, "Creating geometry ..."))
+            job_obj.create_geometry()
+
+            GLib.idle_add(lambda: app_obj_.set_progress_bar(0.6, "Plotting ..."))
+
+        # Object name
+        name = filename.split('/')[-1].split('\\')[-1]
+
+        # New object creation and file processing
+        try:
+            self.new_object("cncjob", name, obj_init)
+        except:
+            e = sys.exc_info()
+            print "ERROR:", e[0]
+            self.message_dialog("Failed to create CNCJob Object",
+                                "Attempting to create a FlatCAM CNCJob Object from " +
+                                "G-Code 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
+        self.register_recent("cncjob", filename)
+
+        # GUI feedback
+        self.info("Opened: " + filename)
+        GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
+        GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
+
     ########################################
     ##         EVENT HANDLERS             ##
     ########################################
@@ -1980,29 +2140,6 @@ class App:
             self.info("Save cancelled.")  # print("Cancel clicked")
             dialog.destroy()
 
-    def open_gerber(self, filename):
-        GLib.idle_add(lambda: self.set_progress_bar(0.1, "Opening Gerber ..."))
-
-        def obj_init(gerber_obj, app_obj):
-            assert isinstance(gerber_obj, FlatCAMGerber)
-
-            # Opening the file happens here
-            GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
-            gerber_obj.parse_file(filename)
-
-            # Further parsing
-            GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Creating Geometry ..."))
-            gerber_obj.create_geometry()
-            GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
-
-        name = filename.split('/')[-1].split('\\')[-1]
-        self.new_object("gerber", name, obj_init)
-        self.register_recent("gerber", filename)
-
-        self.info("Opened: " + filename)
-        GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
-        GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, "Idle"))
-
     def on_fileopengerber(self, param):
         """
         Callback for menu item File->Open Gerber. Defines a function that is then passed
@@ -2015,23 +2152,6 @@ class App:
 
         self.file_chooser_action(lambda ao, filename: self.open_gerber(filename))
 
-    def open_excellon(self, filename):
-        GLib.idle_add(lambda: self.set_progress_bar(0.1, "Opening Excellon ..."))
-
-        def obj_init(excellon_obj, app_obj):
-            GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
-            excellon_obj.parse_file(filename)
-            excellon_obj.create_geometry()
-            GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
-
-        name = filename.split('/')[-1].split('\\')[-1]
-        self.new_object("excellon", name, obj_init)
-        self.register_recent("excellon", filename)
-
-        self.info("Opened: " + filename)
-        GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
-        GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
-
     def on_fileopenexcellon(self, param):
         """
         Callback for menu item File->Open Excellon. Defines a function that is then passed
@@ -2044,38 +2164,6 @@ class App:
 
         self.file_chooser_action(lambda ao, filename: self.open_excellon(filename))
 
-    def open_gcode(self, filename):
-
-        def obj_init(job_obj, app_obj_):
-            """
-
-            :type app_obj_: App
-            """
-            assert isinstance(app_obj_, App)
-            GLib.idle_add(lambda: app_obj_.set_progress_bar(0.1, "Opening G-Code ..."))
-
-            f = open(filename)
-            gcode = f.read()
-            f.close()
-
-            job_obj.gcode = gcode
-
-            GLib.idle_add(lambda: app_obj_.set_progress_bar(0.2, "Parsing ..."))
-            job_obj.gcode_parse()
-
-            GLib.idle_add(lambda: app_obj_.set_progress_bar(0.6, "Creating geometry ..."))
-            job_obj.create_geometry()
-
-            GLib.idle_add(lambda: app_obj_.set_progress_bar(0.6, "Plotting ..."))
-
-        name = filename.split('/')[-1].split('\\')[-1]
-        self.new_object("cncjob", name, obj_init)
-        self.register_recent("cncjob", filename)
-
-        self.info("Opened: " + filename)
-        GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
-        GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
-
     def on_fileopengcode(self, param):
         """
         Callback for menu item File->Open G-Code. Defines a function that is then passed

+ 0 - 71
FlatCAM.ui

@@ -137,77 +137,6 @@ THE SOFTWARE.</property>
     <property name="can_focus">False</property>
     <property name="stock">gtk-open</property>
   </object>
-  <object class="GtkDialog" id="question_dialog">
-    <property name="can_focus">False</property>
-    <property name="border_width">5</property>
-    <property name="type_hint">dialog</property>
-    <child internal-child="vbox">
-      <object class="GtkBox" id="question_box">
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <property name="spacing">2</property>
-        <child internal-child="action_area">
-          <object class="GtkButtonBox" id="dialog-action_area1">
-            <property name="can_focus">False</property>
-            <property name="layout_style">end</property>
-            <child>
-              <placeholder/>
-            </child>
-            <child>
-              <placeholder/>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="pack_type">end</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkBox" id="box40">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <child>
-              <object class="GtkImage" id="image22">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="valign">start</property>
-                <property name="pixbuf">share/warning.png</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="label_warning">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="margin_left">12</property>
-                <property name="margin_right">12</property>
-                <property name="margin_top">12</property>
-                <property name="margin_bottom">12</property>
-                <property name="hexpand">True</property>
-                <property name="label" translatable="yes">label</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-      </object>
-    </child>
-  </object>
   <object class="GtkOffscreenWindow" id="offscreenwindow_dblsided">
     <property name="can_focus">False</property>
     <child>

+ 6 - 0
FlatCAMObj.py

@@ -304,6 +304,12 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
                         [poly['polygon'] for poly in self.regions] + \
                         self.flash_geometry
 
+        # Make sure geometry is iterable.
+        try:
+            _ = iter(geometry)
+        except TypeError:
+            geometry = [geometry]
+
         if self.options["multicolored"]:
             linespec = '-'
         else:

+ 188 - 24
camlib.py

@@ -651,6 +651,9 @@ class Gerber (Geometry):
         # Geometry from flashes
         self.flash_geometry = []
 
+        # On-the-fly geometry. Initialized to an empty polygon
+        self.otf_geometry = Polygon()
+
         # Aperture Macros
         # TODO: Make sure these can be serialized
         self.aperture_macros = {}
@@ -686,16 +689,20 @@ class Gerber (Geometry):
         # May begin with G54 but that is deprecated
         self.tool_re = re.compile(r'^(?:G54)?D(\d\d+)\*$')
 
-        # G01 - Linear interpolation plus flashes
+        # G01... - Linear interpolation plus flashes with coordinates
         # Operation code (D0x) missing is deprecated... oh well I will support it.
-        self.lin_re = re.compile(r'^(?:G0?(1))?(?:X(-?\d+))?(?:Y(-?\d+))?(?:D0?([123]))?\*$')
+        self.lin_re = re.compile(r'^(?:G0?(1))?(?=.*X(-?\d+))?(?=.*Y(-?\d+))?[XY][^DIJ]*(?:D0?([123]))?\*$')
 
-        self.setlin_re = re.compile(r'^(?:G0?1)\*')
+        #
+        self.opcode_re = re.compile(r'^D0?([123])\*$')
 
-        # G02/3 - Circular interpolation
+        # G02/3... - Circular interpolation with coordinates
         # 2-clockwise, 3-counterclockwise
-        self.circ_re = re.compile(r'^(?:G0?([23]))?(?:X(-?\d+))?(?:Y(-?\d+))' +
-                                  '?(?:I(-?\d+))?(?:J(-?\d+))?D0([12])\*$')
+        # Operation code (D0x) missing is deprecated... oh well I will support it.
+        # Optional start with G02 or G03, optional end with D01 or D02 with
+        # optional coordinates but at least one in any order.
+        self.circ_re = re.compile(r'^(?:G0?([23]))?(?=.*X(-?\d+))?(?=.*Y(-?\d+))' +
+                                  '?(?=.*I(-?\d+))?(?=.*J(-?\d+))?[XYIJ][^D]*(?:D0([12]))?\*$')
 
         # G01/2/3 Occurring without coordinates
         self.interp_re = re.compile(r'^(?:G0?([123]))\*')
@@ -1038,6 +1045,7 @@ class Gerber (Geometry):
         current_y = None
 
         # Absolute or Relative/Incremental coordinates
+        # Not implemented
         absolute = True
 
         # How to interpret circular interpolation: SINGLE or MULTI
@@ -1046,6 +1054,12 @@ class Gerber (Geometry):
         # Indicates we are parsing an aperture macro
         current_macro = None
 
+        # Indicates the current polarity: D-Dark, C-Clear
+        current_polarity = 'D'
+
+        # If a region is being defined
+        making_region = False
+
         #### Parsing starts here ####
         line_num = 0
         for gline in glines:
@@ -1107,19 +1121,39 @@ class Gerber (Geometry):
                     path.append([current_x, current_y])
                     last_path_aperture = current_aperture
 
-                # Pen up: finish path
                 elif current_operation_code == 2:
                     if len(path) > 1:
-                        if last_path_aperture is None:
-                            print "Warning: No aperture defined for curent path. (%d)" % line_num
-                        self.paths.append({"linestring": LineString(path),
-                                           "aperture": last_path_aperture})
+
+                        # self.paths.append({"linestring": LineString(path),
+                        #                    "aperture": last_path_aperture})
+
+                        # --- 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)
+
                     path = [[current_x, current_y]]  # Start new path
 
                 # Flash
                 elif current_operation_code == 3:
-                    self.flashes.append({"loc": Point([current_x, current_y]),
-                                         "aperture": current_aperture})
+                    # self.flashes.append({"loc": Point([current_x, current_y]),
+                    #                      "aperture": current_aperture})
+
+                    # --- 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)
 
                 continue
 
@@ -1168,8 +1202,17 @@ class Gerber (Geometry):
                     if len(path) > 1:
                         if last_path_aperture is None:
                             print "Warning: No aperture defined for curent path. (%d)" % line_num
-                        self.paths.append({"linestring": LineString(path),
-                                           "aperture": last_path_aperture})
+                        # self.paths.append({"linestring": LineString(path),
+                        #                    "aperture": last_path_aperture})
+
+                        # --- 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)
+
                     current_x = x
                     current_y = y
                     path = [[current_x, current_y]]  # Start new path
@@ -1204,6 +1247,19 @@ class Gerber (Geometry):
                 if quadrant_mode == 'SINGLE':
                     print "Warning: Single quadrant arc are not implemented yet. (%d)" % line_num
 
+            ### Operation code alone
+            match = self.opcode_re.search(gline)
+            if match:
+                current_operation_code = int(match.group(1))
+                if current_operation_code == 3:
+                    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)
+                continue
+
             ### G74/75* - Single or multiple quadrant arcs
             match = self.quad_re.search(gline)
             if match:
@@ -1213,20 +1269,49 @@ class Gerber (Geometry):
                     quadrant_mode = 'MULTI'
                 continue
 
+            ### G36* - Begin region
+            if self.regionon_re.search(gline):
+                if len(path) > 1:
+                    # Take care of what is left in the path
+                    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)
+                    path = [path[-1]]
+
+                making_region = True
+                continue
+
             ### G37* - End region
             if self.regionoff_re.search(gline):
+                making_region = False
+
                 # Only one path defines region?
+                # This can happen if D02 happened before G37 and
+                # is not and error.
                 if len(path) < 3:
-                    print "ERROR: Path contains less than 3 points:"
-                    print path
-                    print "Line (%d): " % line_num, gline
-                    path = []
+                    # print "ERROR: Path contains less than 3 points:"
+                    # print path
+                    # print "Line (%d): " % line_num, gline
+                    # path = []
+                    #path = [[current_x, current_y]]
                     continue
 
                 # For regions we may ignore an aperture that is None
-                self.regions.append({"polygon": Polygon(path),
-                                     "aperture": last_path_aperture})
-                #path = []
+                # self.regions.append({"polygon": Polygon(path),
+                #                      "aperture": last_path_aperture})
+
+                # --- 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)
+
                 path = [[current_x, current_y]]  # Start new path
                 continue
             
@@ -1252,6 +1337,22 @@ class Gerber (Geometry):
                 current_aperture = match.group(1)
                 continue
 
+            ### Polarity change
+            # Example: %LPD*% or %LPC*%
+            match = self.lpol_re.search(gline)
+            if match:
+                if len(path) > 1 and current_polarity != match.group(1):
+
+                    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)
+                    path = [path[-1]]
+                current_polarity = match.group(1)
+                continue
+
             ### Number format
             # Example: %FSLAX24Y24*%
             # TODO: This is ignoring most of the format. Implement the rest.
@@ -1297,8 +1398,71 @@ class Gerber (Geometry):
         
         if len(path) > 1:
             # EOF, create shapely LineString if something still in path
-            self.paths.append({"linestring": LineString(path),
-                               "aperture": last_path_aperture})
+            # self.paths.append({"linestring": LineString(path),
+            #                    "aperture": last_path_aperture})
+
+            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)
+
+    @staticmethod
+    def create_flash_geometry(location, aperture):
+
+        if type(location) == list:
+            location = Point(location)
+
+        if aperture['type'] == 'C':  # Circles
+            return location.buffer(aperture['size']/2)
+
+        if aperture['type'] == 'R':  # Rectangles
+            loc = location.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
+            return shply_box(minx, miny, maxx, maxy)
+
+        if aperture['type'] == 'O':  # Obround
+            loc = location.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)
+            return cascaded_union([c1, c2]).convex_hull
+
+        if aperture['type'] == 'P':  # Regular polygon
+            loc = location.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'])
+            return ply
+
+        if aperture['type'] == 'AM':  # Aperture Macro
+            loc = location.coords[0]
+            flash_geo = aperture['macro'].make_geometry(aperture['modifiers'])
+            return affinity.translate(flash_geo, xoff=loc[0], yoff=loc[1])
+
+        return None
 
     def do_flashes(self):
         """

+ 1 - 1
recent.json

@@ -1 +1 @@
-[{"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": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\RTWO_fc5_3.fcproj"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\RTWO_fc5_2.fcproj"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\RTWO_fc5.fcproj"}, {"kind": "cncjob", "filename": "Z:\\CNC\\testpcb\\2\\noname-F_Cu_ISOLATION_GCODE3.ngc"}, {"kind": "cncjob", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\RTWO1_CNC\\iso_bottom1.gcode"}]
+[{"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"}]