diff --git a/FlatCAM.py b/FlatCAM.py
index d833a498..ca98ff52 100644
--- a/FlatCAM.py
+++ b/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
diff --git a/FlatCAM.ui b/FlatCAM.ui
index 43d053e5..85521316 100644
--- a/FlatCAM.ui
+++ b/FlatCAM.ui
@@ -137,77 +137,6 @@ THE SOFTWARE.
False
gtk-open
-
False
diff --git a/FlatCAMObj.py b/FlatCAMObj.py
index 53fa8625..1ba91a0a 100644
--- a/FlatCAMObj.py
+++ b/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:
diff --git a/camlib.py b/camlib.py
index 6313a221..d9ea6af4 100644
--- a/camlib.py
+++ b/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):
"""
diff --git a/recent.json b/recent.json
index a6a6aec4..6a0e5a92 100644
--- a/recent.json
+++ b/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"}]
\ No newline at end of file
+[{"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"}]
\ No newline at end of file