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