Double-sided PCB support.

This commit is contained in:
Juan Pablo Caram 2014-02-05 19:36:47 -05:00
parent e930de1793
commit 6284156a0d
3 changed files with 708 additions and 9 deletions

View File

@ -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!"

View File

@ -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>

View File

@ -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() try:
xmin = min([xmin, gxmin]) gxmin, gymin, gxmax, gymax = geometry_set[gs].bounds()
ymin = min([ymin, gymin]) xmin = min([xmin, gxmin])
xmax = max([xmax, gxmax]) ymin = min([ymin, gymin])
ymax = max([ymax, gymax]) 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):
""" """