From ad5e989331572010b09bf17d249b872a0122d99f Mon Sep 17 00:00:00 2001 From: Juan Pablo Caram Date: Mon, 3 Mar 2014 22:12:07 -0500 Subject: [PATCH] Fixed bug in Excellon parser. Did not support numbers with period. --- FlatCAM.py | 319 ++++++- FlatCAM.ui | 2536 ++++++++++++++++++++++++++++------------------------ camlib.py | 80 +- 3 files changed, 1721 insertions(+), 1214 deletions(-) diff --git a/FlatCAM.py b/FlatCAM.py index eac22909..3a462461 100644 --- a/FlatCAM.py +++ b/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 1)") + 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 2)") + 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 3)") + 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() diff --git a/FlatCAM.ui b/FlatCAM.ui index 5428f59c..1f20ca16 100644 --- a/FlatCAM.ui +++ b/FlatCAM.ui @@ -6,7 +6,7 @@ 5 dialog FlatCAM - Version Alpha 2 (2014/02) - UNSTABLE + Version Alpha 3 (2014/03) - UNSTABLE (c) 2014 Juan Pablo Caram 2D Post-processing for Manufacturing specialized in Printed Circuit Boards @@ -33,6 +33,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + share/flatcam_icon128.png False @@ -488,14 +489,37 @@ THE SOFTWARE. 5 vertical - + True False - 3 - CNC Job Object - - - + + + True + False + share/cnc32.png + + + False + True + 0 + + + + + True + False + 3 + CNC Job Object + + + + + + True + True + 1 + + False @@ -856,14 +880,37 @@ THE SOFTWARE. 5 vertical - + True False - 3 - Excellon Object - - - + + + True + False + share/drill32.png + + + False + True + 0 + + + + + True + False + 3 + Excellon Object + + + + + + True + True + 1 + + False @@ -1360,14 +1407,37 @@ THE SOFTWARE. 5 vertical - + True False - 3 - Geometry Object - - - + + + True + False + share/geometry32.png + + + False + True + 0 + + + + + True + False + 3 + Geometry Object + + + + + + True + True + 1 + + False @@ -1970,14 +2040,37 @@ THE SOFTWARE. 5 vertical - + True False - 3 - Gerber Object - - - + + + True + False + share/flatcam_icon32.png + + + False + True + 0 + + + + + True + False + 3 + Gerber Object + + + + + + True + True + 1 + + False @@ -2786,6 +2879,7 @@ this object. True False + True @@ -3103,102 +3197,6 @@ to application defaults. True False icons - - - True - False - Zoom Fit. -(Click on plot and hit <b>1</b>) - Fit - True - gtk-zoom-100 - - - - False - True - - - - - True - False - Zoom in. -(Click on plot and hit <b>3</b> -to zoom around a point) - Zoom+ - True - gtk-zoom-in - - - - False - True - - - - - True - False - Zoom Out. -(Click on plot and hit <b>2</b> -to zoom around a point) - Zoom- - True - gtk-zoom-out - - - - False - True - - - - - True - False - Clear Plot - Clear Plots - True - gtk-stop - - - - False - True - - - - - True - False - Re-plot all - Re-plot - True - gtk-redo - - - - False - True - - - - - True - False - Delete selected -object. - Delete Object - True - gtk-delete - - - - False - True - - False @@ -3300,10 +3298,27 @@ for the current object. True vertical - + True False - Application defaults get transfered + + + True + False + share/gear32.png + + + False + True + 0 + + + + + True + False + True + Application defaults get transfered to every new project. Project options get inherited by new project objects. @@ -3312,17 +3327,33 @@ by choosing <i>File + Save defaults</i>. Project obtions are saved with the project. - 10 - 10 - 5 - 10 - 0 - 1 - - PROJECT OPTIONS - APPLICATION DEFAULTS - - + Application defaults get transfered +to every new project. Project options +get inherited by new project objects. + +Save application defaults +by choosing File + Save defaults. + +Project obtions are saved with the +project. + 10 + 10 + 5 + 10 + 0 + 1 + + PROJECT OPTIONS + APPLICATION DEFAULTS + + + + + False + True + 1 + + False @@ -3407,14 +3438,505 @@ project. - + True False - 3 - Gerber Object - - - + 8 + 0 + + + True + False + 6 + 6 + 12 + 6 + + + True + False + vertical + + + True + False + 5 + 0 + 3 + Plot Options: + + + + + + False + True + 0 + + + + + Plot + True + True + False + 0 + True + True + + + + False + True + 1 + + + + + Merge Polygons + True + True + False + 0 + True + True + + + + False + True + 2 + + + + + Solid + True + True + False + 0 + True + + + + False + True + 3 + + + + + Multi-colored + True + True + False + 0 + True + + + + False + True + 4 + + + + + True + False + 5 + 0 + 3 + Isolation Routing: + + + + + + False + True + 5 + + + + + True + False + 3 + 2 + + + True + False + True + Tool paths for isolation routing are drawn +at 1/2 of the tool diameter away from +polygons defined in Gerber. + Tool paths for isolation routing are drawn +at 1/2 of the tool diameter away from +polygons defined in Gerber. + 1 + 3 + Tool diam: + + + 0 + 0 + 1 + 1 + + + + + True + True + 2 + 2 + + 16 + True + + + + 1 + 0 + 1 + 1 + + + + + False + True + 6 + + + + + True + False + 5 + 0 + 3 + Board cutout: + + + + + + False + True + 7 + + + + + True + False + 3 + + + True + False + 1 + Margin: + + + 0 + 0 + 1 + 1 + + + + + True + True + + 12 + True + + + + 1 + 0 + 1 + 1 + + + + + True + True + + 12 + True + + + + 1 + 1 + 1 + 1 + + + + + True + False + 1 + Gap size: + + + 0 + 1 + 1 + 1 + + + + + True + False + 1 + Gaps: + + + 0 + 2 + 1 + 1 + + + + + True + False + + + 2 (T/B) + True + True + False + 8 + 0 + True + True + + + + False + True + 0 + + + + + 2 (L/R) + True + True + False + 8 + 0 + True + rb_app_2tb + + + + False + True + 1 + + + + + 4 + True + True + False + 0 + True + rb_app_2tb + + + False + True + 2 + + + + + 1 + 2 + 1 + 1 + + + + + False + True + 8 + + + + + True + False + 5 + 0 + 3 + Non-copper regions: + + + + + + False + True + 9 + + + + + True + False + 4 + 4 + 1 + + + True + False + 1 + Boundary margin: + + + False + True + 0 + + + + + True + True + + 14 + True + + + + False + True + 1 + + + + + False + True + 10 + + + + + True + False + 5 + 0 + 3 + Bounding box: + + + + + + False + True + 11 + + + + + True + False + 4 + 4 + 1 + + + True + False + 1 + Boundary margin: + + + False + True + 0 + + + + + True + True + + 14 + True + + + + False + True + 1 + + + + + False + True + 12 + + + + + Rounded corners + True + True + False + 0 + True + + + + False + True + 13 + + + + + + + + + + + + + + + + + + True + False + 5 + <b>Gerber Objects</b> + True + + False @@ -3423,50 +3945,233 @@ project. - + + + + + + + True False - 5 - 0 - 3 - Plot Options: - - - - - - False - True - 5 - - - - - Plot - True - True - False - 0 - True - True - - - - False - True - 6 - - - - - Merge Polygons - True - True - False - 0 - True - True - + 8 + 0 + + + True + False + 6 + 6 + 12 + 6 + + + True + False + vertical + + + True + False + 0 + 3 + Plot Options: + + + + + + False + True + 0 + + + + + Plot + True + True + False + 0 + True + True + + + + False + True + 1 + + + + + Solid + True + True + False + 0 + True + + + + False + True + 2 + + + + + Multi-colored + True + True + False + 0 + True + + + + False + True + 3 + + + + + True + False + 5 + 0 + 3 + Create CNC Job: + + + + + + False + True + 4 + + + + + True + False + 2 + 4 + + + True + True + + True + + + + 1 + 0 + 1 + 1 + + + + + True + True + + True + + + + 1 + 1 + 1 + 1 + + + + + True + True + + True + + + + 1 + 2 + 1 + 1 + + + + + True + False + 1 + Drill Z: + + + 0 + 0 + 1 + 1 + + + + + True + False + 1 + Travel Z: + + + 0 + 1 + 1 + 1 + + + + + True + False + 1 + Feed rate: + + + 0 + 2 + 1 + 1 + + + + + False + True + 5 + + + + + + + + + + + + + + + True + False + 4 + <b>Excellon Objects</b> + True + + False @@ -3475,14 +4180,374 @@ project. - - Solid + True - True - False - 0 - True - + False + 8 + 0 + + + True + False + 6 + 6 + 12 + 6 + + + True + False + vertical + + + True + False + 0 + 3 + Plot Options: + + + + + + False + True + 0 + + + + + Plot + True + True + False + 0 + True + True + + + + False + True + 1 + + + + + Solid + True + True + False + 0 + True + + + + False + True + 2 + + + + + Multi-colored + True + True + False + 0 + True + + + + False + True + 3 + + + + + True + False + 5 + 0 + 3 + Create CNC Job: + + + + + + False + True + 4 + + + + + True + False + 2 + 4 + + + True + True + + True + + + + 1 + 0 + 1 + 1 + + + + + True + True + + True + + + + 1 + 1 + 1 + 1 + + + + + True + True + + True + + + + 1 + 2 + 1 + 1 + + + + + True + False + 1 + Cut Z: + + + 0 + 0 + 1 + 1 + + + + + True + False + 1 + Travel Z: + + + 0 + 1 + 1 + 1 + + + + + True + False + 1 + Feed rate: + + + 0 + 2 + 1 + 1 + + + + + True + False + 1 + Tool diam: + + + 0 + 3 + 1 + 1 + + + + + True + True + + True + + + + 1 + 3 + 1 + 1 + + + + + False + True + 5 + + + + + True + False + 5 + 0 + 3 + Paint Area: + + + + + + False + True + 6 + + + + + True + False + 2 + 5 + + + True + False + 1 + Tool diam: + + + 0 + 0 + 1 + 1 + + + + + True + True + + True + + + + 1 + 0 + 1 + 1 + + + + + True + False + 1 + Overlap: + + + 0 + 1 + 1 + 1 + + + + + True + True + + True + + + + 1 + 1 + 1 + 1 + + + + + True + False + 1 + Margin: + + + 0 + 2 + 1 + 1 + + + + + True + True + + True + + + + 1 + 2 + 1 + 1 + + + + + False + True + 7 + + + + + + + + + + + + + + + True + False + 4 + <b>Geometry Objects</b> + True + + False @@ -3491,14 +4556,101 @@ project. - - Multi-colored + True - True - False - 0 - True - + False + 8 + 0 + + + True + False + 12 + 6 + + + True + False + vertical + + + Plot + True + True + False + 0 + True + True + + + + False + True + 0 + + + + + True + False + 3 + 3 + 3 + + + True + False + 1 + Tool diam: + + + False + True + 0 + + + + + True + True + + True + + + + False + True + 1 + + + + + False + True + 1 + + + + + + + + + + + + + + + + + + True + False + <b>CNC Job Objects</b> + True + + False @@ -3506,1004 +4658,12 @@ project. 9 - - - True - False - 5 - 0 - 3 - Isolation Routing: - - - - - - False - True - 10 - - - - - True - False - 3 - 2 - - - True - False - Tool paths for isolation routing are drawn -at 1/2 of the tool diameter away from -polygons defined in Gerber. - 1 - 3 - Tool diam: - - - 0 - 0 - 1 - 1 - - - - - True - True - 2 - 2 - - 16 - True - - - - 1 - 0 - 1 - 1 - - - - - False - True - 11 - - - - - True - False - 5 - 0 - 3 - Board cutout: - - - - - - False - True - 12 - - - - - True - False - 3 - - - True - False - 1 - Margin: - - - 0 - 0 - 1 - 1 - - - - - True - True - - 12 - True - - - - 1 - 0 - 1 - 1 - - - - - True - True - - 12 - True - - - - 1 - 1 - 1 - 1 - - - - - True - False - 1 - Gap size: - - - 0 - 1 - 1 - 1 - - - - - True - False - 1 - Gaps: - - - 0 - 2 - 1 - 1 - - - - - True - False - - - 2 (T/B) - True - True - False - 8 - 0 - True - - - - False - True - 0 - - - - - 2 (L/R) - True - True - False - 8 - 0 - True - True - rb_app_2tb - - - - False - True - 1 - - - - - 4 - True - True - False - 0 - True - rb_app_2tb - - - False - True - 2 - - - - - 1 - 2 - 1 - 1 - - - - - False - True - 13 - - - - - True - False - 5 - 0 - 3 - Non-copper regions: - - - - - - False - True - 14 - - - - - True - False - 4 - 4 - 1 - - - True - False - 1 - Boundary margin: - - - False - True - 0 - - - - - True - True - - 14 - True - - - - False - True - 1 - - - - - False - True - 15 - - - - - True - False - 5 - 0 - 3 - Bounding box: - - - - - - False - True - 16 - - - - - True - False - 4 - 4 - 1 - - - True - False - 1 - Boundary margin: - - - False - True - 0 - - - - - True - True - - 14 - True - - - - False - True - 1 - - - - - False - True - 17 - - - - - Rounded corners - True - True - False - 0 - True - - - - False - True - 18 - - - - - True - False - 3 - Excellon Object - - - - - - False - True - 19 - - - - - True - False - 0 - 3 - Plot Options: - - - - - - False - True - 20 - - - - - Plot - True - True - False - 0 - True - True - - - - False - True - 21 - - - - - Solid - True - True - False - 0 - True - - - - False - True - 22 - - - - - Multi-colored - True - True - False - 0 - True - - - - False - True - 23 - - - - - True - False - 5 - 0 - 3 - Create CNC Job: - - - - - - False - True - 24 - - - - - True - False - 2 - 4 - - - True - True - - True - - - - 1 - 0 - 1 - 1 - - - - - True - True - - True - - - - 1 - 1 - 1 - 1 - - - - - True - True - - True - - - - 1 - 2 - 1 - 1 - - - - - True - False - 1 - Drill Z: - - - 0 - 0 - 1 - 1 - - - - - True - False - 1 - Travel Z: - - - 0 - 1 - 1 - 1 - - - - - True - False - 1 - Feed rate: - - - 0 - 2 - 1 - 1 - - - - - False - True - 25 - - - - - True - False - 3 - Geometry Object - - - - - - False - True - 26 - - - - - True - False - 0 - 3 - Plot Options: - - - - - - False - True - 27 - - - - - Plot - True - True - False - 0 - True - True - - - - False - True - 28 - - - - - Solid - True - True - False - 0 - True - - - - False - True - 29 - - - - - Multi-colored - True - True - False - 0 - True - - - - False - True - 30 - - - - - True - False - 5 - 0 - 3 - Create CNC Job: - - - - - - False - True - 31 - - - - - True - False - 2 - 4 - - - True - True - - True - - - - 1 - 0 - 1 - 1 - - - - - True - True - - True - - - - 1 - 1 - 1 - 1 - - - - - True - True - - True - - - - 1 - 2 - 1 - 1 - - - - - True - False - 1 - Cut Z: - - - 0 - 0 - 1 - 1 - - - - - True - False - 1 - Travel Z: - - - 0 - 1 - 1 - 1 - - - - - True - False - 1 - Feed rate: - - - 0 - 2 - 1 - 1 - - - - - True - False - 1 - Tool diam: - - - 0 - 3 - 1 - 1 - - - - - True - True - - True - - - - 1 - 3 - 1 - 1 - - - - - False - True - 32 - - - - - True - False - 5 - 0 - 3 - Paint Area: - - - - - - False - True - 33 - - - - - True - False - 2 - 5 - - - True - False - 1 - Tool diam: - - - 0 - 0 - 1 - 1 - - - - - True - True - - True - - - - 1 - 0 - 1 - 1 - - - - - True - False - 1 - Overlap: - - - 0 - 1 - 1 - 1 - - - - - True - True - - True - - - - 1 - 1 - 1 - 1 - - - - - True - False - 1 - Margin: - - - 0 - 2 - 1 - 1 - - - - - True - True - - True - - - - 1 - 2 - 1 - 1 - - - - - False - True - 34 - - - - - True - False - 3 - CNC Job Object - - - - - - False - True - 35 - - - - - Plot - True - True - False - 0 - True - True - - - - False - True - 36 - - - - - True - False - 3 - 3 - 3 - - - True - False - 1 - Tool diam: - - - False - True - 0 - - - - - True - True - - True - - - - False - True - 1 - - - - - False - True - 39 - - diff --git a/camlib.py b/camlib.py index f0335626..d0f4296c 100644 --- a/camlib.py +++ b/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