From 2ed0f73f8796a325642221892d1d3b104c53a55e Mon Sep 17 00:00:00 2001 From: Juan Pablo Caram Date: Sat, 12 Apr 2014 02:16:39 -0400 Subject: [PATCH] Support for LPD and LPC in Gerber. Major changes in Gerber parser. --- FlatCAM.py | 246 ++++++++++++++++++++++++++++++++++---------------- FlatCAM.ui | 71 --------------- FlatCAMObj.py | 6 ++ camlib.py | 212 ++++++++++++++++++++++++++++++++++++++----- recent.json | 2 +- 5 files changed, 362 insertions(+), 175 deletions(-) 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 - 5 - dialog - - - False - vertical - 2 - - - False - end - - - - - - - - - False - True - end - 0 - - - - - True - False - - - True - False - start - share/warning.png - - - False - True - 0 - - - - - True - False - 12 - 12 - 12 - 12 - True - label - - - False - True - 1 - - - - - False - True - 1 - - - - - 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