Explorar o código

Double-sided PCB support.

Juan Pablo Caram %!s(int64=12) %!d(string=hai) anos
pai
achega
6284156a0d
Modificáronse 3 ficheiros con 708 adicións e 9 borrados
  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
 from gi.repository import Gtk, Gdk, GLib, GObject
 import simplejson as json
@@ -255,7 +261,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
     def plot(self, figure):
         FlatCAMObj.plot(self, figure)
 
-        self.create_geometry()
+        #self.create_geometry()
 
         if self.options["mergepolys"]:
             geometry = self.solid_geometry
@@ -334,7 +340,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
     def plot(self, figure):
         FlatCAMObj.plot(self, figure)
         #self.setup_axes(figure)
-        self.create_geometry()
+        #self.create_geometry()
 
         # Plot excellon
         for geo in self.solid_geometry:
@@ -572,6 +578,7 @@ class App:
         self.setup_component_editor()  # The "Selected" tab
 
         #### 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
@@ -1229,10 +1236,196 @@ class App:
 
         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             ##
     ########################################
+    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):
+        """
+        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:
             return
 
@@ -1574,6 +1767,7 @@ class App:
         :param widget: Ignored.
         :return: None
         """
+        # TODO: Use Gerber.get_bounding_box(...)
         gerber = self.get_current()
         gerber.read_form()
         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 ..."))
 
             def obj_init(gerber_obj, app_obj):
+                assert isinstance(gerber_obj, FlatCAMGerber)
                 GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
                 gerber_obj.parse_file(filename)
+                gerber_obj.create_geometry()
                 GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
 
             name = filename.split('/')[-1].split('\\')[-1]
@@ -2126,6 +2322,7 @@ class App:
             def obj_init(excellon_obj, app_obj):
                 GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
                 excellon_obj.parse_file(filename)
+                excellon_obj.create_geometry()
                 GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
 
             name = filename.split('/')[-1].split('\\')[-1]
@@ -2202,6 +2399,10 @@ class App:
         by the Matplotlib backend and has been registered in ``self.__init__()``.
         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
             was clicked, the pixel coordinates and the axes coordinates.
         :return: None
@@ -2213,8 +2414,12 @@ class App:
             print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % (
                 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:
                 self.plot_click_subscribers[subscriber](event)
+
+            self.clipboard.set_text("(%.4f, %.4f)" % (event.xdata, event.ydata), -1)
+
         except Exception, e:
             print "Outside plot!"
 

+ 413 - 0
FlatCAM.ui

@@ -6,6 +6,11 @@
     <property name="can_focus">False</property>
     <property name="stock">gtk-open</property>
   </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">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
@@ -46,6 +51,366 @@
     <property name="can_focus">False</property>
     <property name="stock">gtk-open</property>
   </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">
     <property name="can_focus">False</property>
     <child>
@@ -2267,6 +2632,30 @@ to application defaults.</property>
                 </child>
               </object>
             </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>
               <object class="GtkMenuItem" id="menuitem4">
                 <property name="visible">True</property>
@@ -3738,6 +4127,30 @@ to application defaults.</property>
                     <property name="tab_fill">False</property>
                   </packing>
                 </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>
               <packing>
                 <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 matplotlib.figure import Figure
 import re
@@ -16,6 +22,7 @@ from shapely.geometry.base import BaseGeometry
 from descartes.patch import PolygonPatch
 
 import simplejson as json
+from matplotlib.pyplot import plot
 
 class Geometry:
     def __init__(self):
@@ -202,9 +209,19 @@ class Gerber (Geometry):
     * ``buffered_paths`` (list): List of (Shapely) polygons resulting from
       *buffering* (or thickening) the ``paths`` with the aperture. These are
       generated from ``paths`` in ``buffer_paths()``.
+
+    **USAGE**
+
+
     """
 
     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
         Geometry.__init__(self)        
         
@@ -457,8 +474,9 @@ class Gerber (Geometry):
         Every stroke (linear or circular) has an aperture which gives
         it thickness. Additionally, aperture strokes have non-zero area,
         and regions naturally do as well.
+
         :rtype : None
-        @return: None
+        :return: None
         """
         # if len(self.buffered_paths) == 0:
         #     self.buffer_paths()
@@ -470,6 +488,25 @@ class Gerber (Geometry):
                                 [poly['polygon'] for poly in self.regions] +
                                 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):
     """
@@ -488,6 +525,11 @@ class Excellon(Geometry):
     ================  ====================================
     """
     def __init__(self):
+        """
+        The constructor takes no parameters.
+        :return: Excellon object.
+        :rtype: Excellon
+        """
         Geometry.__init__(self)
         
         self.tools = {}
@@ -1046,12 +1088,15 @@ def get_bounds(geometry_set):
 
     print "Getting bounds of:", str(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]
 
 
@@ -1125,6 +1170,7 @@ def find_polygon(poly_set, point):
             return poly
     return None
 
+
 def to_dict(geo):
     output = ''
     if isinstance(geo, BaseGeometry):
@@ -1134,6 +1180,7 @@ def to_dict(geo):
         }
     return geo
 
+
 def dict2obj(d):
     if '__class__' in d and '__inst__' in d:
         # For now assume all classes are Shapely geometry.
@@ -1141,6 +1188,40 @@ def dict2obj(d):
     else:
         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 ####################
 def coord(gstr, digits, fraction):
     """