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 00000000..6a88a971 Binary files /dev/null and b/share/flatcam_icon16.png differ diff --git a/share/flatcam_icon256.png b/share/flatcam_icon256.png new file mode 100644 index 00000000..7563feaa Binary files /dev/null and b/share/flatcam_icon256.png differ diff --git a/share/flatcam_icon32.png b/share/flatcam_icon32.png new file mode 100644 index 00000000..199ff685 Binary files /dev/null and b/share/flatcam_icon32.png differ diff --git a/share/flatcam_icon48.png b/share/flatcam_icon48.png new file mode 100644 index 00000000..d7a72c0f Binary files /dev/null and b/share/flatcam_icon48.png differ