Juan Pablo Caram 12 лет назад
Родитель
Сommit
6284156a0d
3 измененных файлов с 708 добавлено и 9 удалено
  1. 207 2
      FlatCAM.py
  2. 413 0
      FlatCAM.ui
  3. 88 7
      camlib.py

+ 207 - 2
FlatCAM.py

@@ -1,3 +1,9 @@
+############################################################
+# Author: Juan Pablo Caram                                 #
+# Date: 2/5/2014                                           #
+# caram.cl                                                 #
+############################################################
+
 import threading
 import threading
 from gi.repository import Gtk, Gdk, GLib, GObject
 from gi.repository import Gtk, Gdk, GLib, GObject
 import simplejson as json
 import simplejson as json
@@ -255,7 +261,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
     def plot(self, figure):
     def plot(self, figure):
         FlatCAMObj.plot(self, figure)
         FlatCAMObj.plot(self, figure)
 
 
-        self.create_geometry()
+        #self.create_geometry()
 
 
         if self.options["mergepolys"]:
         if self.options["mergepolys"]:
             geometry = self.solid_geometry
             geometry = self.solid_geometry
@@ -334,7 +340,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
     def plot(self, figure):
     def plot(self, figure):
         FlatCAMObj.plot(self, figure)
         FlatCAMObj.plot(self, figure)
         #self.setup_axes(figure)
         #self.setup_axes(figure)
-        self.create_geometry()
+        #self.create_geometry()
 
 
         # Plot excellon
         # Plot excellon
         for geo in self.solid_geometry:
         for geo in self.solid_geometry:
@@ -572,6 +578,7 @@ class App:
         self.setup_component_editor()  # The "Selected" tab
         self.setup_component_editor()  # The "Selected" tab
 
 
         #### DATA ####
         #### DATA ####
+        self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
         self.setup_obj_classes()
         self.setup_obj_classes()
         self.stuff = {}    # FlatCAMObj's by name
         self.stuff = {}    # FlatCAMObj's by name
         self.mouse = None  # Mouse coordinates over plot
         self.mouse = None  # Mouse coordinates over plot
@@ -1229,10 +1236,196 @@ class App:
 
 
         self.info("Project loaded from: " + filename)
         self.info("Project loaded from: " + filename)
 
 
+    def populate_objects_combo(self, combo):
+        """
+        Populates a Gtk.Comboboxtext with the list of the object in the project.
+
+        :param combo: Name or instance of the comboboxtext.
+        :type combo: str or Gtk.ComboBoxText
+        :return: None
+        """
+        print "Populating combo!"
+        if type(combo) == str:
+            combo = self.builder.get_object(combo)
+
+        combo.remove_all()
+        for obj in self.stuff:
+            combo.append_text(obj)
+
     ########################################
     ########################################
     ##         EVENT HANDLERS             ##
     ##         EVENT HANDLERS             ##
     ########################################
     ########################################
+    def on_create_mirror(self, widget):
+        """
+        Creates a mirror image of a Gerber object to be used as a bottom
+        copper layer.
+
+        :param widget: Ignored.
+        :return: None
+        """
+
+        # Layer to mirror
+        gerb_name = self.builder.get_object("comboboxtext_bottomlayer").get_active_text()
+        gerb = self.stuff[gerb_name]
+
+        # For now, lets limit to Gerbers.
+        assert isinstance(gerb, FlatCAMGerber)
+
+        # Mirror axis "X" or "Y
+        axis = self.get_radio_value({"rb_mirror_x": "X",
+                                     "rb_mirror_y": "Y"})
+        mode = self.get_radio_value({"rb_mirror_box": "box",
+                                     "rb_mirror_point": "point"})
+        if mode == "point":  # A single point defines the mirror axis
+            # TODO: Error handling
+            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]
+            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(gerb.solid_geometry, xscale, yscale, origin=(px, py))
+
+        def obj_init(obj_inst, app_inst):
+            obj_inst.solid_geometry = mirrored
+
+        self.new_object("gerber", gerb.options["name"] + "_mirror", obj_init)
+
+    def on_create_aligndrill(self, widget):
+        """
+        Creates alignment holes Excellon object. Creates mirror duplicates
+        of the specified holes around the specified axis.
+
+        :param widget: Ignored.
+        :return: None
+        """
+        # Mirror axis. Same as in on_create_mirror.
+        axis = self.get_radio_value({"rb_mirror_x": "X",
+                                     "rb_mirror_y": "Y"})
+        # TODO: Error handling
+        mode = self.get_radio_value({"rb_mirror_box": "box",
+                                     "rb_mirror_point": "point"})
+        if mode == "point":
+            px, py = eval(self.point_entry.get_text())
+        else:
+            name = self.box_combo.get_active_text()
+            bb_obj = self.stuff[name]
+            xmin, ymin, xmax, ymax = bb_obj.bounds()
+            px = 0.5*(xmin+xmax)
+            py = 0.5*(ymin+ymax)
+        xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
+
+        # Tools
+        tools = {"1": self.get_eval("entry_dblsided_alignholediam")}
+
+        # Parse hole list
+        # TODO: Better parsing
+        holes = self.builder.get_object("entry_dblsided_alignholes").get_text()
+        holes = eval("[" + holes + "]")
+        drills = []
+        for hole in holes:
+            point = Point(hole)
+            point_mirror = affinity.scale(point, xscale, yscale, origin=(px, py))
+            drills.append({"point": point, "tool": "1"})
+            drills.append({"point": point_mirror, "tool": "1"})
+
+        def obj_init(obj_inst, app_inst):
+            obj_inst.tools = tools
+            obj_inst.drills = drills
+            obj_inst.create_geometry()
+
+        self.new_object("excellon", "Alignment Drills", obj_init)
+
+
+    def on_toggle_pointbox(self, widget):
+        """
+        Callback for radio selection change between point and box in the
+        Double-sided PCB tool. Updates the UI accordingly.
+
+        :param widget: Ignored.
+        :return: None
+        """
+
+        # Where the entry or combo go
+        box = self.builder.get_object("box_pointbox")
+
+        # Clear contents
+        children = box.get_children()
+        for child in children:
+            box.remove(child)
+
+        choice = self.get_radio_value({"rb_mirror_point": "point",
+                                       "rb_mirror_box": "box"})
+
+        if choice == "point":
+            self.point_entry = Gtk.Entry()
+            self.builder.get_object("box_pointbox").pack_start(self.point_entry,
+                                                               False, False, 1)
+            self.point_entry.show()
+        else:
+            self.box_combo = Gtk.ComboBoxText()
+            self.builder.get_object("box_pointbox").pack_start(self.box_combo,
+                                                               False, False, 1)
+            self.populate_objects_combo(self.box_combo)
+            self.box_combo.show()
+
+
+    def on_tools_doublesided(self, param):
+        """
+        Callback for menu item Tools->Double Sided PCB Tool. Launches the
+        tool placing its UI in the "Tool" tab in the notebook.
+
+        :param param: Ignored.
+        :return: None
+        """
+
+        # Were are we drawing the UI
+        box_tool = self.builder.get_object("box_tool")
+
+        # Remove anything else in the box
+        box_children = box_tool.get_children()
+        for child in box_children:
+            box_tool.remove(child)
+
+        # Get the UI
+        osw = self.builder.get_object("offscreenwindow_dblsided")
+        sw = self.builder.get_object("sw_dblsided")
+        osw.remove(sw)
+        vp = self.builder.get_object("vp_dblsided")
+        vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
+
+        # Put in the UI
+        box_tool.pack_start(sw, True, True, 0)
+
+        # INITIALIZATION
+        # Populate combo box
+        self.populate_objects_combo("comboboxtext_bottomlayer")
+
+        # Point entry
+        self.point_entry = Gtk.Entry()
+        box = self.builder.get_object("box_pointbox")
+        for child in box.get_children():
+            box.remove(child)
+        box.pack_start(self.point_entry, False, False, 1)
+
+        # Show the "Tool" tab
+        self.notebook.set_current_page(3)
+        sw.show_all()
+
     def on_toggle_units(self, widget):
     def on_toggle_units(self, widget):
+        """
+        Callback for the Units radio-button change in the Options tab.
+        Changes the application's default units or the current project's units.
+        If changing the project's units, the change propagates to all of
+        the objects in the project.
+
+        :param widget: Ignored.
+        :return: None
+        """
         if self.toggle_units_ignore:
         if self.toggle_units_ignore:
             return
             return
 
 
@@ -1574,6 +1767,7 @@ class App:
         :param widget: Ignored.
         :param widget: Ignored.
         :return: None
         :return: None
         """
         """
+        # TODO: Use Gerber.get_bounding_box(...)
         gerber = self.get_current()
         gerber = self.get_current()
         gerber.read_form()
         gerber.read_form()
         name = self.selected_item_name + "_bbox"
         name = self.selected_item_name + "_bbox"
@@ -2094,8 +2288,10 @@ class App:
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.1, "Opening Gerber ..."))
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.1, "Opening Gerber ..."))
 
 
             def obj_init(gerber_obj, app_obj):
             def obj_init(gerber_obj, app_obj):
+                assert isinstance(gerber_obj, FlatCAMGerber)
                 GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
                 GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
                 gerber_obj.parse_file(filename)
                 gerber_obj.parse_file(filename)
+                gerber_obj.create_geometry()
                 GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
                 GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
 
 
             name = filename.split('/')[-1].split('\\')[-1]
             name = filename.split('/')[-1].split('\\')[-1]
@@ -2126,6 +2322,7 @@ class App:
             def obj_init(excellon_obj, app_obj):
             def obj_init(excellon_obj, app_obj):
                 GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
                 GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
                 excellon_obj.parse_file(filename)
                 excellon_obj.parse_file(filename)
+                excellon_obj.create_geometry()
                 GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
                 GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
 
 
             name = filename.split('/')[-1].split('\\')[-1]
             name = filename.split('/')[-1].split('\\')[-1]
@@ -2202,6 +2399,10 @@ class App:
         by the Matplotlib backend and has been registered in ``self.__init__()``.
         by the Matplotlib backend and has been registered in ``self.__init__()``.
         For details, see: http://matplotlib.org/users/event_handling.html
         For details, see: http://matplotlib.org/users/event_handling.html
 
 
+        Default actions are:
+
+        * Copy coordinates to clipboard. Ex.: (65.5473, -13.2679)
+
         :param event: Contains information about the event, like which button
         :param event: Contains information about the event, like which button
             was clicked, the pixel coordinates and the axes coordinates.
             was clicked, the pixel coordinates and the axes coordinates.
         :return: None
         :return: None
@@ -2213,8 +2414,12 @@ class App:
             print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % (
             print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % (
                 event.button, event.x, event.y, event.xdata, event.ydata)
                 event.button, event.x, event.y, event.xdata, event.ydata)
 
 
+            # TODO: This custom subscription mechanism is probably not necessary.
             for subscriber in self.plot_click_subscribers:
             for subscriber in self.plot_click_subscribers:
                 self.plot_click_subscribers[subscriber](event)
                 self.plot_click_subscribers[subscriber](event)
+
+            self.clipboard.set_text("(%.4f, %.4f)" % (event.xdata, event.ydata), -1)
+
         except Exception, e:
         except Exception, e:
             print "Outside plot!"
             print "Outside plot!"
 
 

+ 413 - 0
FlatCAM.ui

@@ -6,6 +6,11 @@
     <property name="can_focus">False</property>
     <property name="can_focus">False</property>
     <property name="stock">gtk-open</property>
     <property name="stock">gtk-open</property>
   </object>
   </object>
+  <object class="GtkImage" id="image10">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-page-setup</property>
+  </object>
   <object class="GtkImage" id="image2">
   <object class="GtkImage" id="image2">
     <property name="visible">True</property>
     <property name="visible">True</property>
     <property name="can_focus">False</property>
     <property name="can_focus">False</property>
@@ -46,6 +51,366 @@
     <property name="can_focus">False</property>
     <property name="can_focus">False</property>
     <property name="stock">gtk-open</property>
     <property name="stock">gtk-open</property>
   </object>
   </object>
+  <object class="GtkOffscreenWindow" id="offscreenwindow_dblsided">
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkScrolledWindow" id="sw_dblsided">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="hscrollbar_policy">never</property>
+        <property name="shadow_type">in</property>
+        <child>
+          <object class="GtkViewport" id="vp_dblsided">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkBox" id="box_dblsided">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_left">5</property>
+                <property name="margin_right">5</property>
+                <property name="margin_top">5</property>
+                <property name="margin_bottom">5</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkLabel" id="label53">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="margin_bottom">6</property>
+                    <property name="ypad">3</property>
+                    <property name="label" translatable="yes">Double-Sided PCB Tool</property>
+                    <attributes>
+                      <attribute name="weight" value="semibold"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkGrid" id="grid7">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="row_spacing">3</property>
+                    <property name="column_spacing">3</property>
+                    <child>
+                      <object class="GtkLabel" id="label84">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">1</property>
+                        <property name="xpad">3</property>
+                        <property name="label" translatable="yes">Bottom Layer:</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">0</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBoxText" id="comboboxtext_bottomlayer">
+                        <property name="width_request">200</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="entry_text_column">0</property>
+                        <property name="id_column">1</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">0</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label85">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">1</property>
+                        <property name="xpad">3</property>
+                        <property name="label" translatable="yes">Mirror Axis:</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">1</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="box24">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="spacing">10</property>
+                        <child>
+                          <object class="GtkRadioButton" id="rb_mirror_x">
+                            <property name="label" translatable="yes">X</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">False</property>
+                            <property name="xalign">0</property>
+                            <property name="active">True</property>
+                            <property name="draw_indicator">True</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkRadioButton" id="rb_mirror_y">
+                            <property name="label" translatable="yes">Y</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">False</property>
+                            <property name="xalign">0</property>
+                            <property name="draw_indicator">True</property>
+                            <property name="group">rb_mirror_x</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">1</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label86">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">1</property>
+                        <property name="xpad">3</property>
+                        <property name="label" translatable="yes">Axis location:</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">2</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="box25">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="spacing">10</property>
+                        <child>
+                          <object class="GtkRadioButton" id="rb_mirror_point">
+                            <property name="label" translatable="yes">Point</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">False</property>
+                            <property name="xalign">0</property>
+                            <property name="active">True</property>
+                            <property name="draw_indicator">True</property>
+                            <signal name="toggled" handler="on_toggle_pointbox" swapped="no"/>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkRadioButton" id="rb_mirror_box">
+                            <property name="label" translatable="yes">Box</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">False</property>
+                            <property name="xalign">0</property>
+                            <property name="draw_indicator">True</property>
+                            <property name="group">rb_mirror_point</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">2</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label87">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">1</property>
+                        <property name="xpad">3</property>
+                        <property name="label" translatable="yes">Point/Box:</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">3</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="box_pointbox">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="orientation">vertical</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">3</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label89">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">1</property>
+                        <property name="xpad">3</property>
+                        <property name="label" translatable="yes">Algnmt holes:</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">4</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="entry_dblsided_alignholes">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="invisible_char">●</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">4</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label90">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">1</property>
+                        <property name="xpad">3</property>
+                        <property name="label" translatable="yes">Drill diam.:</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">5</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="entry_dblsided_alignholediam">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="invisible_char">●</property>
+                        <property name="invisible_char_set">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">5</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="box27">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">end</property>
+                    <property name="margin_top">6</property>
+                    <property name="margin_bottom">3</property>
+                    <child>
+                      <object class="GtkButton" id="button19">
+                        <property name="label" translatable="yes">Create Alignment Drill</property>
+                        <property name="width_request">120</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="halign">end</property>
+                        <signal name="activate" handler="on_create_aligndrill" swapped="no"/>
+                        <signal name="clicked" handler="on_create_aligndrill" swapped="no"/>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="padding">4</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="button18">
+                        <property name="label" translatable="yes">Create Mirror</property>
+                        <property name="width_request">120</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="halign">end</property>
+                        <signal name="activate" handler="on_create_mirror" swapped="no"/>
+                        <signal name="clicked" handler="on_create_mirror" swapped="no"/>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="padding">4</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
   <object class="GtkOffscreenWindow" id="offscrwindow_cncjob">
   <object class="GtkOffscreenWindow" id="offscrwindow_cncjob">
     <property name="can_focus">False</property>
     <property name="can_focus">False</property>
     <child>
     <child>
@@ -2267,6 +2632,30 @@ to application defaults.</property>
                 </child>
                 </child>
               </object>
               </object>
             </child>
             </child>
+            <child>
+              <object class="GtkMenuItem" id="menuitem11">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">_Tools</property>
+                <property name="use_underline">True</property>
+                <child type="submenu">
+                  <object class="GtkMenu" id="menu6">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkImageMenuItem" id="imagemenuitem12">
+                        <property name="label" translatable="yes">Double-Sided PCB Tool</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="image">image10</property>
+                        <property name="use_stock">False</property>
+                        <signal name="activate" handler="on_tools_doublesided" swapped="no"/>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
             <child>
             <child>
               <object class="GtkMenuItem" id="menuitem4">
               <object class="GtkMenuItem" id="menuitem4">
                 <property name="visible">True</property>
                 <property name="visible">True</property>
@@ -3738,6 +4127,30 @@ to application defaults.</property>
                     <property name="tab_fill">False</property>
                     <property name="tab_fill">False</property>
                   </packing>
                   </packing>
                 </child>
                 </child>
+                <child>
+                  <object class="GtkBox" id="box_tool">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">3</property>
+                  </packing>
+                </child>
+                <child type="tab">
+                  <object class="GtkLabel" id="label88">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Tool</property>
+                  </object>
+                  <packing>
+                    <property name="position">3</property>
+                    <property name="tab_fill">False</property>
+                  </packing>
+                </child>
               </object>
               </object>
               <packing>
               <packing>
                 <property name="resize">False</property>
                 <property name="resize">False</property>

+ 88 - 7
camlib.py

@@ -1,3 +1,9 @@
+############################################################
+# Author: Juan Pablo Caram                                 #
+# Date: 2/5/2014                                           #
+# caram.cl                                                 #
+############################################################
+
 from numpy import arctan2, Inf, array, sqrt, pi, ceil, sin, cos
 from numpy import arctan2, Inf, array, sqrt, pi, ceil, sin, cos
 from matplotlib.figure import Figure
 from matplotlib.figure import Figure
 import re
 import re
@@ -16,6 +22,7 @@ from shapely.geometry.base import BaseGeometry
 from descartes.patch import PolygonPatch
 from descartes.patch import PolygonPatch
 
 
 import simplejson as json
 import simplejson as json
+from matplotlib.pyplot import plot
 
 
 class Geometry:
 class Geometry:
     def __init__(self):
     def __init__(self):
@@ -202,9 +209,19 @@ class Gerber (Geometry):
     * ``buffered_paths`` (list): List of (Shapely) polygons resulting from
     * ``buffered_paths`` (list): List of (Shapely) polygons resulting from
       *buffering* (or thickening) the ``paths`` with the aperture. These are
       *buffering* (or thickening) the ``paths`` with the aperture. These are
       generated from ``paths`` in ``buffer_paths()``.
       generated from ``paths`` in ``buffer_paths()``.
+
+    **USAGE**
+
+
     """
     """
 
 
     def __init__(self):
     def __init__(self):
+        """
+        The constructor takes no parameters. Use ``gerber.parse_files()``
+        or ``gerber.parse_lines()`` to populate the object from Gerber source.
+        :return: Gerber object
+        :rtype: Gerber
+        """
         # Initialize parent
         # Initialize parent
         Geometry.__init__(self)        
         Geometry.__init__(self)        
         
         
@@ -457,8 +474,9 @@ class Gerber (Geometry):
         Every stroke (linear or circular) has an aperture which gives
         Every stroke (linear or circular) has an aperture which gives
         it thickness. Additionally, aperture strokes have non-zero area,
         it thickness. Additionally, aperture strokes have non-zero area,
         and regions naturally do as well.
         and regions naturally do as well.
+
         :rtype : None
         :rtype : None
-        @return: None
+        :return: None
         """
         """
         # if len(self.buffered_paths) == 0:
         # if len(self.buffered_paths) == 0:
         #     self.buffer_paths()
         #     self.buffer_paths()
@@ -470,6 +488,25 @@ class Gerber (Geometry):
                                 [poly['polygon'] for poly in self.regions] +
                                 [poly['polygon'] for poly in self.regions] +
                                 self.flash_geometry)
                                 self.flash_geometry)
 
 
+    def get_bounding_box(self, margin=0.0, rounded=False):
+        """
+        Creates and returns a rectangular polygon bounding at a distance of
+        margin from the object's ``solid_geometry``. If margin > 0, the polygon
+        can optionally have rounded corners of radius equal to margin.
+
+        :param margin: Distance to enlarge the rectangular bounding
+        box in both positive and negative, x and y axes.
+        :type margin: float
+        :param rounded: Wether or not to have rounded corners.
+        :type rounded: bool
+        :return: The bounding box.
+        :rtype: Shapely.Polygon
+        """
+        bbox = self.solid_geometry.envelope.buffer(margin)
+        if not rounded:
+            bbox = bbox.envelope
+        return bbox
+
 
 
 class Excellon(Geometry):
 class Excellon(Geometry):
     """
     """
@@ -488,6 +525,11 @@ class Excellon(Geometry):
     ================  ====================================
     ================  ====================================
     """
     """
     def __init__(self):
     def __init__(self):
+        """
+        The constructor takes no parameters.
+        :return: Excellon object.
+        :rtype: Excellon
+        """
         Geometry.__init__(self)
         Geometry.__init__(self)
         
         
         self.tools = {}
         self.tools = {}
@@ -1046,12 +1088,15 @@ def get_bounds(geometry_set):
 
 
     print "Getting bounds of:", str(geometry_set)
     print "Getting bounds of:", str(geometry_set)
     for gs in geometry_set:
     for gs in geometry_set:
-        gxmin, gymin, gxmax, gymax = geometry_set[gs].bounds()
-        xmin = min([xmin, gxmin])
-        ymin = min([ymin, gymin])
-        xmax = max([xmax, gxmax])
-        ymax = max([ymax, gymax])
-            
+        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]
     return [xmin, ymin, xmax, ymax]
 
 
 
 
@@ -1125,6 +1170,7 @@ def find_polygon(poly_set, point):
             return poly
             return poly
     return None
     return None
 
 
+
 def to_dict(geo):
 def to_dict(geo):
     output = ''
     output = ''
     if isinstance(geo, BaseGeometry):
     if isinstance(geo, BaseGeometry):
@@ -1134,6 +1180,7 @@ def to_dict(geo):
         }
         }
     return geo
     return geo
 
 
+
 def dict2obj(d):
 def dict2obj(d):
     if '__class__' in d and '__inst__' in d:
     if '__class__' in d and '__inst__' in d:
         # For now assume all classes are Shapely geometry.
         # For now assume all classes are Shapely geometry.
@@ -1141,6 +1188,40 @@ def dict2obj(d):
     else:
     else:
         return d
         return d
 
 
+
+def plotg(geo):
+    try:
+        _ = iter(geo)
+    except:
+        geo = [geo]
+
+    for g in geo:
+        if type(g) == Polygon:
+            x, y = g.exterior.coords.xy
+            plot(x, y)
+            for ints in g.interiors:
+                x, y = ints.coords.xy
+                plot(x, y)
+            continue
+
+        if type(g) == LineString or type(g) == LinearRing:
+            x, y = g.coords.xy
+            plot(x, y)
+            continue
+
+        if type(g) == Point:
+            x, y = g.coords.xy
+            plot(x, y, 'o')
+            continue
+
+        try:
+            _ = iter(g)
+            plotg(g)
+        except:
+            print "Cannot plot:", str(type(g))
+            continue
+
+
 ############### cam.py ####################
 ############### cam.py ####################
 def coord(gstr, digits, fraction):
 def coord(gstr, digits, fraction):
     """
     """