From 609561f7a3c53389cfb49a378cf87eb5b3b8c088 Mon Sep 17 00:00:00 2001 From: Juan Pablo Caram Date: Wed, 2 Apr 2014 19:53:00 -0400 Subject: [PATCH] Created class ObjectCollection to manage the list of objects in the program. Converted the program to use it. Not fully functional yet. --- FlatCAM.py | 562 ++++++++++++++++++++--------------- FlatCAM.ui | 797 +++++++++++++++++++++++++------------------------- FlatCAMObj.py | 4 +- camlib.py | 26 +- recent.json | 2 +- 5 files changed, 746 insertions(+), 645 deletions(-) diff --git a/FlatCAM.py b/FlatCAM.py index cf5a3f4b..0facf0ff 100644 --- a/FlatCAM.py +++ b/FlatCAM.py @@ -23,14 +23,17 @@ from numpy import arange, sin, pi from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas #from mpl_toolkits.axes_grid.anchored_artists import AnchoredText -from camlib import * + import sys import urllib import copy import random +######################################## +## Imports part of FlatCAM ## +######################################## +from camlib import * from FlatCAMObj import * - from FlatCAMWorker import Worker ######################################## @@ -41,6 +44,8 @@ class App: The main application class. The constructor starts the GUI. """ + version_url = "http://caram.cl/flatcam/VERSION" + def __init__(self): """ Starts the application. Takes no parameters. @@ -78,7 +83,7 @@ class App: self.combo_options = self.builder.get_object("combo_options") self.combo_options.set_active(1) - self.setup_project_list() # The "Project" tab + #self.setup_project_list() # The "Project" tab self.setup_component_editor() # The "Selected" tab ## Setup the toolbar. Adds buttons. @@ -95,16 +100,20 @@ class App: self.setup_tooltips() + # TODO: Hardcoded list + for kind in ['gerber', 'excellon', 'geometry', 'cncjob']: + entry_name = self.builder.get_object("entry_text_" + kind + "_name") + entry_name.connect("activate", self.on_activate_name) + #### DATA #### self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) self.setup_obj_classes() - self.stuff = {} # FlatCAMObj's by name self.mouse = None # Mouse coordinates over plot self.recent = [] - - # What is selected by the user. It is - # a key if self.stuff - self.selected_item_name = None + self.collection = ObjectCollection() + self.builder.get_object("box_project").pack_start(self.collection.view, False, False, 1) + # TODO: Do this different + self.collection.view.connect("row_activated", self.on_row_activated) # Used to inhibit the on_options_update callback when # the options are being changed by the program and not the user. @@ -140,6 +149,7 @@ class App: # self.radios_inv.update({obj.kind + "_" + option: obj.radios_inv[option]}) ## Event subscriptions ## + # TODO: Move to plotcanvas self.plot_click_subscribers = {} self.plot_mousemove_subscribers = {} @@ -168,11 +178,11 @@ class App: self.worker.start() #### Check for updates #### + # Separate thread (Not worker) self.version = 4 t1 = threading.Thread(target=self.versionCheck) t1.daemon = True t1.start() - # self.worker.add_task(self.versionCheck) #### For debugging only ### def somethreadfunc(app_obj): @@ -245,27 +255,47 @@ class App: """ FlatCAMObj.app = self - def setup_project_list(self): - """ - Sets up list or Tree where whatever has been loaded or created is - displayed. - - :return: None - """ - - self.store = Gtk.ListStore(str) - self.tree = Gtk.TreeView(self.store) - self.tree_select = self.tree.get_selection() - renderer = Gtk.CellRendererText() - column = Gtk.TreeViewColumn("Objects", renderer, text=0) - self.tree.append_column(column) - self.builder.get_object("box_project").pack_start(self.tree, False, False, 1) - - # Double-click or Enter takes you to the object's options - self.tree.connect("row_activated", self.on_row_activated) - - # Changes the selected item and populates the object options form - self.signal_id = self.tree_select.connect("changed", self.on_tree_selection_changed) + # def setup_project_list(self): + # """ + # Sets up list or Tree where whatever has been loaded or created is + # displayed. + # + # :return: None + # """ + # + # # Model + # self.store = Gtk.ListStore(GdkPixbuf.Pixbuf, str) + # #self.store = Gtk.ListStore(str, str) + # + # # View + # self.tree = Gtk.TreeView(model=self.store) + # + # # Renderers + # renderer_pixbuf = Gtk.CellRendererPixbuf() + # column_pixbuf = Gtk.TreeViewColumn("Type", renderer_pixbuf, pixbuf=0) + # # column_pixbuf = Gtk.TreeViewColumn("Type") + # # column_pixbuf.pack_start(renderer_pixbuf, False) + # # column_pixbuf.add_attribute(renderer_pixbuf, "pixbuf", 1) + # self.tree.append_column(column_pixbuf) + # + # # renderer1 = Gtk.CellRendererText() + # # column1 = Gtk.TreeViewColumn("Type", renderer1, text=0) + # # self.tree.append_column(column1) + # + # renderer = Gtk.CellRendererText() + # column = Gtk.TreeViewColumn("Object name", renderer, text=1) + # self.tree.append_column(column) + # + # self.tree_select = self.tree.get_selection() + # + # + # self.builder.get_object("box_project").pack_start(self.tree, False, False, 1) + # + # # Double-click or Enter takes you to the object's options + # self.tree.connect("row_activated", self.on_row_activated) + # + # # Changes the selected item and populates the object options form + # self.signal_id = self.tree_select.connect("changed", self.on_tree_selection_changed) def setup_component_editor(self): """ @@ -293,6 +323,7 @@ class App: def setup_recent_items(self): + # TODO: Move this to constructor icons = { "gerber": "share/flatcam_icon16.png", "excellon": "share/drill16.png", @@ -309,13 +340,6 @@ class App: # Closure needed to create callbacks in a loop. # Otherwise late binding occurs. - # def make_callback(func, fname): - # def opener(*args): - # t = threading.Thread(target=lambda: func(fname)) - # t.daemon = True - # t.start() - # return opener - def make_callback(func, fname): def opener(*args): self.worker.add_task(func, [fname]) @@ -362,21 +386,31 @@ class App: """ self.info_label.set_text(text) - def build_list(self): - """ - Clears and re-populates the list of objects in currently - in the project. - - :return: None - """ - print "build_list(): clearing" - self.tree_select.unselect_all() - self.store.clear() - print "repopulating...", - for key in self.stuff: - print key, - self.store.append([key]) - print + # def build_list(self): + # """ + # Clears and re-populates the list of objects in currently + # in the project. + # + # :return: None + # """ + # icons = { + # "gerber": "share/flatcam_icon16.png", + # "excellon": "share/drill16.png", + # "cncjob": "share/cnc16.png", + # "geometry": "share/geometry16.png" + # } + # + # + # print "build_list(): clearing" + # self.tree_select.unselect_all() + # self.store.clear() + # print "repopulating...", + # for key in self.stuff: + # print key, + # obj = self.stuff[key] + # icon = GdkPixbuf.Pixbuf.new_from_file(icons[obj.kind]) + # self.store.append([icon, key]) + # print def get_radio_value(self, radio_set): """ @@ -401,26 +435,24 @@ class App: self.plotcanvas.clear() self.set_progress_bar(0.1, "Re-plotting...") - def thread_func(app_obj): + def worker_task(app_obj): percentage = 0.1 try: - delta = 0.9 / len(self.stuff) + delta = 0.9 / len(self.collection.get_list()) except ZeroDivisionError: GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, "")) return - for i in self.stuff: - self.stuff[i].plot() + for obj in self.collection.get_list(): + obj.plot() percentage += delta GLib.idle_add(lambda: app_obj.set_progress_bar(percentage, "Re-plotting...")) GLib.idle_add(app_obj.plotcanvas.auto_adjust_axes) GLib.idle_add(lambda: self.on_zoom_fit(None)) - GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, "")) + GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, "Idle")) - # t = threading.Thread(target=thread_func, args=(self,)) - # t.daemon = True - # t.start() - self.worker.add_task(thread_func, [self]) + # Send to worker + self.worker.add_task(worker_task, [self]) def get_eval(self, widget_name): """ @@ -442,26 +474,26 @@ class App: self.info("Could not evaluate: " + value) return None - def set_list_selection(self, name): - """ - Marks a given object as selected in the list ob objects - in the GUI. This selection will in turn trigger - ``self.on_tree_selection_changed()``. - - :param name: Name of the object. - :type name: str - :return: None - """ - - 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) - - # Need to return False such that GLib.idle_add - # or .timeout_add do not repeat. - return False + # def set_list_selection(self, name): + # """ + # Marks a given object as selected in the list ob objects + # in the GUI. This selection will in turn trigger + # ``self.on_tree_selection_changed()``. + # + # :param name: Name of the object. + # :type name: str + # :return: None + # """ + # + # iter = self.store.get_iter_first() + # while iter is not None and self.store[iter][1] != name: + # iter = self.store.iter_next(iter) + # self.tree_select.unselect_all() + # self.tree_select.select_iter(iter) + # + # # Need to return False such that GLib.idle_add + # # or .timeout_add do not repeat. + # return False def new_object(self, kind, name, initialize): """ @@ -482,7 +514,7 @@ class App: """ ### Check for existing name - if name in self.stuff: + if name in self.collection.get_names(): ## Create a new name # Ends with number? match = re.search(r'(.*[^\d])?(\d+)$', name) @@ -503,12 +535,6 @@ class App: obj = classdict[kind](name) obj.units = self.options["units"] # TODO: The constructor should look at defaults. - # Initialize as per user request - # User must take care to implement initialize - # in a thread-safe way as is is likely that we - # have been invoked in a separate thread. - #initialize(obj, self) - # Set default options from self.options for option in self.options: if option.find(kind + "_") == 0: @@ -527,14 +553,10 @@ class App: obj.convert_units(self.options["units"]) # Add to our records - self.stuff[name] = obj + # TODO: Perhaps make collection thread safe instead? + GLib.idle_add(lambda: self.collection.append(obj, active=True)) - # Update GUI list and select it (Thread-safe?) - self.store.append([name]) - #self.build_list() - GLib.idle_add(lambda: self.set_list_selection(name)) - # TODO: Gtk.notebook.set_current_page is not known to - # TODO: return False. Fix this?? + # Show object details now. GLib.timeout_add(100, lambda: self.notebook.set_current_page(1)) # Plot @@ -560,22 +582,22 @@ class App: self.progress_bar.set_fraction(percentage) return False - def get_current(self): - """ - Returns the currently selected FlatCAMObj in the application. - - :return: Currently selected FlatCAMObj in the application. - :rtype: FlatCAMObj or None - """ - - # TODO: Could possibly read the form into the object here. - # But there are some cases when the form for the object - # is not up yet. See on_tree_selection_changed. - - try: - return self.stuff[self.selected_item_name] - except: - return None + # def get_current(self): + # """ + # Returns the currently selected FlatCAMObj in the application. + # + # :return: Currently selected FlatCAMObj in the application. + # :rtype: FlatCAMObj or None + # """ + # + # # TODO: Could possibly read the form into the object here. + # # But there are some cases when the form for the object + # # is not up yet. See on_tree_selection_changed. + # + # try: + # return self.stuff[self.selected_item_name] + # except: + # return None def load_defaults(self): """ @@ -712,11 +734,11 @@ class App: # Capture the latest changes try: - self.get_current().read_form() + self.collection.get_active().read_form() except: pass - d = {"objs": [self.stuff[o].to_dict() for o in self.stuff], + d = {"objs": [obj.to_dict() for obj in self.collection.get_list()], "options": self.options} try: @@ -746,15 +768,12 @@ class App: try: f = open(filename, 'r') except: - #print "WARNING: Failed to open project file:", filename self.info("ERROR: Failed to open project file: %s" % filename) return try: d = json.load(f, object_hook=dict2obj) except: - #print sys.exc_info() - #print "WARNING: Failed to parse project file:", filename self.info("ERROR: Failed to parse project file: %s" % filename) f.close() return @@ -790,8 +809,8 @@ class App: combo = self.builder.get_object(combo) combo.remove_all() - for obj in self.stuff: - combo.append_text(obj) + for name in self.collection.get_names(): + combo.append_text(name) def versionCheck(self, *args): """ @@ -803,7 +822,7 @@ class App: """ try: - f = urllib.urlopen("http://caram.cl/flatcam/VERSION") # TODO: Hardcoded. + f = urllib.urlopen(App.version_url) except: GLib.idle_add(lambda: self.info("ERROR trying to check for latest version.")) return @@ -868,52 +887,49 @@ class App: self.set_progress_bar(0.1, "Re-plotting...") - def thread_func(app_obj): + def worker_task(app_obj): percentage = 0.1 try: - delta = 0.9 / len(self.stuff) + delta = 0.9 / len(self.collection.get_list()) except ZeroDivisionError: GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, "")) return - for i in self.stuff: - if i != app_obj.selected_item_name or not except_current: - self.stuff[i].options['plot'] = False - self.stuff[i].plot() + for obj in self.collection.get_list(): + #if i != app_obj.selected_item_name or not except_current: + if obj != self.collection.get_active() or not except_current: + obj.options['plot'] = False + obj.plot() percentage += delta GLib.idle_add(lambda: app_obj.set_progress_bar(percentage, "Re-plotting...")) GLib.idle_add(app_obj.plotcanvas.auto_adjust_axes) GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, "")) - # t = threading.Thread(target=thread_func, args=(self,)) - # t.daemon = True - # t.start() - self.worker.add_task(thread_func, [self]) + # Send to worker + self.worker.add_task(worker_task, [self]) def enable_all_plots(self, *args): self.plotcanvas.clear() self.set_progress_bar(0.1, "Re-plotting...") - def thread_func(app_obj): + def worker_task(app_obj): percentage = 0.1 try: - delta = 0.9 / len(self.stuff) + delta = 0.9 / len(self.collection.get_list()) except ZeroDivisionError: GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, "")) return - for i in self.stuff: - self.stuff[i].options['plot'] = True - self.stuff[i].plot() + for obj in self.collection.get_list(): + obj.options['plot'] = True + obj.plot() percentage += delta GLib.idle_add(lambda: app_obj.set_progress_bar(percentage, "Re-plotting...")) GLib.idle_add(app_obj.plotcanvas.auto_adjust_axes) GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, "")) - # t = threading.Thread(target=thread_func, args=(self,)) - # t.daemon = True - # t.start() - self.worker.add_task(thread_func, [self]) + # Send to worker + self.worker.add_task(worker_task, [self]) def register_recent(self, kind, filename): record = {'kind': kind, 'filename': filename} @@ -960,7 +976,7 @@ class App: :return: None """ - obj = self.get_current() + obj = self.collection.get_active() obj.read_form() assert isinstance(obj, FlatCAMObj) try: @@ -981,8 +997,10 @@ class App: :return: None """ - self.get_current().read_form() - self.get_current().plot() + # self.get_current().read_form() + # self.get_current().plot() + self.collection.get_active().read_form() + self.collection.get_active().plot() def on_about(self, widget): """ @@ -1007,12 +1025,8 @@ class App: # TODO: Move (some of) this to camlib! # Object to mirror - try: - obj_name = self.builder.get_object("comboboxtext_bottomlayer").get_active_text() - fcobj = self.stuff[obj_name] - except KeyError: - self.info("WARNING: Cannot mirror that object.") - return + obj_name = self.builder.get_object("comboboxtext_bottomlayer").get_active_text() + fcobj = self.collection.get_by_name(obj_name) # For now, lets limit to Gerbers and Excellons. # assert isinstance(gerb, FlatCAMGerber) @@ -1030,23 +1044,13 @@ class App: px, py = eval(self.point_entry.get_text()) else: # The axis is the line dividing the box in the middle name = self.box_combo.get_active_text() - bb_obj = self.stuff[name] + bb_obj = self.collection.get_by_name(name) xmin, ymin, xmax, ymax = bb_obj.bounds() px = 0.5*(xmin+xmax) py = 0.5*(ymin+ymax) - # Do the mirroring - # xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis] - # mirrored = affinity.scale(fcobj.solid_geometry, xscale, yscale, origin=(px, py)) - # - # def obj_init(obj_inst, app_inst): - # obj_inst.solid_geometry = mirrored - # - # self.new_object("gerber", fcobj.options["name"] + "_mirror", obj_init) - fcobj.mirror(axis, [px, py]) fcobj.plot() - #self.on_update_plot(None) def on_create_aligndrill(self, widget): """ @@ -1067,7 +1071,7 @@ class App: px, py = eval(self.point_entry.get_text()) else: name = self.box_combo.get_active_text() - bb_obj = self.stuff[name] + bb_obj = self.collection.get_by_name(name) xmin, ymin, xmax, ymax = bb_obj.bounds() px = 0.5*(xmin+xmax) py = 0.5*(ymin+ymax) @@ -1232,11 +1236,10 @@ class App: self.read_form() scale_options(factor) self.options2form() - for obj in self.stuff: + for obj in self.collection.get_list(): units = self.get_radio_value({"rb_mm": "MM", "rb_inch": "IN"}) - #print "Converting ", obj, " to ", units - self.stuff[obj].convert_units(units) - current = self.get_current() + obj.convert_units(units) + current = self.collection.get_active() if current is not None: current.to_form() self.plot_all() @@ -1355,7 +1358,7 @@ class App: :return: None """ - obj = self.get_current() + obj = self.collection.get_active() if obj is None: self.info("WARNING: No object selected.") return @@ -1374,7 +1377,7 @@ class App: :return: None """ - obj = self.get_current() + obj = self.collection.get_active() if obj is None: self.info("WARNING: No object selected.") return @@ -1393,7 +1396,7 @@ class App: :param param: Ignored. :return: None """ - obj = self.get_current() + obj = self.collection.get_active() if obj is None: self.info("WARNING: No object selected.") return @@ -1413,7 +1416,7 @@ class App: :return: None """ - obj = self.get_current() + obj = self.collection.get_active() if obj is None: self.info("WARNING: No object selected.") return @@ -1501,7 +1504,7 @@ class App: :return: None """ - obj = self.get_current() + obj = self.collection.get_active() factor = self.get_eval("entry_eval_" + obj.kind + "_scalefactor") obj.scale(factor) obj.to_form() @@ -1542,7 +1545,7 @@ class App: :return: None """ # TODO: Use Gerber.get_bounding_box(...) - gerber = self.get_current() + gerber = self.collection.get_active() gerber.read_form() name = gerber.options["name"] + "_bbox" @@ -1565,7 +1568,7 @@ class App: :return: None """ - obj = self.get_current() + obj = self.collection.get_active() obj.read_form() self.set_progress_bar(0.5, "Plotting...") @@ -1589,7 +1592,7 @@ class App: :return: None """ - excellon = self.get_current() + excellon = self.collection.get_active() excellon.read_form() job_name = excellon.options["name"] + "_cnc" @@ -1636,7 +1639,7 @@ class App: :param widget: The widget from which this was called. :return: None """ - excellon = self.get_current() + excellon = self.collection.get_active() assert isinstance(excellon, FlatCAMExcellon) excellon.show_tool_chooser() @@ -1650,7 +1653,7 @@ class App: :return: """ self.on_eval_update(widget) - obj = self.get_current() + obj = self.collection.get_active() assert isinstance(obj, FlatCAMObj) obj.read_form() @@ -1664,7 +1667,7 @@ class App: :return: None """ - gerb = self.get_current() + gerb = self.collection.get_active() gerb.read_form() name = gerb.options["name"] + "_noncopper" @@ -1688,7 +1691,7 @@ class App: :return: None """ - gerb = self.get_current() + gerb = self.collection.get_active() gerb.read_form() name = gerb.options["name"] + "_cutout" @@ -1749,7 +1752,7 @@ class App: :return: None """ - gerb = self.get_current() + gerb = self.collection.get_active() gerb.read_form() dia = gerb.options["isotooldia"] passes = int(gerb.options["isopasses"]) @@ -1779,7 +1782,7 @@ class App: :return: None """ - source_geo = self.get_current() + source_geo = self.collection.get_active() source_geo.read_form() job_name = source_geo.options["name"] + "_cnc" @@ -1834,7 +1837,7 @@ class App: """ self.info("Click inside the desired polygon.") - geo = self.get_current() + geo = self.collection.get_active() geo.read_form() assert isinstance(geo, FlatCAMGeometry) tooldia = geo.options["painttooldia"] @@ -1868,7 +1871,7 @@ class App: :return: None """ def on_success(app_obj, filename): - cncjob = app_obj.get_current() + cncjob = app_obj.collection.get_active() f = open(filename, 'w') f.write(cncjob.gcode) f.close() @@ -1885,17 +1888,17 @@ class App: """ # Keep this for later - name = copy.copy(self.selected_item_name) + name = copy.copy(self.collection.get_active().options["name"]) # Remove plot - self.plotcanvas.figure.delaxes(self.get_current().axes) + self.plotcanvas.figure.delaxes(self.collection.get_active().axes) self.plotcanvas.auto_adjust_axes() - # Remove from dictionary - self.stuff.pop(self.selected_item_name) + # Clear form + self.setup_component_editor() - # Update UI - self.build_list() # Update the items list + # Remove from dictionary + self.collection.delete_active() self.info("Object deleted: %s" % name) @@ -1907,7 +1910,7 @@ class App: :return: None """ - self.get_current().read_form() + self.collection.get_active().read_form() self.plot_all() @@ -1929,49 +1932,10 @@ class App: :return: None """ - # Disconnect event listener - self.tree.get_selection().disconnect(self.signal_id) - - new_name = entry.get_text() # Get from form - self.stuff[new_name] = self.stuff.pop(self.selected_item_name) # Update dictionary - self.stuff[new_name].options["name"] = new_name # update object - self.info('Name change: ' + self.selected_item_name + " to " + new_name) - - self.selected_item_name = new_name # Update selection name - - self.build_list() # Update the items list - - # Reconnect event listener - self.signal_id = self.tree.get_selection().connect( - "changed", self.on_tree_selection_changed) - - def on_tree_selection_changed(self, selection): - """ - Callback for selection change in the project list. This changes - the currently selected FlatCAMObj. - - :param selection: Selection associated to the project tree or list - :type selection: Gtk.TreeSelection - :return: None - """ - print "DEBUG: on_tree_selection_change(): ", - model, treeiter = selection.get_selected() - - if treeiter is not None: - # Save data for previous selection - obj = self.get_current() - if obj is not None: - obj.read_form() - - print "DEBUG: You selected", model[treeiter][0] - self.selected_item_name = model[treeiter][0] - obj_new = self.get_current() - if obj_new is not None: - GLib.idle_add(lambda: obj_new.build_ui()) - else: - print "DEBUG: Nothing selected" - self.selected_item_name = None - self.setup_component_editor() + old_name = copy.copy(self.collection.get_active().options["name"]) + new_name = entry.get_text() + self.collection.change_name(old_name, new_name) + self.info("Name changed from %s to %s" % (old_name, new_name)) def on_file_new(self, param): """ @@ -1981,19 +1945,18 @@ class App: :param param: Whatever is passed by the event. Ignore. :return: None """ - # Remove everythong from memory + # Remove everything from memory # Clear plot self.plotcanvas.clear() - # Clear object editor - #self.setup_component_editor() + # Delete data + self.collection.delete_all() - # Clear data - self.stuff = {} + # Clear object editor + self.setup_component_editor() # Clear list - #self.tree_select.unselect_all() - self.build_list() + self.collection.build_list() # Clear project filename self.project_filename = None @@ -2339,7 +2302,7 @@ class App: :param event: Ignored. :return: None """ - xmin, ymin, xmax, ymax = get_bounds(self.stuff) + xmin, ymin, xmax, ymax = self.collection.get_bounds() width = xmax - xmin height = ymax - ymin xmin -= 0.05 * width @@ -2804,11 +2767,79 @@ class PlotCanvas: class ObjectCollection: + + classdict = { + "gerber": FlatCAMGerber, + "excellon": FlatCAMExcellon, + "cncjob": FlatCAMCNCjob, + "geometry": FlatCAMGeometry + } + + icons = { + "gerber": "share/flatcam_icon16.png", + "excellon": "share/drill16.png", + "cncjob": "share/cnc16.png", + "geometry": "share/geometry16.png" + } + def __init__(self): + + ### Data + # List of FLatCAMObject's self.collection = [] self.active = None + ### GUI List components + ## Model + self.store = Gtk.ListStore(GdkPixbuf.Pixbuf, str) + + ## View + self.view = Gtk.TreeView(model=self.store) + self.view.connect("row_activated", self.on_row_activated) + self.tree_selection = self.view.get_selection() + self.change_subscription = self.tree_selection.connect("changed", self.on_list_selection_change) + + # Renderers + renderer_pixbuf = Gtk.CellRendererPixbuf() + column_pixbuf = Gtk.TreeViewColumn("Type", renderer_pixbuf, pixbuf=0) + self.view.append_column(column_pixbuf) + + renderer_text = Gtk.CellRendererText() + column_text = Gtk.TreeViewColumn("Name", renderer_text, text=1) + self.view.append_column(column_text) + + def delete_all(self): + print "OC.delete_all()" + self.collection = [] + self.active = None + + def delete_active(self): + print "OC.delete_active()" + self.collection.remove(self.active) + self.active = None + self.build_list() + + def on_row_activated(self, *args): + print "OC.on_row_activated()" + return + + def on_list_selection_change(self, selection): + print "OC.on_list_selection_change()" + model, treeiter = selection.get_selected() + + try: + self.get_active().read_form() + except: + pass + + try: + self.set_active(model[treeiter][1]) + self.get_active().build_ui() + except: + pass + def set_active(self, name): + print "OC.set_active()" for obj in self.collection: if obj.options['name'] == name: self.active = obj @@ -2816,10 +2847,63 @@ class ObjectCollection: return None def get_active(self): + print "OC.get_active()" return self.active - def append(self, obj): - self.collection.append(obj) + def set_list_selection(self, name): + print "OC.set_list_selection()" + iterat = self.store.get_iter_first() + while iterat is not None and self.store[iterat][1] != name: + iterat = self.store.iter_next(iterat) + self.tree_selection.unselect_all() + self.tree_selection.select_iter(iterat) + + def append(self, obj, active=False): + print "OC.append()" + if obj not in self.collection: + self.collection.append(obj) + self.build_list() + + if active: + self.set_list_selection(obj.options["name"]) + + def get_names(self): + print "OC.get_names()" + return [o.options["name"] for o in self.collection] + + def build_list(self): + print "OC.build_list()" + self.store.clear() + for obj in self.collection: + icon = GdkPixbuf.Pixbuf.new_from_file(ObjectCollection.icons[obj.kind]) + self.store.append([icon, obj.options["name"]]) + + def get_bounds(self): + print "OC.get_bounds()" + return get_bounds(self.collection) + + def get_list(self): + return self.collection + + def get_by_name(self, name): + for obj in self.collection: + if obj.options["name"] == name: + return obj + return None + + def change_name(self, old_name, new_name): + self.tree_selection.disconnect(self.change_subscription) + + for obj in self.collection: + if obj.options["name"] == old_name: + obj.options["name"] = new_name + self.build_list() + self.tree_selection.connect("changed", self.on_list_selection_change) + if obj == self.get_active(): + self.set_active(new_name) + self.set_list_selection(new_name) + break + app = App() Gtk.main() diff --git a/FlatCAM.ui b/FlatCAM.ui index 002e2f5e..f3b3ef65 100644 --- a/FlatCAM.ui +++ b/FlatCAM.ui @@ -132,6 +132,404 @@ THE SOFTWARE. False gtk-open + + False + + + True + True + never + in + + + True + False + + + True + False + 5 + 5 + 5 + 5 + vertical + + + True + False + 6 + 3 + Double-Sided PCB Tool + + + + + + False + True + 0 + + + + + True + False + 3 + 3 + + + True + False + The object that you want to flip around, +usually the Gerber object defining the +bottom copper layer. You can also flip +an Excellon object in case you want to drill +from the bottom side. + 1 + 3 + Bottom Layer: + + + 0 + 0 + 1 + 1 + + + + + 200 + True + False + start + 0 + 1 + + + 1 + 0 + 1 + 1 + + + + + True + False + <b>X</b> flips from top to bottom, +<b>Y</b> flips from left to right. + 1 + 3 + Mirror Axis: + + + 0 + 1 + 1 + 1 + + + + + True + False + 10 + + + X + True + True + False + 0 + True + True + + + False + True + 0 + + + + + Y + True + True + False + 0 + True + rb_mirror_x + + + False + True + 1 + + + + + 1 + 1 + 1 + 1 + + + + + True + False + How the location of the axis +is specified. + 1 + 3 + Axis location: + + + 0 + 2 + 1 + 1 + + + + + True + False + 10 + + + Point + True + True + False + The axis must pass through the +specified point. + 0 + True + True + + + + False + True + 0 + + + + + Box + True + True + False + The axis cuts a box (some Geometry object +in the project) exactly in the middle. + 0 + True + rb_mirror_point + + + False + True + 1 + + + + + 1 + 2 + 1 + 1 + + + + + True + False + <b>Point:</b> Click on the desired point on the plot. +This copies the point to the clipboard. Then paste it +in the box by right-clicking and choosing paste, or +hitting Control-v. + +<b>Box:</b> Choose an object in the project +that you want to use as a box for specifying +the flipping axis. If the object is not a rectangle, +a bounding box arounf the object is calculated. + 1 + 3 + Point/Box: + + + 0 + 3 + 1 + 1 + + + + + True + False + vertical + + + + + + 1 + 3 + 1 + 1 + + + + + True + False + List of coordinates where to drill alignment +holes, in the format <b>(x1, y1), (x2, y2)</b>, etc. +You can click on the plot and paste each +coordinate here. + +All <b>coordinates are duplicated</b> and mirrored +automatically around the axis so drill pattens are +identical when flipping your board around. + 1 + 3 + Algnmt holes: + + + 0 + 4 + 1 + 1 + + + + + True + True + + + + 1 + 4 + 1 + 1 + + + + + True + False + Diameter of the drill for +the aligment holes. + 1 + 3 + Drill diam.: + + + 0 + 5 + 1 + 1 + + + + + True + True + + True + + + 1 + 5 + 1 + 1 + + + + + False + True + 1 + + + + + True + False + end + 6 + 3 + + + Create Alignment Drill + 120 + True + True + True + Creates an Excellon object with +the specified holes and their +mirror pairs. + end + + + + + False + False + 4 + 0 + + + + + Mirror Object + 120 + True + True + True + Mirrors the object specified in +<b>Bottom Layer</b> around the +specified axis. + end + + + + + False + False + 4 + 1 + + + + + False + True + 2 + + + + + + + + + + + + + + + + + + + + + False @@ -1145,7 +1543,6 @@ after hitting Enter in the text box. True True - False @@ -2721,404 +3118,6 @@ this object. - - False - - - True - True - never - in - - - True - False - - - True - False - 5 - 5 - 5 - 5 - vertical - - - True - False - 6 - 3 - Double-Sided PCB Tool - - - - - - False - True - 0 - - - - - True - False - 3 - 3 - - - True - False - The object that you want to flip around, -usually the Gerber object defining the -bottom copper layer. You can also flip -an Excellon object in case you want to drill -from the bottom side. - 1 - 3 - Bottom Layer: - - - 0 - 0 - 1 - 1 - - - - - 200 - True - False - start - 0 - 1 - - - 1 - 0 - 1 - 1 - - - - - True - False - <b>X</b> flips from top to bottom, -<b>Y</b> flips from left to right. - 1 - 3 - Mirror Axis: - - - 0 - 1 - 1 - 1 - - - - - True - False - 10 - - - X - True - True - False - 0 - True - True - - - False - True - 0 - - - - - Y - True - True - False - 0 - True - rb_mirror_x - - - False - True - 1 - - - - - 1 - 1 - 1 - 1 - - - - - True - False - How the location of the axis -is specified. - 1 - 3 - Axis location: - - - 0 - 2 - 1 - 1 - - - - - True - False - 10 - - - Point - True - True - False - The axis must pass through the -specified point. - 0 - True - True - - - - False - True - 0 - - - - - Box - True - True - False - The axis cuts a box (some Geometry object -in the project) exactly in the middle. - 0 - True - rb_mirror_point - - - False - True - 1 - - - - - 1 - 2 - 1 - 1 - - - - - True - False - <b>Point:</b> Click on the desired point on the plot. -This copies the point to the clipboard. Then paste it -in the box by right-clicking and choosing paste, or -hitting Control-v. - -<b>Box:</b> Choose an object in the project -that you want to use as a box for specifying -the flipping axis. If the object is not a rectangle, -a bounding box arounf the object is calculated. - 1 - 3 - Point/Box: - - - 0 - 3 - 1 - 1 - - - - - True - False - vertical - - - - - - 1 - 3 - 1 - 1 - - - - - True - False - List of coordinates where to drill alignment -holes, in the format <b>(x1, y1), (x2, y2)</b>, etc. -You can click on the plot and paste each -coordinate here. - -All <b>coordinates are duplicated</b> and mirrored -automatically around the axis so drill pattens are -identical when flipping your board around. - 1 - 3 - Algnmt holes: - - - 0 - 4 - 1 - 1 - - - - - True - True - - - - 1 - 4 - 1 - 1 - - - - - True - False - Diameter of the drill for -the aligment holes. - 1 - 3 - Drill diam.: - - - 0 - 5 - 1 - 1 - - - - - True - True - - True - - - 1 - 5 - 1 - 1 - - - - - False - True - 1 - - - - - True - False - end - 6 - 3 - - - Create Alignment Drill - 120 - True - True - True - Creates an Excellon object with -the specified holes and their -mirror pairs. - end - - - - - False - False - 4 - 0 - - - - - Mirror Object - 120 - True - True - True - Mirrors the object specified in -<b>Bottom Layer</b> around the -specified axis. - end - - - - - False - False - 4 - 1 - - - - - False - True - 2 - - - - - - - - - - - - - - - - - - - - - 600 400 diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 33c5145d..b6a1f5d7 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -111,8 +111,8 @@ class FlatCAMObj: # Put in the UI box_selected.pack_start(sw, True, True, 0) - entry_name = self.app.builder.get_object("entry_text_" + self.kind + "_name") - entry_name.connect("activate", self.app.on_activate_name) + # entry_name = self.app.builder.get_object("entry_text_" + self.kind + "_name") + # entry_name.connect("activate", self.app.on_activate_name) self.to_form() sw.show() diff --git a/camlib.py b/camlib.py index bc90ff2f..12500394 100644 --- a/camlib.py +++ b/camlib.py @@ -2251,16 +2251,35 @@ class CNCjob(Geometry): self.create_geometry() -def get_bounds(geometry_set): +# def get_bounds(geometry_set): +# xmin = Inf +# ymin = Inf +# xmax = -Inf +# ymax = -Inf +# +# #print "Getting bounds of:", str(geometry_set) +# for gs in geometry_set: +# try: +# gxmin, gymin, gxmax, gymax = geometry_set[gs].bounds() +# xmin = min([xmin, gxmin]) +# ymin = min([ymin, gymin]) +# xmax = max([xmax, gxmax]) +# ymax = max([ymax, gymax]) +# except: +# print "DEV WARNING: Tried to get bounds of empty geometry." +# +# return [xmin, ymin, xmax, ymax] + +def get_bounds(geometry_list): xmin = Inf ymin = Inf xmax = -Inf ymax = -Inf #print "Getting bounds of:", str(geometry_set) - for gs in geometry_set: + for gs in geometry_list: try: - gxmin, gymin, gxmax, gymax = geometry_set[gs].bounds() + gxmin, gymin, gxmax, gymax = gs.bounds() xmin = min([xmin, gxmin]) ymin = min([ymin, gymin]) xmax = max([xmax, gxmax]) @@ -2270,7 +2289,6 @@ def get_bounds(geometry_set): return [xmin, ymin, xmax, ymax] - def arc(center, radius, start, stop, direction, steps_per_circ): """ Creates a list of point along the specified arc. diff --git a/recent.json b/recent.json index 93d7f0ce..7189b80d 100644 --- a/recent.json +++ b/recent.json @@ -1 +1 @@ -[{"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\BLDC2003Through.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.GTL"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\BLDC_1303_Bottom.gbr"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles-F_Cu.gtl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom.gbr"}, {"kind": "cncjob", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\RTWO1_CNC\\cutout1.gcode"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\RTWO1.fcproj"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.TXT"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.DRL"}] \ No newline at end of file +[{"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.GBL"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\BLDC2003Through.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.GTL"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\BLDC_1303_Bottom.gbr"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles-F_Cu.gtl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom.gbr"}, {"kind": "cncjob", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\RTWO1_CNC\\cutout1.gcode"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\RTWO1.fcproj"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.TXT"}] \ No newline at end of file