From 015e84314824dfe6ef2932f31f7b1ae3209586c1 Mon Sep 17 00:00:00 2001 From: Juan Pablo Caram Date: Tue, 14 Jan 2014 00:13:09 -0500 Subject: [PATCH] Major redesign of plotting and class structures. --- camlib.py | 10 +- cirkuix.py | 501 +++++++++++++++++++++++++++++++++++++---------------- cirkuix.ui | 26 +-- 3 files changed, 365 insertions(+), 172 deletions(-) diff --git a/camlib.py b/camlib.py index e89b3299..92adeed1 100644 --- a/camlib.py +++ b/camlib.py @@ -425,7 +425,7 @@ class CNCjob(Geometry): self.gcode += self.feedminutecode + "\n" self.gcode += "F%.2f\n"%self.feedrate self.gcode += "G00 Z%.4f\n"%self.z_move # Move to travel height - self.gcode += "M03\n" # Spindle start + self.gcode += "M03\n" # Spindle start self.gcode += self.pausecode + "\n" for geo in geometry: @@ -497,7 +497,7 @@ class CNCjob(Geometry): if 'X' in gobj or 'Y' in gobj: x = 0 y = 0 - kind = ["C","F"] # T=travel, C=cut, F=fast, S=slow + kind = ["C", "F"] # T=travel, C=cut, F=fast, S=slow if 'X' in gobj: x = gobj['X'] @@ -524,8 +524,8 @@ class CNCjob(Geometry): start = arctan2( -gobj['J'], -gobj['I']) stop = arctan2(-center[1]+y, -center[0]+x) geometry.append({'geom': arc(center, radius, start, stop, - arcdir[current['G']], - steps_per_circ), + arcdir[current['G']], + steps_per_circ), 'kind': kind}) # Update current instruction @@ -682,7 +682,7 @@ def arc(center, radius, start, stop, direction, steps_per_circ): ############### cam.py #################### -def coord(gstr,digits,fraction): +def coord(gstr, digits, fraction): """ Parse Gerber coordinates """ diff --git a/cirkuix.py b/cirkuix.py index 0cc4d262..355d7e90 100644 --- a/cirkuix.py +++ b/cirkuix.py @@ -1,6 +1,8 @@ import threading from gi.repository import Gtk +#from gi.repository import Gdk +from gi.repository import GLib from matplotlib.figure import Figure from numpy import arange, sin, pi @@ -10,33 +12,90 @@ from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCan from camlib import * - class CirkuixObj: def __init__(self, name, kind): self.name = name - self.kind = kind + self.kind = kind # TODO: Probably not needed + self.axes = None # Matplotlib axes + self.options = {} + + def setup_axes(self, figure): + if self.axes is None: + self.axes = figure.add_axes([0.05, 0.05, 0.9, 0.9], label=self.name) + elif self.axes not in figure.axes: + figure.add_axes(self.axes) + + self.axes.patch.set_visible(False) # No background + self.axes.set_aspect(1) + + return self.axes + + def set_options(self, options): + for name in options: + self.options[name] = options[name] + return class CirkuixGerber(CirkuixObj, Gerber): def __init__(self, name): Gerber.__init__(self) CirkuixObj.__init__(self, name, "gerber") - self.fields = [{"name": "plot", - "type": bool, - "value": True, - "get": None, - "set": None, - "onchange": None}, - {}] + + self.options = { + "plot": True, + "mergepolys": True, + "multicolored": False, + "solid": False, + "isotooldia": 0.4/25.4, + "cutoutmargin": 0.2, + "cutoutgapsize": 0.15, + "gaps": "tb" + } + + def plot(self, figure): + self.setup_axes(figure) + + self.create_geometry() + + geometry = None + if self.options["mergepolys"]: + geometry = self.solid_geometry + else: + geometry = self.buffered_paths + \ + [poly['polygon'] for poly in self.regions] + \ + self.flash_geometry + + linespec = None + if self.options["multicolored"]: + linespec = '-' + else: + linespec = 'k-' + + for poly in geometry: + x, y = poly.exterior.xy + self.axes.plot(x, y, linespec) + for ints in poly.interiors: + x, y = ints.coords.xy + self.axes.plot(x, y, linespec) class CirkuixExcellon(CirkuixObj, Excellon): def __init__(self, name): Excellon.__init__(self) CirkuixObj.__init__(self, name, "excellon") - self.options = {"plot": True, - "solid": False, - "multicolored": False} + + def plot(self, figure): + self.setup_axes(figure) + + self.create_geometry() + + # Plot excellon + for geo in self.solid_geometry: + x, y = geo.exterior.coords.xy + self.axes.plot(x, y, 'r-') + for ints in geo.interiors: + x, y = ints.coords.xy + self.axes.plot(x, y, 'g-') class CirkuixCNCjob(CirkuixObj, CNCjob): @@ -45,7 +104,11 @@ class CirkuixCNCjob(CirkuixObj, CNCjob): CNCjob.__init__(self, units=units, kind=kind, z_move=z_move, feedrate=feedrate, z_cut=z_cut, tooldia=tooldia) CirkuixObj.__init__(self, name, "cncjob") - self.options = {"plot": True} + + def plot(self, figure): + self.setup_axes(figure) + + self.plot2(self.axes) class CirkuixGeometry(CirkuixObj, Geometry): @@ -55,48 +118,30 @@ class CirkuixGeometry(CirkuixObj, Geometry): "solid": False, "multicolored": False} + def plot(self, figure): + self.setup_axes(figure) -class CirkuixObjForm: - def __init__(self, container, cobj): - self.cobj = cobj - self.container = container - self.fields = {} - - def populate(self): - return - - def save(self): - return + for geo in self.solid_geometry: -#class CirkuixGerberForm(CirkuixObjForm) - + if type(geo) == Polygon: + x, y = geo.exterior.coords.xy + self.axes.plot(x, y, 'r-') + for ints in geo.interiors: + x, y = ints.coords.xy + self.axes.plot(x, y, 'r-') + continue -def get_entry_text(entry): - return entry.get_text() - - -def get_entry_int(entry): - return int(entry.get_text()) - - -def get_entry_float(entry): - return float(entry.get_text()) - - -def get_entry_eval(entry): - return eval(entry.get_text) - -getters = {"entry_text": get_entry_text, - "entry_int": get_entry_int, - "entry_float": get_entry_float, - "entry_eval": get_entry_eval} - -setters = {"entry"} + if type(geo) == LineString or type(geo) == LinearRing: + x, y = geo.coords.xy + self.axes.plot(x, y, 'r-') + continue class App: def __init__(self): - + # Needed to interact with the GUI from other threads. + GLib.threads_init() + ######################################## ## GUI ## ######################################## @@ -134,7 +179,13 @@ class App: # What is selected by the user. It is # a key if self.stuff self.selected_item_name = None - + + # For debugging only + def someThreadFunc(self): + print "Hello World!" + t = threading.Thread(target=someThreadFunc, args=(self,)) + t.start() + ######################################## ## START ## ######################################## @@ -143,7 +194,7 @@ class App: def plot_setup(self): self.figure = Figure(dpi=50) - self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9]) + self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0) self.axes.set_aspect(1) #t = arange(0.0,5.0,0.01) #s = sin(2*pi*t) @@ -162,7 +213,7 @@ class App: self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move_over_plot) self.canvas.set_can_focus(True) # For key press self.canvas.mpl_connect('key_press_event', self.on_key_over_plot) - self.canvas.mpl_connect('scroll_event', self.on_scroll_over_plot) + #self.canvas.mpl_connect('scroll_event', self.on_scroll_over_plot) self.grid.attach(self.canvas, 0, 0, 600, 400) @@ -192,77 +243,87 @@ class App: new_width = width/factor new_height = height/factor - self.axes.set_xlim((center[0]-new_width*(1-relx), center[0]+new_width*relx)) - self.axes.set_ylim((center[1]-new_height*(1-rely), center[1]+new_height*rely)) - + #self.axes.set_xlim((center[0]-new_width*(1-relx), center[0]+new_width*relx)) + #self.axes.set_ylim((center[1]-new_height*(1-rely), center[1]+new_height*rely)) + 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 + + for name in self.stuff: + self.stuff[name].axes.set_xlim((xmin, xmax)) + self.stuff[name].axes.set_ylim((ymin, ymax)) + self.axes.set_xlim((xmin, xmax)) + self.axes.set_ylim((ymin, ymax)) + self.canvas.queue_draw() - def plot_gerber(self, gerber): - gerber.create_geometry() - - # Options - mergepolys = self.builder.get_object("cb_mergepolys").get_active() - multicolored = self.builder.get_object("cb_multicolored").get_active() - - geometry = None - if mergepolys: - geometry = gerber.solid_geometry - else: - geometry = gerber.buffered_paths + \ - [poly['polygon'] for poly in gerber.regions] + \ - gerber.flash_geometry - - linespec = None - if multicolored: - linespec = '-' - else: - linespec = 'k-' - - for poly in geometry: - x, y = poly.exterior.xy - #a.plot(x, y) - self.axes.plot(x, y, linespec) - for ints in poly.interiors: - x, y = ints.coords.xy - self.axes.plot(x, y, linespec) - - self.canvas.queue_draw() - - def plot_excellon(self, excellon): - excellon.create_geometry() - - # Plot excellon - for geo in excellon.solid_geometry: - x, y = geo.exterior.coords.xy - self.axes.plot(x, y, 'r-') - for ints in geo.interiors: - x, y = ints.coords.xy - self.axes.plot(x, y, 'g-') - - self.canvas.queue_draw() - - def plot_cncjob(self, job): - #job.gcode_parse() - job.plot2(self.axes) - self.canvas.queue_draw() - - def plot_geometry(self, geometry): - for geo in geometry.solid_geometry: - - if type(geo) == Polygon: - x, y = geo.exterior.coords.xy - self.axes.plot(x, y, 'r-') - for ints in geo.interiors: - x, y = ints.coords.xy - self.axes.plot(x, y, 'r-') - continue - - if type(geo) == LineString or type(geo) == LinearRing: - x, y = geo.coords.xy - self.axes.plot(x, y, 'r-') - continue - - self.canvas.queue_draw() + # def plot_gerber(self, gerber): + # gerber.create_geometry() + # + # # Options + # mergepolys = self.builder.get_object("cb_mergepolys").get_active() + # multicolored = self.builder.get_object("cb_multicolored").get_active() + # + # geometry = None + # if mergepolys: + # geometry = gerber.solid_geometry + # else: + # geometry = gerber.buffered_paths + \ + # [poly['polygon'] for poly in gerber.regions] + \ + # gerber.flash_geometry + # + # linespec = None + # if multicolored: + # linespec = '-' + # else: + # linespec = 'k-' + # + # for poly in geometry: + # x, y = poly.exterior.xy + # #a.plot(x, y) + # self.axes.plot(x, y, linespec) + # for ints in poly.interiors: + # x, y = ints.coords.xy + # self.axes.plot(x, y, linespec) + # + # self.canvas.queue_draw() + # + # def plot_excellon(self, excellon): + # excellon.create_geometry() + # + # # Plot excellon + # for geo in excellon.solid_geometry: + # x, y = geo.exterior.coords.xy + # self.axes.plot(x, y, 'r-') + # for ints in geo.interiors: + # x, y = ints.coords.xy + # self.axes.plot(x, y, 'g-') + # + # self.canvas.queue_draw() + # + # def plot_cncjob(self, job): + # #job.gcode_parse() + # job.plot2(self.axes) + # self.canvas.queue_draw() + # + # def plot_geometry(self, geometry): + # for geo in geometry.solid_geometry: + # + # if type(geo) == Polygon: + # x, y = geo.exterior.coords.xy + # self.axes.plot(x, y, 'r-') + # for ints in geo.interiors: + # x, y = ints.coords.xy + # self.axes.plot(x, y, 'r-') + # continue + # + # if type(geo) == LineString or type(geo) == LinearRing: + # x, y = geo.coords.xy + # self.axes.plot(x, y, 'r-') + # continue + # + # self.canvas.queue_draw() def setup_component_viewer(self): """ @@ -271,8 +332,9 @@ class App: """ self.store = Gtk.ListStore(str) self.tree = Gtk.TreeView(self.store) - select = self.tree.get_selection() - self.signal_id = select.connect("changed", self.on_tree_selection_changed) + #self.list = Gtk.ListBox() + self.tree_select = self.tree.get_selection() + self.signal_id = self.tree_select.connect("changed", self.on_tree_selection_changed) renderer = Gtk.CellRendererText() column = Gtk.TreeViewColumn("Title", renderer, text=0) self.tree.append_column(column) @@ -299,6 +361,8 @@ class App: entry_name = self.builder.get_object("entry_gerbername") entry_name.set_text(self.selected_item_name) entry_name.connect("activate", self.on_activate_name) + if self.selected_item_name is not None: + self.selected_object_to_form() box1.show() def build_excellon_ui(self): @@ -310,6 +374,8 @@ class App: entry_name = self.builder.get_object("entry_excellonname") entry_name.set_text(self.selected_item_name) entry_name.connect("activate", self.on_activate_name) + if self.selected_item_name is not None: + self.selected_object_to_form() box1.show() def build_cncjob_ui(self): @@ -321,6 +387,8 @@ class App: entry_name = self.builder.get_object("entry_cncjobname") entry_name.set_text(self.selected_item_name) entry_name.connect("activate", self.on_activate_name) + if self.selected_item_name is not None: + self.selected_object_to_form() box1.show() def build_geometry_ui(self): @@ -332,18 +400,113 @@ class App: entry_name = self.builder.get_object("entry_geometryname") entry_name.set_text(self.selected_item_name) entry_name.connect("activate", self.on_activate_name) + if self.selected_item_name is not None: + self.selected_object_to_form() box1.show() - + + def get_radio_value(self, radio_set): + """ + Returns the radio_set[key] if the radiobutton + whose name is key is active. + """ + for name in radio_set: + if self.builder.get_object(name).get_active(): + return radio_set[name] + + def selected_object_to_form(self): + print "Object --> Form" + obj = self.stuff[self.selected_item_name] + + if obj.__class__.__name__ == 'CirkuixGerber': + setters = { + "plot": self.builder.get_object("cb_gerber_plot").set_active, + "mergepolys": self.builder.get_object("cb_gerber_mergepolys").set_active, + "solid": self.builder.get_object("cb_gerber_solid").set_active, + "multicolored": self.builder.get_object("cb_gerber_multicolored").set_active, + "isotooldia": lambda x: self.builder.get_object("entry_gerberisotooldia").set_text(str(x)), + "cutoutmargin": lambda x: self.builder.get_object("entry_gerber_cutout_margin").set_text(str(x)), + "cutoutgapsize": lambda x: self.builder.get_object("entry_gerber_cutout_gapsize").set_text(str(x)), + "gaps": lambda x: self.builder.get_object("cb_gerber_solid").set_active( + {"tb": "rb_2tb", "lr": "rb_2lr", "4": "rb_4"}[x]) + } + for option in obj.options: + if option in setters: + setters[option](obj.options[option]) + return + + if obj.__class__.__name__ == 'CirkuixExcellon': + setters = { + "plot": self.builder.get_object("cb_excellon_plot").set_active, + "solid": self.builder.get_object("cb_excellon_solid").set_active, + "multicolored": self.builder.get_object("cb_excellon_multicolored").set_active + } + for option in obj.options: + if option in setters: + setters[option](obj.options[option]) + return + + if obj.__class__.__name__ == 'CirkuixCNCjob': + obj.set_options({ + "plot": self.builder.get_object("cb_cncjob_plot").set_active, + "solid": self.builder.get_object("cb_cncjob_solid").set_active, + "tooldia": lambda x: self.builder.get_object("entry_cncjob_tooldia").set_text(str(x)) + }) + + def form_to_selected_object(self): + obj = self.stuff[self.selected_item_name] + + if obj.__class__.__name__ == 'CirkuixGerber': + + obj.set_options({ + "plot": self.builder.get_object("cb_gerber_plot").get_active(), + "mergepolys": self.builder.get_object("cb_gerber_mergepolys").get_active(), + "solid": self.builder.get_object("cb_gerber_solid").get_active(), + "multicolored": self.builder.get_object("cb_gerber_multicolored").get_active(), + "isotooldia": self.get_eval("entry_gerberisotooldia"), + "cutoutmargin": self.get_eval("entry_gerber_cutout_margin"), + "cutoutgapsize": self.get_eval("entry_gerber_cutout_gapsize"), + "gaps": lambda x: self.get_radio_value({"rb_2tb": "tb", "rb_2lr": "lr", "rb_4": "4"}) + }) + return + + if obj.__class__.__name__ == 'CirkuixExcellon': + obj.set_options({ + "plot": self.builder.get_object("cb_excellon_plot").get_active(), + "solid": self.builder.get_object("cb_excellon_solid").get_active(), + "multicolored": self.builder.get_object("cb_excellon_multicolored").get_active() + }) + + return + + if obj.__class__.__name__ == 'CirkuixCNCjob': + obj.set_options({ + "plot": self.builder.get_object("cb_cncjob_plot").get_active(), + "solid": self.builder.get_object("cb_cncjob_solid").get_active(), + "multicolored": self.builder.get_object("cb_cncjob_multicolored").get_active(), + "tooldia": self.get_eval("entry_cncjob_tooldia") + }) + + if type(obj) is CirkuixGeometry: + obj.set_options({ + "plot": self.builder.get_object("cb_geometry_plot").get_active(), + "solid": self.builder.get_object("cb_geometry_solid").get_active(), + "multicolored": self.builder.get_object("cb_geometry_multicolored").get_active(), + "cutz": self.get_eval("entry_geometry_cutz"), + "travelz": self.get_eval("entry_geometry_travelz"), + "feedrate": self.get_eval("entry_geometry_feedrate") + }) + def plot_all(self): self.clear_plots() - plotter = {"gerber":self.plot_gerber, - "excellon":self.plot_excellon, - "cncjob":self.plot_cncjob, - "geometry":self.plot_geometry} + #plotter = {"gerber": self.plot_gerber, + # "excellon": self.plot_excellon, + # "cncjob": self.plot_cncjob, + # "geometry": self.plot_geometry} for i in self.stuff: - kind = self.stuff[i].kind - plotter[kind](self.stuff[i]) + #kind = self.stuff[i].kind + #plotter[kind](self.stuff[i]) + self.stuff[i].plot(self.figure) self.on_zoom_fit(None) self.axes.grid() @@ -351,12 +514,21 @@ class App: def clear_plots(self): self.axes.cla() + self.figure.clf() + self.figure.add_axes(self.axes) self.canvas.queue_draw() def get_eval(self, widget_name): value = self.builder.get_object(widget_name).get_text() return eval(value) - + + def set_list_selection(self, name): + iter = self.store.get_iter_first() + while iter is not None and self.store[iter][0] != name: + iter = self.store.iter_next(iter) + self.tree_select.unselect_all() + self.tree_select.select_iter(iter) + ######################################## ## EVENT HANDLERS ## ######################################## @@ -500,12 +672,11 @@ class App: def on_tree_selection_changed(self, selection): model, treeiter = selection.get_selected() - - - if treeiter != None: + + if treeiter is not None: print "You selected", model[treeiter][0] else: - return # TODO: Clear "Selected" page + return # TODO: Clear "Selected" page self.selected_item_name = model[treeiter][0] # Remove the current selection page @@ -555,10 +726,10 @@ class App: dialog.destroy() def file_chooser_save_action(self, on_success): - ''' + """ Opens the file chooser and runs on_success upon completion of valid file choice. - ''' + """ dialog = Gtk.FileChooserDialog("Save file", self.window, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, @@ -591,15 +762,21 @@ class App: self.progress_bar.set_text("Plotting ...") self.progress_bar.set_fraction(0.6) - self.plot_gerber(gerber) + #self.plot_gerber(gerber) + gerber.plot(self.figure) + gerber.axes.set_alpha(0.0) self.on_zoom_fit(None) self.progress_bar.set_text("Done!") self.progress_bar.set_fraction(1.0) + self.notebook.set_current_page(1) + self.set_list_selection(name) + def clear_bar(bar): bar.set_text("") bar.set_fraction(0.0) + threading.Timer(1, clear_bar, args=(self.progress_bar,)).start() self.file_chooser_action(on_success) @@ -621,7 +798,8 @@ class App: self.progress_bar.set_text("Plotting ...") self.progress_bar.set_fraction(0.6) - self.plot_excellon(excellon) + #self.plot_excellon(excellon) + excellon.plot(self.figure) self.on_zoom_fit(None) self.progress_bar.set_text("Done!") @@ -658,7 +836,8 @@ class App: self.progress_bar.set_text("Plotting ...") self.progress_bar.set_fraction(0.6) - self.plot_cncjob(job) + #self.plot_cncjob(job) + job.plot(self.figure) self.on_zoom_fit(None) self.progress_bar.set_text("Done!") @@ -708,29 +887,43 @@ class App: #self.axes.set_ylim((ymin-0.05*height, ymax+0.05*height)) if r > Fr: - self.axes.set_xlim((xmin-0.05*width, xmax+0.05*width)) + #self.axes.set_xlim((xmin-0.05*width, xmax+0.05*width)) + xmin -= 0.05*width + xmax += 0.05*width ycenter = (ymin+ymax)/2.0 newheight = height*r/Fr - self.axes.set_ylim((ycenter-newheight/2.0, ycenter+newheight/2.0)) + ymin = ycenter-newheight/2.0 + ymax = ycenter+newheight/2.0 + #self.axes.set_ylim((ycenter-newheight/2.0, ycenter+newheight/2.0)) else: - self.axes.set_ylim((ymin-0.05*height, ymax+0.05*height)) + #self.axes.set_ylim((ymin-0.05*height, ymax+0.05*height)) + ymin -= 0.05*height + ymax += 0.05*height xcenter = (xmax+ymin)/2.0 newwidth = width*Fr/r - self.axes.set_xlim((xcenter-newwidth/2.0, xcenter+newwidth/2.0)) - + xmin = xcenter-newwidth/2.0 + xmax = xcenter+newwidth/2.0 + #self.axes.set_xlim((xcenter-newwidth/2.0, xcenter+newwidth/2.0)) + + for name in self.stuff: + self.stuff[name].axes.set_xlim((xmin, xmax)) + self.stuff[name].axes.set_ylim((ymin, ymax)) + self.axes.set_xlim((xmin, xmax)) + self.axes.set_ylim((ymin, ymax)) + self.canvas.queue_draw() return - def on_scroll_over_plot(self, event): - print "Scroll" - center = [event.xdata, event.ydata] - if sign(event.step): - self.zoom(1.5, center=center) - else: - self.zoom(1/1.5, center=center) - - def on_window_scroll(self, event): - print "Scroll" + # def on_scroll_over_plot(self, event): + # print "Scroll" + # center = [event.xdata, event.ydata] + # if sign(event.step): + # self.zoom(1.5, center=center) + # else: + # self.zoom(1/1.5, center=center) + # + # def on_window_scroll(self, event): + # print "Scroll" def on_key_over_plot(self, event): print 'you pressed', event.key, event.xdata, event.ydata diff --git a/cirkuix.ui b/cirkuix.ui index 9358d795..de610434 100644 --- a/cirkuix.ui +++ b/cirkuix.ui @@ -98,7 +98,7 @@ - + Plot True True @@ -114,7 +114,7 @@ - + Solid True True @@ -129,7 +129,7 @@ - + Multi-colored True True @@ -304,7 +304,7 @@ - + Plot True True @@ -320,7 +320,7 @@ - + Solid True True @@ -335,7 +335,7 @@ - + Multi-colored True True @@ -447,7 +447,7 @@ - + Plot True True @@ -463,7 +463,7 @@ - + Solid True True @@ -478,7 +478,7 @@ - + Multi-colored True True @@ -706,7 +706,7 @@ - + Plot True True @@ -722,7 +722,7 @@ - + Merge Polygons True True @@ -738,7 +738,7 @@ - + Solid True True @@ -753,7 +753,7 @@ - + Multi-colored True True