From 68a275e042f1adabd48faa3ccd49d4e6c268ebbf Mon Sep 17 00:00:00 2001 From: Juan Pablo Caram Date: Thu, 27 Feb 2014 00:12:49 -0500 Subject: [PATCH] Excellon parser bug fixed, improved Gerber parse, added icons, added some tooltips. --- FlatCAM.py | 131 ++++++++++--- FlatCAM.ui | 30 ++- camlib.py | 400 ++++++++++++++++++++++++++------------ share/flatcam_icon16.png | Bin 0 -> 336 bytes share/flatcam_icon256.png | Bin 0 -> 3926 bytes share/flatcam_icon32.png | Bin 0 -> 603 bytes share/flatcam_icon48.png | Bin 0 -> 833 bytes 7 files changed, 409 insertions(+), 152 deletions(-) create mode 100644 share/flatcam_icon16.png create mode 100644 share/flatcam_icon256.png create mode 100644 share/flatcam_icon32.png create mode 100644 share/flatcam_icon48.png diff --git a/FlatCAM.py b/FlatCAM.py index c0df2460..f1d870c0 100644 --- a/FlatCAM.py +++ b/FlatCAM.py @@ -11,6 +11,7 @@ import threading # TODO: Bundle together. This is just for debugging. 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 @@ -23,6 +24,7 @@ from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCan from camlib import * import sys +import urllib ######################################## @@ -129,7 +131,7 @@ class FlatCAMObj: def set_form_item(self, option): """ - Copies the specified options to the UI form. + Copies the specified option to the UI form. :param option: Name of the option (Key in ``self.options``). :type option: str @@ -150,6 +152,13 @@ class FlatCAMObj: print "Unknown kind of form item:", fkind def read_form_item(self, option): + """ + Reads the specified option from the UI form into ``self.options``. + + :param option: Name of the option. + :type option: str + :return: None + """ fkind = self.form_kinds[option] fname = fkind + "_" + self.kind + "_" + option @@ -175,25 +184,23 @@ class FlatCAMObj: # Creates the axes if necessary and sets them up. self.setup_axes(figure) - # Clear axes. - # self.axes.cla() - # return - def serialize(self): """ Returns a representation of the object as a dictionary so it can be later exported as JSON. Override this method. - @return: Dictionary representing the object - @rtype: dict + + :return: Dictionary representing the object + :rtype: dict """ return def deserialize(self, obj_dict): """ Re-builds an object from its serialized version. - @param obj_dict: Dictionary representing a FlatCAMObj - @type obj_dict: dict - @return None + + :param obj_dict: Dictionary representing a FlatCAMObj + :type obj_dict: dict + :return None """ return @@ -257,6 +264,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): :return: None :rtype: None """ + factor = Gerber.convert_units(self, units) self.options['isotooldia'] *= factor @@ -317,7 +325,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): class FlatCAMExcellon(FlatCAMObj, Excellon): """ - Represents Excellon code. + Represents Excellon/Drill code. """ def __init__(self, name): @@ -367,7 +375,12 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): if not self.options["plot"]: return - # Plot excellon + try: + _ = iter(self.solid_geometry) + except TypeError: + self.solid_geometry = [self.solid_geometry] + + # Plot excellon (All polygons?) for geo in self.solid_geometry: x, y = geo.exterior.coords.xy self.axes.plot(x, y, 'r-') @@ -446,6 +459,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): print "FlatCAMCNCjob.convert_units()" self.options["tooldia"] *= factor + class FlatCAMGeometry(FlatCAMObj, Geometry): """ Geometric object not associated with a specific @@ -596,12 +610,11 @@ class App: # Needed to interact with the GUI from other threads. GObject.threads_init() - ## GUI ## + #### GUI #### self.gladefile = "FlatCAM.ui" self.builder = Gtk.Builder() self.builder.add_from_file(self.gladefile) self.window = self.builder.get_object("window1") - self.window.set_title("FlatCAM - Alpha 1 UNSTABLE - Check for updates!") self.position_label = self.builder.get_object("label3") self.grid = self.builder.get_object("grid1") self.notebook = self.builder.get_object("notebook1") @@ -613,22 +626,23 @@ class App: # White (transparent) background on the "Options" tab. self.builder.get_object("vp_options").override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1)) - # Combo box to choose between project and application options. self.combo_options = self.builder.get_object("combo_options") self.combo_options.set_active(1) - ## Event handling ## + self.setup_project_list() # The "Project" tab + self.setup_component_editor() # The "Selected" tab + + #### Event handling #### self.builder.connect_signals(self) - ## Make plot area ## + #### Make plot area #### self.figure = None self.axes = None self.canvas = None self.setup_plot() - self.setup_project_list() # The "Project" tab - self.setup_component_editor() # The "Selected" tab + self.setup_tooltips() #### DATA #### self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) @@ -663,8 +677,6 @@ class App: self.radios_inv = {"units": {"IN": "rb_inch", "MM": "rb_mm"}, "gerber_gaps": {"tb": "rb_app_2tb", "lr": "rb_app_2lr", "4": "rb_app_4"}} - # self.combos = [] - # Options for each kind of FlatCAMObj. # Example: 'gerber_plot': 'cb'. The widget name would be: 'cb_app_gerber_plot' for FlatCAMClass in [FlatCAMExcellon, FlatCAMGeometry, FlatCAMGerber, FlatCAMCNCjob]: @@ -677,22 +689,34 @@ class App: self.plot_click_subscribers = {} - # Initialization + #### Initialization #### self.load_defaults() self.options.update(self.defaults) # Copy app defaults to project options self.options2form() # Populate the app defaults form self.units_label.set_text("[" + self.options["units"] + "]") - # For debugging only + #### Check for updates #### + self.version = 1 + t1 = threading.Thread(target=self.versionCheck) + t1.daemon = True + t1.start() + + #### For debugging only ### def someThreadFunc(self): print "Hello World!" t = threading.Thread(target=someThreadFunc, args=(self,)) + t.daemon = True t.start() ######################################## ## START ## ######################################## + self.icon256 = GdkPixbuf.Pixbuf.new_from_file('share/flatcam_icon256.png') + self.icon48 = GdkPixbuf.Pixbuf.new_from_file('share/flatcam_icon48.png') + self.icon16 = GdkPixbuf.Pixbuf.new_from_file('share/flatcam_icon16.png') + Gtk.Window.set_default_icon_list([self.icon16, self.icon48, self.icon256]) + self.window.set_title("FlatCAM - Alpha 2 UNSTABLE - Check for updates!") self.window.set_default_size(900, 600) self.window.show_all() @@ -1309,6 +1333,67 @@ class App: for obj in self.stuff: combo.append_text(obj) + def versionCheck(self): + """ + Checks for the latest version of the program. Alerts the + user if theirs is outdated. This method is meant to be run + in a saeparate thread. + + :return: None + """ + + try: + f = urllib.urlopen("http://caram.cl/flatcam/VERSION") # TODO: Hardcoded. + except: + GLib.idle_add(lambda: self.info("ERROR trying to check for latest version.")) + return + + try: + data = json.load(f) + except: + GLib.idle_add(lambda: self.info("ERROR trying to check for latest version.")) + f.close() + return + + f.close() + + if self.version >= data["version"]: + GLib.idle_add(lambda: self.info("FlatCAM is up to date!")) + return + + label = Gtk.Label("There is a newer version of FlatCAM\n" + + "available for download:\n\n" + + data["name"] + "\n\n" + data["message"]) + dialog = Gtk.Dialog("Newer Version Available", self.window, 0, + (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + Gtk.STOCK_OK, Gtk.ResponseType.OK)) + dialog.set_default_size(150, 100) + dialog.set_modal(True) + box = dialog.get_content_area() + box.set_border_width(10) + box.add(label) + + def do_dialog(): + dialog.show_all() + response = dialog.run() + dialog.destroy() + + GLib.idle_add(lambda: do_dialog()) + + return + + def setup_tooltips(self): + tooltips = { + "cb_gerber_plot": "Plot this object on the main window.", + "cb_gerber_mergepolys": "Show overlapping polygons as single.", + "cb_gerber_solid": "Paint inside polygons.", + "cb_gerber_multicolored": "Draw polygons with different polygons.", + "button1": "" + } + + for widget in tooltips: + self.builder.get_object(widget).set_tooltip_markup(tooltips[widget]) + ######################################## ## EVENT HANDLERS ## ######################################## diff --git a/FlatCAM.ui b/FlatCAM.ui index eb47d239..982696c4 100644 --- a/FlatCAM.ui +++ b/FlatCAM.ui @@ -3045,7 +3045,8 @@ to application defaults. True False - Zoom Fit + Zoom Fit. +(Click on plot and hit <b>1</b>) Fit True gtk-zoom-100 @@ -3060,7 +3061,9 @@ to application defaults. True False - Zoom+ + Zoom in. +(Click on plot and hit <b>3</b> +to zoom around a point) Zoom+ True gtk-zoom-in @@ -3075,7 +3078,9 @@ to application defaults. True False - Zoom- + Zoom Out. +(Click on plot and hit <b>2</b> +to zoom around a point) Zoom- True gtk-zoom-out @@ -3090,6 +3095,7 @@ to application defaults. True False + Clear Plot Clear Plots True gtk-stop @@ -3104,6 +3110,7 @@ to application defaults. True False + Re-plot all Re-plot True gtk-redo @@ -3118,6 +3125,8 @@ to application defaults. True False + Delete selected +object. Delete Object True gtk-delete @@ -3165,6 +3174,7 @@ to application defaults. True False + Objects in the project. Project @@ -3190,6 +3200,8 @@ to application defaults. True False + Options and action +for the current object. Selected @@ -3229,6 +3241,15 @@ to application defaults. True False + Application defaults get transfered +to every new project. Project options +get inherited by new project objects. + +<b>Save</b> application defaults +by choosing <i>File + Save defaults</i>. + +Project obtions are saved with the +project. 10 10 5 @@ -4444,6 +4465,8 @@ to application defaults. True False + Project and application +defaults. Options @@ -4468,6 +4491,7 @@ to application defaults. True False + Active tool Tool diff --git a/camlib.py b/camlib.py index 4ebdecfc..6d0d71c0 100644 --- a/camlib.py +++ b/camlib.py @@ -348,6 +348,9 @@ class Gerber (Geometry): # LP - Level polarity self.lpol_re = re.compile(r'^%LP([DC])\*%$') + # TODO: This is bad. + self.steps_per_circ = 40 + def scale(self, factor): """ Scales the objects' geometry on the XY plane by a given factor. @@ -453,8 +456,12 @@ class Gerber (Geometry): self.buffered_paths = [] for path in self.paths: - width = self.apertures[path["aperture"]]["size"] - self.buffered_paths.append(path["linestring"].buffer(width/2)) + 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, gline): """ @@ -523,7 +530,7 @@ class Gerber (Geometry): :rtype: None """ - path = [] # Coordinates of the current path + path = [] # Coordinates of the current path, each is [x, y] last_path_aperture = None current_aperture = None @@ -540,11 +547,25 @@ class Gerber (Geometry): current_x = None current_y = None - for gline in glines: + # How to interprest circular interpolation: SINGLE or MULTI + quadrant_mode = None - # Linear interpolation plus flashes + line_num = 0 + for gline in glines: + line_num += 1 + + ## G01 - Linear interpolation plus flashes + # Operation code (D0x) missing is deprecated... oh well I will support it. match = self.lin_re.search(gline) if match: + # Dxx alone? Will ignore for now. + if match.group(1) is None and match.group(2) is None and match.group(3) is None: + try: + current_operation_code = int(match.group(4)) + except: + pass # A line with just * will match too. + continue + # Parse coordinates if match.group(2) is not None: current_x = parse_gerber_number(match.group(2), self.frac_digits) @@ -553,69 +574,141 @@ class Gerber (Geometry): # Parse operation code if match.group(4) is not None: - current_operation_code = match.group(4) + current_operation_code = int(match.group(4)) # Pen down: add segment - if current_operation_code == '1': + if current_operation_code == 1: path.append([current_x, current_y]) last_path_aperture = current_aperture # Pen up: finish path - elif current_operation_code == '2': + 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}) - path = [[current_x, current_y]] + path = [[current_x, current_y]] # Start new path # Flash - elif current_operation_code == '3': + elif current_operation_code == 3: self.flashes.append({"loc": [current_x, current_y], "aperture": current_aperture}) continue - # if gline.find("D01*") != -1: # pen down - # path.append(parse_gerber_coords(gline, self.int_digits, self.frac_digits)) - # last_path_aperture = current_aperture - # continue - # - # if gline.find("D02*") != -1: # pen up - # if len(path) > 1: - # # Path completed, create shapely LineString - # self.paths.append({"linestring": LineString(path), - # "aperture": last_path_aperture}) - # path = [parse_gerber_coords(gline, self.int_digits, self.frac_digits)] - # continue - # - # indexd3 = gline.find("D03*") - # if indexd3 > 0: # Flash - # self.flashes.append({"loc": parse_gerber_coords(gline, self.int_digits, self.frac_digits), - # "aperture": current_aperture}) - # continue - # if indexd3 == 0: # Flash? - # print "WARNING: Uninplemented flash style:", gline - # continue + ## G02/3 - Circular interpolation + # 2-clockwise, 3-counterclockwise + match = self.circ_re.search(gline) + if match: - # End region - if self.regionoff_re.search(gline): - # Only one path defines region? - self.regions.append({"polygon": Polygon(path), - "aperture": last_path_aperture}) - path = [] + mode, x, y, i, j, d = match.groups() + try: + x = parse_gerber_number(x, self.frac_digits) + except: + x = current_x + try: + y = parse_gerber_number(y, self.frac_digits) + except: + y = current_y + try: + i = parse_gerber_number(i, self.frac_digits) + except: + i = 0 + try: + j = parse_gerber_number(j, self.frac_digits) + except: + j = 0 + + if quadrant_mode is None: + print "ERROR: Found arc without preceding quadrant specification G74 or G75. (%d)" % line_num + print gline + continue + + if mode is None and current_interpolation_mode not in [2, 3]: + print "ERROR: Found arc without circular interpolation mode defined. (%d)" % line_num + print gline + continue + elif mode is not None: + current_interpolation_mode = int(mode) + + # Set operation code if provided + if d is not None: + current_operation_code = int(d) + + # Nothing created! Pen Up. + if current_operation_code == 2: + print "Warning: Arc with D2. (%d)" % line_num + 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}) + current_x = x + current_y = y + path = [[current_x, current_y]] # Start new path + continue + + # Flash should not happen here + if current_operation_code == 3: + print "ERROR: Trying to flash within arc. (%d)" % line_num + continue + + if quadrant_mode == 'MULTI': + center = [i + current_x, j + current_y] + radius = sqrt(i**2 + j**2) + start = arctan2(-j, -i) + stop = arctan2(-center[1] + y, -center[0] + x) + arcdir = [None, None, "cw", "ccw"] + this_arc = arc(center, radius, start, stop, + arcdir[current_interpolation_mode], + self.steps_per_circ) + + # Last point in path is current point + current_x = this_arc[-1][0] + current_y = this_arc[-1][1] + + # Append + path += this_arc + + last_path_aperture = current_aperture + + continue + + if quadrant_mode == 'SINGLE': + print "Warning: Single quadrant arc are not implemented yet. (%d)" % line_num + + ## G74/75* - Single or multiple quadrant arcs + match = self.quad_re.search(gline) + if match: + if match.group(1) == '4': + quadrant_mode = 'SINGLE' + else: + quadrant_mode = 'MULTI' continue - # if gline.find("G37*") != -1: # end region - # # Only one path defines region? - # self.regions.append({"polygon": Polygon(path), - # "aperture": last_path_aperture}) - # path = [] - # continue + ## G37* - End region + if self.regionoff_re.search(gline): + # Only one path defines region? + if len(path) < 3: + print "ERROR: Path contains less than 3 points:" + print path + print "Line (%d): " % line_num, gline + path = [] + continue + + # For regions we may ignore an aperture that is None + self.regions.append({"polygon": Polygon(path), + "aperture": last_path_aperture}) + #path = [] + path = [[current_x, current_y]] # Start new path + continue if gline.find("%ADD") != -1: # aperture definition self.aperture_parse(gline) # adds element to apertures continue - # Interpolation mode change + ## G01/2/3* - Interpolation mode change # Can occur along with coordinates and operation code but # sometimes by itself (handled here). # Example: G01* @@ -624,22 +717,15 @@ class Gerber (Geometry): current_interpolation_mode = int(match.group(1)) continue - # Tool/aperture change + ## Tool/aperture change # Example: D12* match = self.tool_re.search(gline) if match: current_aperture = match.group(1) continue - # indexstar = gline.find("*") - # if gline.find("D") == 0: # Aperture change - # current_aperture = gline[1:indexstar] - # continue - # if gline.find("G54D") == 0: # Aperture change (deprecated) - # current_aperture = gline[4:indexstar] - # continue - - # Number format + ## Number format + # Example: %FSLAX24Y24*% # TODO: This is ignoring most of the format. Implement the rest. match = self.fmt_re.search(gline) if match: @@ -647,30 +733,20 @@ class Gerber (Geometry): self.frac_digits = int(match.group(4)) continue - # if gline.find("%FS") != -1: # Format statement - # indexx = gline.find("X") - # self.int_digits = int(gline[indexx + 1]) - # self.frac_digits = int(gline[indexx + 2]) - # continue - - # Mode (IN/MM) + ## Mode (IN/MM) + # Example: %MOIN*% match = self.mode_re.search(gline) if match: self.units = match.group(1) continue - print "WARNING: Line ignored:", gline + print "WARNING: Line ignored (%d):" % line_num, gline if len(path) > 1: # EOF, create shapely LineString if something still in path self.paths.append({"linestring": LineString(path), "aperture": last_path_aperture}) - # if len(path) > 1: - # # EOF, create shapely LineString if something still in path - # self.paths.append({"linestring": LineString(path), - # "aperture": current_aperture}) - def do_flashes(self): """ Creates geometry for Gerber flashes (aperture on a single point). @@ -778,6 +854,7 @@ class Excellon(Geometry): def __init__(self): """ The constructor takes no parameters. + :return: Excellon object. :rtype: Excellon """ @@ -795,8 +872,74 @@ class Excellon(Geometry): # Always append to it because it carries contents # from Geometry. self.ser_attrs += ['tools', 'drills', 'zeros'] + + #### Patterns #### + # Regex basics: + # ^ - beginning + # $ - end + # *: 0 or more, +: 1 or more, ?: 0 or 1 + + # M48 - Beggining of Part Program Header + self.hbegin_re = re.compile(r'^M48$') + + # M95 or % - End of Part Program Header + # NOTE: % has different meaning in the body + self.hend_re = re.compile(r'^(?:M95|%)$') + + # FMAT Excellon format + self.fmat_re = re.compile(r'^FMAT,([12])$') + + # Number format and units + # INCH uses 6 digits + # METRIC uses 5/6 + self.units_re = re.compile(r'^(INCH|METRIC)(?:,([TL])Z)?$') + + # Tool definition/parameters (?= is look-ahead + # NOTE: This might be an overkill! + self.toolset_re = re.compile(r'^T(0?\d|\d\d)(?=.*C(\d*\.?\d*))?' + + r'(?=.*F(\d*\.?\d*))?(?=.*S(\d*\.?\d*))?' + + r'(?=.*B(\d*\.?\d*))?(?=.*H(\d*\.?\d*))?' + + r'(?=.*Z(-?\d*\.?\d*))?[CFSBHT]') + + # Tool select + # Can have additional data after tool number but + # is ignored if present in the header. + # Warning: This will match toolset_re too. + self.toolsel_re = re.compile(r'^T((?:\d\d)|(?:\d))') + + # Comment + self.comm_re = re.compile(r'^;(.*)$') + + # Absolute/Incremental G90/G91 + self.absinc_re = re.compile(r'^G9([01])$') + + # Modes of operation + # 1-linear, 2-circCW, 3-cirCCW, 4-vardwell, 5-Drill + self.modes_re = re.compile(r'^G0([012345])') + + # Measuring mode + # 1-metric, 2-inch + self.meas_re = re.compile(r'^M7([12])$') + + # Coordinates + self.xcoord_re = re.compile(r'^X(\d*\.?\d*)(?:Y\d*\.?\d*)?$') + self.ycoord_re = re.compile(r'^(?:X\d*\.?\d*)?Y(\d*\.?\d*)$') + + # R - Repeat hole (# times, X offset, Y offset) + self.rep_re = re.compile(r'^R(\d+)(?=.*[XY])+(?:X(\d*\.?\d*))?(?:Y(\d*\.?\d*))?$') + + # Various stop/pause commands + self.stop_re = re.compile(r'^((G04)|(M09)|(M06)|(M00)|(M30))') def parse_file(self, filename): + """ + Reads the specified file as array of lines as + passes it to ``parse_lines()``. + + :param filename: The file to be read and parsed. + :type filename: str + :return: None + """ efile = open(filename, 'r') estr = efile.readlines() efile.close() @@ -805,71 +948,79 @@ class Excellon(Geometry): def parse_lines(self, elines): """ Main Excellon parser. + + :param elines: List of strings, each being a line of Excellon code. + :type elines: list + :return: None """ - units_re = re.compile(r'^(INCH|METRIC)(?:,([TL])Z)?$') current_tool = "" - + in_header = False + for eline in elines: - - ## Tool definitions ## - # TODO: Verify all this - indexT = eline.find("T") - indexC = eline.find("C") - indexF = eline.find("F") - # Type 1 - if indexT != -1 and indexC > indexT and indexF > indexC: - tool = eline[1:indexC] - spec = eline[indexC+1:indexF] - self.tools[tool] = float(spec) - continue - # Type 2 - # TODO: Is this inches? - #indexsp = eline.find(" ") - #indexin = eline.find("in") - #if indexT != -1 and indexsp > indexT and indexin > indexsp: - # tool = eline[1:indexsp] - # spec = eline[indexsp+1:indexin] - # self.tools[tool] = spec - # continue - # Type 3 - if indexT != -1 and indexC > indexT: - tool = eline[1:indexC] - spec = eline[indexC+1:-1] - self.tools[tool] = float(spec) - continue - - ## Tool change - if indexT == 0: - current_tool = eline[1:-1] - continue - - ## Drill - indexx = eline.find("X") - indexy = eline.find("Y") - if indexx != -1 and indexy != -1: - x = float(int(eline[indexx+1:indexy])/10000.0) - y = float(int(eline[indexy+1:-1])/10000.0) - self.drills.append({'point': Point((x, y)), 'tool': current_tool}) + + ## Header Begin/End ## + if self.hbegin_re.search(eline): + in_header = True continue - # Units and number format - match = units_re.match(eline) - if match: - self.zeros = match.group(2) # "T" or "L" - self.units = {"INCH": "IN", "METRIC": "MM"}[match.group(1)] + if self.hend_re.search(eline): + in_header = False + continue + + #### Body #### + if not in_header: + + ## Tool change ## + match = self.toolsel_re.search(eline) + if match: + current_tool = str(int(match.group(1))) + continue + + ## Drill ## + indexx = eline.find("X") + indexy = eline.find("Y") + if indexx != -1 and indexy != -1: + x = float(int(eline[indexx+1:indexy])/10000.0) + y = float(int(eline[indexy+1:-1])/10000.0) + self.drills.append({'point': Point((x, y)), 'tool': current_tool}) + continue + + #### Header #### + if in_header: + + ## Tool definitions ## + match = self.toolset_re.search(eline) + if match: + name = str(int(match.group(1))) + spec = { + "C": float(match.group(2)), + # "F": float(match.group(3)), + # "S": float(match.group(4)), + # "B": float(match.group(5)), + # "H": float(match.group(6)), + # "Z": float(match.group(7)) + } + self.tools[name] = spec + continue + + ## Units and number format ## + match = self.units_re.match(eline) + if match: + self.zeros = match.group(2) # "T" or "L" + self.units = {"INCH": "IN", "METRIC": "MM"}[match.group(1)] + continue print "WARNING: Line ignored:", eline def create_geometry(self): self.solid_geometry = [] - sizes = {} - for tool in self.tools: - sizes[tool] = float(self.tools[tool]) + for drill in self.drills: - poly = Point(drill['point']).buffer(sizes[drill['tool']]/2.0) + poly = Point(drill['point']).buffer(self.tools[drill['tool']]["C"]/2.0) self.solid_geometry.append(poly) - self.solid_geometry = cascaded_union(self.solid_geometry) + + #self.solid_geometry = cascaded_union(self.solid_geometry) def scale(self, factor): """ @@ -910,7 +1061,9 @@ class Excellon(Geometry): # Tools for tname in self.tools: - self.tools[tname] *= factor + self.tools[tname]["C"] *= factor + + self.create_geometry() return factor @@ -1236,8 +1389,7 @@ class CNCjob(Geometry): arcdir = [None, None, "cw", "ccw"] if current['G'] in [0, 1]: # line path.append((x, y)) - # geometry.append({'geom': LineString([(current['X'], current['Y']), - # (x, y)]), 'kind': kind}) + if current['G'] in [2, 3]: # arc center = [gobj['I'] + current['X'], gobj['J'] + current['Y']] radius = sqrt(gobj['I']**2 + gobj['J']**2) @@ -1246,10 +1398,6 @@ class CNCjob(Geometry): path += arc(center, radius, start, stop, arcdir[current['G']], self.steps_per_circ) - # geometry.append({'geom': arc(center, radius, start, stop, - # arcdir[current['G']], - # self.steps_per_circ), - # 'kind': kind}) # Update current instruction for code in gobj: diff --git a/share/flatcam_icon16.png b/share/flatcam_icon16.png new file mode 100644 index 0000000000000000000000000000000000000000..6a88a9718f6f24ed5d36e0583acbc12a4c964182 GIT binary patch literal 336 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0Vf zYe1Os14sN;pdfpRr>`sfJtip;Ne=x2$wxq;N1iT@Ar`0iUN-bP;vm8LAird*aqH4) zdKEm{%uP)b()g27lczc_`N$Bxf@4vVxTo+Dr#&s|0@q*MSQ6oMCARf%Vg=9VXMaE6 zJG0@zE{{nnk6u|H5?J3+t9*J>zr^F`875s84F|70%e6`DDQ@Zev1HY%(~C5wq;h`0 z`|i5M@0WiRTXg5N2(;C{Hn*5%K4qz-g|x?W(GyYDk4=2#Kilj->L*`#VW#_=M-smy zR=)nVA#;|k&6j7FPCnpUU2*Vby-~zG(<7POuh%|EJ1_lX>$Rx=mEGq*$0fgc@H?JW gO?RK}o9a5ozgw+;`n+2C9_U*JPgg&ebxsLQ03|Gupa1{> literal 0 HcmV?d00001 diff --git a/share/flatcam_icon256.png b/share/flatcam_icon256.png new file mode 100644 index 0000000000000000000000000000000000000000..7563feaa9ac3ce96e8269378dc2fe92f7274cc83 GIT binary patch literal 3926 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 literal 0 HcmV?d00001 diff --git a/share/flatcam_icon32.png b/share/flatcam_icon32.png new file mode 100644 index 0000000000000000000000000000000000000000..199ff6856dd269513f95c943c03ef8cab4abe70d GIT binary patch literal 603 zcmV-h0;K(kP)Px#32;bRa{vH6d;kDFd;#zXU$p=L00(qQO+^RZ0vZ$;Fdk`;Jpcd!?MXyIR9M69 zm%nRMQ545NH@rxKq@p2irCSmZLKe*+rl`AguyGXp4@8$NE<(uUB4d)3HX*!*@{eEAp zwPLN6;czIKOh$lLu9IMf^Jl;f9lr+-0TTnb085j}9o{3$EJ0D0Bl zhA)_5JkeJ;jR*3AP9n#lGQul?mR$+N%#>s7g@UmTyyj)@SswuNUPP7 zd_FJ0Enfguf$veqGsehZFqn#nG#U*7dOiTG>fAoi0B!^8`h8Td*QL|xNTE;=;Lf}O zECJiV4zLp4oXh15a1*!>TuGcmF%vZf8~~rAi=)vzB_pC&RhTh002ovPDHLkV1kQ-1vdZy literal 0 HcmV?d00001 diff --git a/share/flatcam_icon48.png b/share/flatcam_icon48.png new file mode 100644 index 0000000000000000000000000000000000000000..d7a72c0f7b9803b0f3ac40e9b8f4d50cfff8bd6c GIT binary patch literal 833 zcmV-H1HSx;P)Px#32;bRa{vH6d;kDFd;#zXU$p=L00(qQO+^RZ0vZ$;4MqXnEC2uk(@8`@RA}Dq znLlU~aTLct*95DHX-~{hELsbKbvUUCLLHPAEP{j7!CBiyLFnq>Py#|1L5etKDNY^4 zK}4OE4n?zM5Ygy4v|^=N!WnICe$S!5gY$ZcZF+4la1S2&<;T0L1_r!9{s5V9vIHLWXIYRIAm0HkDGCrs@9O6Lev6oeGA0kI(?VXy4on3D5@YD;A4l+qTi$+qA9da#DJFdXz_Jf1}3#_8C6| zq(XL~+b)PiA|aVfMv}>-H%9I@a_9lzmoM06wlJ060TzH2t!oANuH(+A89a3kJPEwf zhZ(>N;4)CzLJ-G*v%r2}5qPOnc0+X9bLW?VYf-F#EgQ@jO$~q;Kn!5#0UQKcn-Rb{ z-3kp+=_lFDgXv`h5i)4AAz~{_I4?i zN=ih;aUALF?3B$kXN`2D^{VwFU~fI4Y&I*|Y!(qAolcWVrR0CtYzKf-z-zr;(hb*Q zKwO!nQt8(C_&B4ZqcSx$W!ScD0MB;Pg)BX6yF>TIy3>kbg$39Hg8ffNpfm-(Lm31n8%<9(}_hoM@@PU|OYEJqKiWNDdT-Io>00000 LNkvXXu0mjfSTcb7 literal 0 HcmV?d00001