Browse Source

Fixed bug in Excellon parser. Did not support numbers with period.

Juan Pablo Caram 12 năm trước cách đây
mục cha
commit
ad5e989331
3 tập tin đã thay đổi với 1257 bổ sung205 xóa
  1. 314 5
      FlatCAM.py
  2. 884 179
      FlatCAM.ui
  3. 59 21
      camlib.py

+ 314 - 5
FlatCAM.py

@@ -19,8 +19,7 @@ import simplejson as json
 from matplotlib.figure import Figure
 from numpy import arange, sin, pi
 from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
-#from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvas
-#from matplotlib.backends.backend_cairo import FigureCanvasCairo as FigureCanvas
+from mpl_toolkits.axes_grid.anchored_artists import AnchoredText
 
 from camlib import *
 import sys
@@ -552,11 +551,23 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         return factor
 
     def plot(self, figure):
+        """
+        Plots the object onto the give figure. Updates the canvas
+        when done.
+
+        :param figure: Matplotlib figure on which to plot.
+        :type figure: Matplotlib.Figure
+        :return: None
+        """
+        # Sets up and clears self.axes.
+        # Attaches axes to the figure... Maybe we want to do that
+        # when plotting is complete?
         FlatCAMObj.plot(self, figure)
 
         if not self.options["plot"]:
             return
 
+        # Make sure solid_geometry is iterable.
         try:
             _ = iter(self.solid_geometry)
         except TypeError:
@@ -611,6 +622,8 @@ class App:
         # Needed to interact with the GUI from other threads.
         GObject.threads_init()
 
+        # GLib.log_set_handler()
+
         #### GUI ####
         self.gladefile = "FlatCAM.ui"
         self.builder = Gtk.Builder()
@@ -634,6 +647,8 @@ class App:
         self.setup_project_list()  # The "Project" tab
         self.setup_component_editor()  # The "Selected" tab
 
+        self.setup_toolbar()
+
         #### Event handling ####
         self.builder.connect_signals(self)
 
@@ -688,7 +703,14 @@ class App:
                 #     self.radios.update({obj.kind + "_" + option: obj.radios[option]})
                 #     self.radios_inv.update({obj.kind + "_" + option: obj.radios_inv[option]})
 
+        ## Event subscriptions ##
         self.plot_click_subscribers = {}
+        self.plot_mousemove_subscribers = {}
+
+        ## Tools ##
+        self.measure = Measurement(self.axes, self.plot_click_subscribers,
+                                   self.plot_mousemove_subscribers,
+                                   lambda: self.canvas.queue_draw())
 
         #### Initialization ####
         self.load_defaults()
@@ -697,13 +719,13 @@ class App:
         self.units_label.set_text("[" + self.options["units"] + "]")
 
         #### Check for updates ####
-        self.version = 1
+        self.version = 2
         t1 = threading.Thread(target=self.versionCheck)
         t1.daemon = True
         t1.start()
 
         #### For debugging only ###
-        def someThreadFunc(self):
+        def someThreadFunc(app_obj):
             print "Hello World!"
 
         t = threading.Thread(target=someThreadFunc, args=(self,))
@@ -717,10 +739,55 @@ class App:
         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_title("FlatCAM - Alpha 3 UNSTABLE - Check for updates!")
         self.window.set_default_size(900, 600)
         self.window.show_all()
 
+    def setup_toolbar(self):
+        toolbar = self.builder.get_object("toolbar_main")
+
+        # Zoom fit
+        zf_ico = Gtk.Image.new_from_file('share/zoom_fit32.png')
+        zoom_fit = Gtk.ToolButton.new(zf_ico, "")
+        zoom_fit.connect("clicked", self.on_zoom_fit)
+        zoom_fit.set_tooltip_markup("Zoom Fit.\n(Click on plot and hit <b>1</b>)")
+        toolbar.insert(zoom_fit, -1)
+
+        # Zoom out
+        zo_ico = Gtk.Image.new_from_file('share/zoom_out32.png')
+        zoom_out = Gtk.ToolButton.new(zo_ico, "")
+        zoom_out.connect("clicked", self.on_zoom_out)
+        zoom_out.set_tooltip_markup("Zoom Out.\n(Click on plot and hit <b>2</b>)")
+        toolbar.insert(zoom_out, -1)
+
+        # Zoom in
+        zi_ico = Gtk.Image.new_from_file('share/zoom_in32.png')
+        zoom_in = Gtk.ToolButton.new(zi_ico, "")
+        zoom_in.connect("clicked", self.on_zoom_in)
+        zoom_in.set_tooltip_markup("Zoom In.\n(Click on plot and hit <b>3</b>)")
+        toolbar.insert(zoom_in, -1)
+
+        # Clear plot
+        cp_ico = Gtk.Image.new_from_file('share/clear_plot32.png')
+        clear_plot = Gtk.ToolButton.new(cp_ico, "")
+        clear_plot.connect("clicked", self.on_clear_plots)
+        clear_plot.set_tooltip_markup("Clear Plot")
+        toolbar.insert(clear_plot, -1)
+
+        # Replot
+        rp_ico = Gtk.Image.new_from_file('share/replot32.png')
+        replot = Gtk.ToolButton.new(rp_ico, "")
+        replot.connect("clicked", self.on_toolbar_replot)
+        replot.set_tooltip_markup("Re-plot all")
+        toolbar.insert(replot, -1)
+
+        # Delete item
+        del_ico = Gtk.Image.new_from_file('share/delete32.png')
+        delete = Gtk.ToolButton.new(del_ico, "")
+        delete.connect("clicked", self.on_delete)
+        delete.set_tooltip_markup("Delete selected\nobject.")
+        toolbar.insert(delete, -1)
+
     def setup_plot(self):
         """
         Sets up the main plotting area by creating a Matplotlib
@@ -1403,6 +1470,9 @@ class App:
         for widget in tooltips:
             self.builder.get_object(widget).set_tooltip_markup(tooltips[widget])
 
+    def do_nothing(self, param):
+        return
+
     ########################################
     ##         EVENT HANDLERS             ##
     ########################################
@@ -2284,6 +2354,7 @@ class App:
                 assert isinstance(app_obj, App)
                 cp = clear_poly(poly.buffer(-geo.options["paintmargin"]), tooldia, overlap)
                 geo_obj.solid_geometry = cp
+                geo_obj.options["cnctooldia"] = tooldia
 
             name = self.selected_item_name + "_paint"
             self.new_object("geometry", name, gen_paintarea)
@@ -2628,6 +2699,10 @@ class App:
             self.position_label.set_label("X: %.4f   Y: %.4f" % (
                 event.xdata, event.ydata))
             self.mouse = [event.xdata, event.ydata]
+
+            for subscriber in self.plot_mousemove_subscribers:
+                self.plot_mousemove_subscribers[subscriber](event)
+
         except:
             self.position_label.set_label("")
             self.mouse = None
@@ -2743,6 +2818,240 @@ class App:
             self.zoom(1.5, self.mouse)
             return
 
+        if event.key == 'm':
+            if self.measure.toggle_active():
+                self.info("Measuring tool ON")
+            else:
+                self.info("Measuring tool OFF")
+            return
+
+
+class Measurement:
+    def __init__(self, axes, click_subscibers, move_subscribers, update=None):
+        self.update = update
+        self.axes = axes
+        self.click_subscribers = click_subscibers
+        self.move_subscribers = move_subscribers
+        self.point1 = None
+        self.point2 = None
+        self.active = False
+        self.at = None  # AnchoredText object on plot
+
+    def toggle_active(self):
+        if self.active:
+            self.active = False
+            self.move_subscribers.pop("meas")
+            self.click_subscribers.pop("meas")
+            self.at.remove()
+            if self.update is not None:
+                self.update()
+            return False
+        else:
+            self.active = True
+            self.click_subscribers["meas"] = self.on_click
+            self.move_subscribers["meas"] = self.on_move
+            return True
+
+    def on_move(self, event):
+        try:
+            self.at.remove()
+        except:
+            pass
+        if self.point1 is None:
+            self.at = AnchoredText("Click on a reference point...")
+        else:
+            dx = event.xdata - self.point1[0]
+            dy = event.ydata - self.point1[1]
+            d = sqrt(dx**2 + dy**2)
+            self.at = AnchoredText("D = %.4f\nD(x) = %.4f\nD(y) = %.4f" % (d, dx, dy),
+                                   loc=2, prop={'size': 14}, frameon=False)
+        self.axes.add_artist(self.at)
+        if self.update is not None:
+            self.update()
+
+    def on_click(self, event):
+            if self.point1 is None:
+                self.point1 = (event.xdata, event.ydata)
+                return
+            else:
+                self.point2 = copy.copy(self.point1)
+                self.point1 = (event.xdata, event.ydata)
+
+
+class PlotCanvas:
+    """
+    Class handling the plotting area in the application.
+    """
+
+    def __init__(self, container):
+        # Options
+        self.x_margin = 15  # pixels
+        self.y_margin = 25  # Pixels
+
+        # Parent container
+        self.container = container
+
+        # Plots go onto a single matplotlib.figure
+        self.figure = Figure(dpi=50)  # TODO: dpi needed?
+        self.figure.patch.set_visible(False)
+
+        # These axes show the ticks and grid. No plotting done here.
+        # New axes must have a label, otherwise mpl returns an existing one.
+        self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
+        self.axes.set_aspect(1)
+        self.axes.grid(True)
+
+        # The canvas is the top level container (Gtk.DrawingArea)
+        self.canvas = FigureCanvas(self.figure)
+        self.canvas.set_hexpand(1)
+        self.canvas.set_vexpand(1)
+        self.canvas.set_can_focus(True)  # For key press
+
+        # Attach to parent
+        self.container.attach(self.canvas, 0, 0, 600, 400)
+
+    def mpl_connect(self, event_name, callback):
+        """
+        Attach an event handler to the canvas through the Matplotlib interface.
+
+        :param event_name: Name of the event
+        :type event_name: str
+        :param callback: Function to call
+        :type callback: func
+        :return: Nothing
+        """
+        self.canvas.mpl_connect(event_name, callback)
+
+    def connect(self, event_name, callback):
+        """
+        Attach an event handler to the canvas through the native GTK interface.
+
+        :param event_name: Name of the event
+        :type event_name: str
+        :param callback: Function to call
+        :type callback: function
+        :return: Nothing
+        """
+        self.canvas.connect(event_name, callback)
+
+    def clear(self):
+        """
+        Clears axes and figure.
+
+        :return: None
+        """
+
+        # Clear
+        self.axes.cla()
+        self.figure.clf()
+
+        # Re-build
+        self.figure.add_axes(self.axes)
+        self.axes.set_aspect(1)
+        self.axes.grid(True)
+
+        # Re-draw
+        self.canvas.queue_draw()
+
+    def adjust_axes(self, xmin, ymin, xmax, ymax):
+        """
+        Adjusts axes of all plots while maintaining the use of the whole canvas
+        and an aspect ratio to 1:1 between x and y axes. The parameters are an original
+        request that will be modified to fit these restrictions.
+
+        :param xmin: Requested minimum value for the X axis.
+        :type xmin: float
+        :param ymin: Requested minimum value for the Y axis.
+        :type ymin: float
+        :param xmax: Requested maximum value for the X axis.
+        :type xmax: float
+        :param ymax: Requested maximum value for the Y axis.
+        :type ymax: float
+        :return: None
+        """
+
+        width = xmax - xmin
+        height = ymax - ymin
+        try:
+            r = width / height
+        except:
+            print "ERROR: Height is", height
+            return
+        canvas_w, canvas_h = self.canvas.get_width_height()
+        canvas_r = float(canvas_w) / canvas_h
+        x_ratio = float(self.x_margin) / canvas_w
+        y_ratio = float(self.y_margin) / canvas_h
+
+        if r > canvas_r:
+            ycenter = (ymin + ymax) / 2.0
+            newheight = height * r / canvas_r
+            ymin = ycenter - newheight / 2.0
+            ymax = ycenter + newheight / 2.0
+        else:
+            xcenter = (xmax + ymin) / 2.0
+            newwidth = width * canvas_r / r
+            xmin = xcenter - newwidth / 2.0
+            xmax = xcenter + newwidth / 2.0
+
+        # Adjust axes
+        for ax in self.figure.get_axes():
+            ax.set_xlim((xmin, xmax))
+            ax.set_ylim((ymin, ymax))
+            ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio])
+
+        # Re-draw
+        self.canvas.queue_draw()
+
+    def auto_adjust_axes(self):
+        """
+        Calls ``adjust_axes()`` using the extents of the base axes.
+
+        :return: None
+        """
+
+        xmin, xmax = self.axes.get_xlim()
+        ymin, ymax = self.axes.get_ylim()
+        self.adjust_axes(xmin, ymin, xmax, ymax)
+
+    def zoom(self, factor, center=None):
+        """
+        Zooms the plot by factor around a given
+        center point. Takes care of re-drawing.
+
+        :param factor: Number by which to scale the plot.
+        :type factor: float
+        :param center: Coordinates [x, y] of the point around which to scale the plot.
+        :type center: list
+        :return: None
+        """
+
+        xmin, xmax = self.axes.get_xlim()
+        ymin, ymax = self.axes.get_ylim()
+        width = xmax - xmin
+        height = ymax - ymin
+
+        if center is None:
+            center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0]
+
+        # For keeping the point at the pointer location
+        relx = (xmax - center[0]) / width
+        rely = (ymax - center[1]) / height
+
+        new_width = width / factor
+        new_height = height / factor
+
+        xmin = center[0] - new_width * (1 - relx)
+        xmax = center[0] + new_width * relx
+        ymin = center[1] - new_height * (1 - rely)
+        ymax = center[1] + new_height * rely
+
+        # Adjust axes
+        for ax in self.figure.get_axes():
+            ax.set_xlim((xmin, xmax))
+            ax.set_ylim((ymin, ymax))
+
+        # Re-draw
+        self.canvas.queue_draw()
 
 app = App()
 Gtk.main()

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 884 - 179
FlatCAM.ui


+ 59 - 21
camlib.py

@@ -376,7 +376,7 @@ class Gerber (Geometry):
         :rtype : None
         """
         # Apertures
-        print "Scaling apertures..."
+        #print "Scaling apertures..."
         for apid in self.apertures:
             for param in self.apertures[apid]:
                 if param != "type":  # All others are dimensions.
@@ -384,20 +384,20 @@ class Gerber (Geometry):
                     self.apertures[apid][param] *= factor
 
         # Paths
-        print "Scaling paths..."
+        #print "Scaling paths..."
         for path in self.paths:
             path['linestring'] = affinity.scale(path['linestring'],
                                                 factor, factor, origin=(0, 0))
 
         # Flashes
-        print "Scaling flashes..."
+        #print "Scaling flashes..."
         for fl in self.flashes:
             # TODO: Shouldn't 'loc' be a numpy.array()?
             fl['loc'][0] *= factor
             fl['loc'][1] *= factor
 
         # Regions
-        print "Scaling regions..."
+        #print "Scaling regions..."
         for reg in self.regions:
             reg['polygon'] = affinity.scale(reg['polygon'], factor, factor,
                                             origin=(0, 0))
@@ -424,20 +424,20 @@ class Gerber (Geometry):
         dx, dy = vect
 
         # Paths
-        print "Shifting paths..."
+        #print "Shifting paths..."
         for path in self.paths:
             path['linestring'] = affinity.translate(path['linestring'],
                                                     xoff=dx, yoff=dy)
 
         # Flashes
-        print "Shifting flashes..."
+        #print "Shifting flashes..."
         for fl in self.flashes:
             # TODO: Shouldn't 'loc' be a numpy.array()?
             fl['loc'][0] += dx
             fl['loc'][1] += dy
 
         # Regions
-        print "Shifting regions..."
+        #print "Shifting regions..."
         for reg in self.regions:
             reg['polygon'] = affinity.translate(reg['polygon'],
                                                 xoff=dx, yoff=dy)
@@ -808,15 +808,12 @@ class Gerber (Geometry):
         :return: None
         """
 
-        # if len(self.buffered_paths) == 0:
-        #     self.buffer_paths()
-        print "... buffer_paths()"
         self.buffer_paths()
-        print "... fix_regions()"
+
         self.fix_regions()
-        print "... do_flashes()"
+
         self.do_flashes()
-        print "... cascaded_union()"
+
         self.solid_geometry = cascaded_union(self.buffered_paths +
                                              [poly['polygon'] for poly in self.regions] +
                                              self.flash_geometry)
@@ -930,8 +927,10 @@ class Excellon(Geometry):
         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*)$')
+        #self.xcoord_re = re.compile(r'^X(\d*\.?\d*)(?:Y\d*\.?\d*)?$')
+        #self.ycoord_re = re.compile(r'^(?:X\d*\.?\d*)?Y(\d*\.?\d*)$')
+        self.coordsperiod_re = re.compile(r'(?=.*X(\d*\.\d*))?(?=.*Y(\d*\.\d*))?[XY]')
+        self.coordsnoperiod_re = re.compile(r'(?!.*\.)(?=.*X(\d*))?(?=.*Y(\d*))?[XY]')
 
         # R - Repeat hole (# times, X offset, Y offset)
         self.rep_re = re.compile(r'^R(\d+)(?=.*[XY])+(?:X(\d*\.?\d*))?(?:Y(\d*\.?\d*))?$')
@@ -962,10 +961,15 @@ class Excellon(Geometry):
         :return: None
         """
 
+        # State variables
         current_tool = ""
         in_header = False
+        current_x = None
+        current_y = None
 
+        i = 0  # Line number
         for eline in elines:
+            i += 1
 
             ## Header Begin/End ##
             if self.hbegin_re.search(eline):
@@ -985,12 +989,46 @@ class Excellon(Geometry):
                     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)
+                ## Coordinates without period ##
+                match = self.coordsnoperiod_re.search(eline)
+                if match:
+                    try:
+                        x = float(match.group(1))/10000
+                        current_x = x
+                    except TypeError:
+                        x = current_x
+
+                    try:
+                        y = float(match.group(2))/10000
+                        current_y = y
+                    except TypeError:
+                        y = current_y
+
+                    if x is None or y is None:
+                        print "ERROR: Missing coordinates"
+                        continue
+
+                    self.drills.append({'point': Point((x, y)), 'tool': current_tool})
+                    continue
+
+                ## Coordinates with period ##
+                match = self.coordsperiod_re.search(eline)
+                if match:
+                    try:
+                        x = float(match.group(1))
+                        current_x = x
+                    except TypeError:
+                        x = current_x
+
+                    try:
+                        y = float(match.group(2))
+                    except TypeError:
+                        y = current_y
+
+                    if x is None or y is None:
+                        print "ERROR: Missing coordinates"
+                        continue
+
                     self.drills.append({'point': Point((x, y)), 'tool': current_tool})
                     continue
 

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác