diff --git a/camlib.py b/camlib.py
index 442c9d6..f6f8e3c 100644
--- a/camlib.py
+++ b/camlib.py
@@ -1,4 +1,4 @@
-import cairo
+#import cairo
#from string import *
#from math import *
@@ -11,7 +11,7 @@ from numpy import arctan2, Inf, array, sqrt, pi, ceil, sin, cos
from matplotlib.figure import Figure
# See: http://toblerity.org/shapely/manual.html
-from shapely.geometry import Polygon, LineString, Point
+from shapely.geometry import Polygon, LineString, Point, LinearRing
from shapely.geometry import MultiPoint, MultiPolygon
from shapely.geometry import box as shply_box
from shapely.ops import cascaded_union
@@ -41,7 +41,11 @@ class Geometry:
if self.solid_geometry == None:
print "Warning: solid_geometry not computed yet."
return (0,0,0,0)
- return self.solid_geometry.bounds
+
+ if type(self.solid_geometry) == list:
+ return cascaded_union(self.solid_geometry).bounds
+ else:
+ return self.solid_geometry.bounds
def size(self):
'''
@@ -62,7 +66,7 @@ class Geometry:
'''
if boundary == None:
boundary = self.solid_geometry.envelope
- return boundary.difference(g.solid_geometry)
+ return boundary.difference(self.solid_geometry)
def clear_polygon(self, polygon, tooldia, overlap = 0.15):
'''
@@ -340,9 +344,10 @@ class Excellon(Geometry):
self.solid_geometry.append(poly)
self.solid_geometry = cascaded_union(self.solid_geometry)
-class CNCjob:
+class CNCjob(Geometry):
def __init__(self, units="in", kind="generic", z_move = 0.1,
feedrate = 3.0, z_cut = -0.002):
+
# Options
self.kind = kind
self.units = units
@@ -366,7 +371,8 @@ class CNCjob:
self.tooldia = 0
# Output generated by CNCjob.create_gcode_geometry()
- self.G_geometry = None
+ #self.G_geometry = None
+ self.gcode_parsed = None
def generate_from_excellon(self, exobj):
'''
@@ -447,7 +453,7 @@ class CNCjob:
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
continue
- if type(geo) == LineString or type(geo) == LineRing:
+ if type(geo) == LineString or type(geo) == LinearRing:
path = list(geo.coords)
self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
@@ -469,7 +475,7 @@ class CNCjob:
self.gcode += "G00 X0Y0\n"
self.gcode += "M05\n" # Spindle stop
- def create_gcode_geometry(self):
+ def gcode_parse(self):
steps_per_circ = 20
'''
G-Code parser (from self.gcode). Generates dictionary with
@@ -534,7 +540,8 @@ class CNCjob:
for code in gobj:
current[code] = gobj[code]
- self.G_geometry = geometry
+ #self.G_geometry = geometry
+ self.gcode_parsed = geometry
return geometry
def plot(self, tooldia=None, dpi=75, margin=0.1,
@@ -555,7 +562,7 @@ class CNCjob:
ax.set_ylim(ymin-margin, ymax+margin)
if tooldia == 0:
- for geo in self.G_geometry:
+ for geo in self.gcode_parsed:
linespec = '--'
linecolor = color[geo['kind'][0]][1]
if geo['kind'][0] == 'C':
@@ -563,7 +570,7 @@ class CNCjob:
x, y = geo['geom'].coords.xy
ax.plot(x, y, linespec, color=linecolor)
else:
- for geo in self.G_geometry:
+ for geo in self.gcode_parsed:
poly = geo['geom'].buffer(tooldia/2.0)
patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0],
edgecolor=color[geo['kind'][0]][1],
@@ -576,20 +583,13 @@ class CNCjob:
color={"T":["#F0E24D", "#B5AB3A"], "C":["#5E6CFF", "#4650BD"]},
alpha={"T":0.3, "C":1.0}):
'''
- Plots the G-code job onto the given axes
+ Plots the G-code job onto the given axes.
'''
if tooldia == None:
tooldia = self.tooldia
-
- #fig = Figure(dpi=dpi)
- #ax = fig.add_subplot(111)
- #ax.set_aspect(1)
- #xmin, ymin, xmax, ymax = self.input_geometry_bounds
- #ax.set_xlim(xmin-margin, xmax+margin)
- #ax.set_ylim(ymin-margin, ymax+margin)
if tooldia == 0:
- for geo in self.G_geometry:
+ for geo in self.gcode_parsed:
linespec = '--'
linecolor = color[geo['kind'][0]][1]
if geo['kind'][0] == 'C':
@@ -597,13 +597,17 @@ class CNCjob:
x, y = geo['geom'].coords.xy
axes.plot(x, y, linespec, color=linecolor)
else:
- for geo in self.G_geometry:
+ for geo in self.gcode_parsed:
poly = geo['geom'].buffer(tooldia/2.0)
patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0],
edgecolor=color[geo['kind'][0]][1],
alpha=alpha[geo['kind'][0]], zorder=2)
axes.add_patch(patch)
+ def create_geometry(self):
+ self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed])
+
+
def gparse1b(gtext):
'''
@@ -650,21 +654,18 @@ def gparse1b(gtext):
gcmds.append(cmds)
return gcmds
-def get_bounds(geometry_sets):
+def get_bounds(geometry_set):
xmin = Inf
ymin = Inf
xmax = -Inf
ymax = -Inf
- #geometry_sets = [self.gerbers, self.excellons]
-
- for gs in geometry_sets:
- for g in gs:
- gxmin, gymin, gxmax, gymax = g.solid_geometry.bounds
- xmin = min([xmin, gxmin])
- ymin = min([ymin, gymin])
- xmax = max([xmax, gxmax])
- ymax = max([ymax, gymax])
+ 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])
return [xmin, ymin, xmax, ymax]
diff --git a/camlib.pyc b/camlib.pyc
index c02cf4a..c70f7ce 100644
Binary files a/camlib.pyc and b/camlib.pyc differ
diff --git a/cirkuix.py b/cirkuix.py
index b2f4d85..236eeeb 100644
--- a/cirkuix.py
+++ b/cirkuix.py
@@ -13,18 +13,92 @@ from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCan
from camlib import *
+class CirkuixObj:
+ def __init__(self, name, kind):
+ self.name = name
+ self.kind = kind
+
+class CirkuixGerber(CirkuixObj, Gerber):
+ def __init__(self, name):
+ Gerber.__init__(self)
+ CirkuixObj.__init__(self, name, "gerber")
+ self.fields = [{"name":"plot",
+ "type":bool,
+ "value":True,
+ "get":None,
+ "set":None,
+ "onchange":None},
+ {}]
+
+class CirkuixExcellon(CirkuixObj, Excellon):
+ def __init__(self, name):
+ Excellon.__init__(self)
+ CirkuixObj.__init__(self, name, "excellon")
+ self.options = {"plot": True,
+ "solid": False,
+ "multicolored": False}
+
+class CirkuixCNCjob(CirkuixObj, CNCjob):
+ def __init__(self, name):
+ CNCjob.__init__(self)
+ CirkuixObj.__init__(self, name, "cncjob")
+ self.options = {"plot": True}
+
+class CirkuixGeometry(CirkuixObj, Geometry):
+ def __init__(self, name):
+ CirkuixObj.__init__(self, name, "geometry")
+ self.options = {"plot": True,
+ "solid": False,
+ "multicolored": False}
+
+class CirkuixObjForm:
+ def __init__(self, container, Cobj):
+ self.Cobj = Cobj
+ self.container = container
+ self.fields = {}
+
+ def populate(self):
+ return
+
+ def save(self):
+ return
+
+#class CirkuixGerberForm(CirkuixObjForm)
+
+
+def get_entry_text(entry):
+ return entry.get_text()
+
+def get_entry_int(entry):
+ return int(entry.get_text())
+
+def get_entry_float(entry):
+ return float(entry.get_text())
+
+def get_entry_eval(entry):
+ return eval(entry.get_text)
+
+getters = {"entry_text":get_entry_text,
+ "entry_int":get_entry_int,
+ "entry_float":get_entry_float,
+ "entry_eval":get_entry_eval}
+
+setters = {"entry"}
+
class App:
def __init__(self):
########################################
## GUI ##
- ########################################
+ ########################################
self.gladefile = "cirkuix.ui"
self.builder = Gtk.Builder()
self.builder.add_from_file(self.gladefile)
self.window = self.builder.get_object("window1")
+ self.window.set_title("Cirkuix")
self.positionLabel = self.builder.get_object("label3")
self.grid = self.builder.get_object("grid1")
+ self.notebook = self.builder.get_object("notebook1")
## Event handling ##
self.builder.connect_signals(self)
@@ -33,34 +107,36 @@ class App:
self.figure = None
self.axes = None
self.canvas = None
- self.mplpaint()
+ self.plot_setup()
+ self.setup_component_viewer()
+ self.setup_component_editor()
########################################
## DATA ##
########################################
- self.gerbers = []
- self.excellons = []
- self.cncjobs = []
+ self.stuff = {} # CirkuixObj's by name
self.mouse = None
+ # What is selected by the user. It is
+ # a key if self.stuff
+ self.selected_item_name = None
+
########################################
## START ##
########################################
self.window.show_all()
Gtk.main()
- def mplpaint(self):
+ def plot_setup(self):
self.figure = Figure(dpi=50)
- #self.axes = self.figure.add_subplot(111)
self.axes = self.figure.add_axes([0.05,0.05,0.9,0.9])
self.axes.set_aspect(1)
#t = arange(0.0,5.0,0.01)
#s = sin(2*pi*t)
#self.axes.plot(t,s)
self.axes.grid()
- #a.patch.set_visible(False) Background of the axes
self.figure.patch.set_visible(False)
self.canvas = FigureCanvas(self.figure) # a Gtk.DrawingArea
@@ -76,11 +152,13 @@ class App:
self.canvas.mpl_connect('key_press_event', self.on_key_over_plot)
self.canvas.mpl_connect('scroll_event', self.on_scroll_over_plot)
- #self.builder.get_object("viewport2").add(self.canvas)
self.grid.attach(self.canvas,0,0,600,400)
- #self.builder.get_object("scrolledwindow1").add(self.canvas)
def zoom(self, factor, center=None):
+ '''
+ Zooms the plot by factor around a given
+ center point. Takes care of re-drawing.
+ '''
xmin, xmax = self.axes.get_xlim()
ymin, ymax = self.axes.get_ylim()
width = xmax-xmin
@@ -129,6 +207,8 @@ class App:
for ints in poly.interiors:
x, y = ints.coords.xy
self.axes.plot(x, y, linespec)
+
+ self.canvas.queue_draw()
def plot_excellon(self, excellon):
excellon.create_geometry()
@@ -140,31 +220,152 @@ class App:
for ints in geo.interiors:
x, y = ints.coords.xy
self.axes.plot(x, y, 'g-')
+
+ self.canvas.queue_draw()
def plot_cncjob(self, job):
- job.create_gcode_geometry()
+ #job.gcode_parse()
tooldia_text = self.builder.get_object("entry_tooldia").get_text()
tooldia_val = eval(tooldia_text)
job.plot2(self.axes, tooldia=tooldia_val)
- return
-
- def file_chooser_action(self, on_success):
- dialog = Gtk.FileChooserDialog("Please choose a file", self.window,
- Gtk.FileChooserAction.OPEN,
- (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
- response = dialog.run()
- if response == Gtk.ResponseType.OK:
- on_success(self, dialog.get_filename())
- elif response == Gtk.ResponseType.CANCEL:
- print("Cancel clicked")
- dialog.destroy()
+ self.canvas.queue_draw()
+ def setup_component_viewer(self):
+ '''
+ List or Tree where whatever has been loaded or created is
+ displayed.
+ '''
+ self.store = Gtk.ListStore(str)
+ self.tree = Gtk.TreeView(self.store)
+ select = self.tree.get_selection()
+ self.signal_id = select.connect("changed", self.on_tree_selection_changed)
+ renderer = Gtk.CellRendererText()
+ column = Gtk.TreeViewColumn("Title", renderer, text=0)
+ self.tree.append_column(column)
+ self.builder.get_object("notebook1").append_page(self.tree, Gtk.Label("Project"))
+
+ def setup_component_editor(self):
+ box1 = Gtk.Box(Gtk.Orientation.VERTICAL)
+ label1 = Gtk.Label("Choose an item from Project")
+ box1.pack_start(label1, False, False, 1)
+ self.builder.get_object("notebook1").append_page(box1, Gtk.Label("Selection"))
+
+ def build_list(self):
+ self.store.clear()
+ for key in self.stuff:
+ self.store.append([key])
+
+ def build_gerber_ui(self):
+ print "build_gerber_ui()"
+ osw = self.builder.get_object("offscrwindow_gerber")
+ box1 = self.builder.get_object("box_gerber")
+ osw.remove(box1)
+ self.notebook.append_page(box1, Gtk.Label("Selection"))
+ gerber = self.stuff[self.selected_item_name]
+ entry_name = self.builder.get_object("entry_gerbername")
+ entry_name.set_text(self.selected_item_name)
+ entry_name.connect("activate", self.on_activate_name)
+ box1.show()
+
+ def build_excellon_ui(self):
+ print "build_excellon_ui()"
+ osw = self.builder.get_object("offscrwindow_excellon")
+ box1 = self.builder.get_object("box_excellon")
+ osw.remove(box1)
+ self.notebook.append_page(box1, Gtk.Label("Selection"))
+ entry_name = self.builder.get_object("entry_excellonname")
+ entry_name.set_text(self.selected_item_name)
+ entry_name.connect("activate", self.on_activate_name)
+ box1.show()
+
+ def build_cncjob_ui(self):
+ print "build_cncjob_ui()"
+ osw = self.builder.get_object("offscrwindow_cncjob")
+ box1 = self.builder.get_object("box_cncjob")
+ osw.remove(box1)
+ self.notebook.append_page(box1, Gtk.Label("Selection"))
+ entry_name = self.builder.get_object("entry_cncjobname")
+ entry_name.set_text(self.selected_item_name)
+ entry_name.connect("activate", self.on_activate_name)
+ box1.show()
+
+ def plot_all(self):
+ self.clear_plots()
+ plotter = {"gerber":self.plot_gerber,
+ "excellon":self.plot_excellon,
+ "cncjob":self.plot_cncjob}
+
+ for i in self.stuff:
+ kind = self.stuff[i].kind
+ plotter[kind](self.stuff[i])
+
+ self.on_zoom_fit(None)
+ self.axes.grid()
+ self.canvas.queue_draw()
+
+ def clear_plots(self):
+ self.axes.cla()
+ self.canvas.queue_draw()
+
########################################
## EVENT HANDLERS ##
########################################
-
+ def on_delete(self, widget):
+ self.stuff.pop(self.selected_item_name)
+
+ #self.tree.get_selection().disconnect(self.signal_id)
+ self.build_list() # Update the items list
+ #self.signal_id = self.tree.get_selection().connect(
+ # "changed", self.on_tree_selection_changed)
+
+ self.plot_all()
+ #self.notebook.set_current_page(1)
+
+ def on_replot(self, widget):
+ self.plot_all()
+
+ def on_clear_plots(self, widget):
+ self.clear_plots()
+
+ def on_activate_name(self, entry):
+ '''
+ Hitting 'Enter' after changing the name of an item
+ updates the item dictionary and re-builds the item list.
+ '''
+ print "Changing name"
+ self.tree.get_selection().disconnect(self.signal_id)
+ new_name = entry.get_text() # Get from form
+ self.stuff[new_name] = self.stuff.pop(self.selected_item_name) # Update dictionary
+ self.selected_item_name = new_name # Update selection name
+ self.build_list() # Update the items list
+ self.signal_id = self.tree.get_selection().connect(
+ "changed", self.on_tree_selection_changed)
+
+ def on_tree_selection_changed(self, selection):
+ model, treeiter = selection.get_selected()
+
+
+ if treeiter != None:
+ print "You selected", model[treeiter][0]
+ else:
+ return # TODO: Clear "Selected" page
+
+ self.selected_item_name = model[treeiter][0]
+ # Remove the current selection page
+ # from the notebook
+ # TODO: Assuming it was last page or #2. Find the right page
+ self.builder.get_object("notebook1").remove_page(2)
+
+ # Determine the kind of item selected
+ kind = self.stuff[model[treeiter][0]].kind
+
+ # Build the UI
+ builder = {"gerber": self.build_gerber_ui,
+ "excellon": self.build_excellon_ui,
+ "cncjob": self.build_cncjob_ui}
+ builder[kind]()
+
def on_filequit(self, param):
print "quit from menu"
self.window.destroy()
@@ -175,31 +376,61 @@ class App:
self.window.destroy()
Gtk.main_quit()
+ def file_chooser_action(self, on_success):
+ '''
+ Opens the file chooser and runs on_success
+ upon completion of valid file choice.
+ '''
+ dialog = Gtk.FileChooserDialog("Please choose a file", self.window,
+ Gtk.FileChooserAction.OPEN,
+ (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
+ Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
+ response = dialog.run()
+ if response == Gtk.ResponseType.OK:
+ on_success(self, dialog.get_filename())
+ elif response == Gtk.ResponseType.CANCEL:
+ print("Cancel clicked")
+ dialog.destroy()
+
def on_fileopengerber(self, param):
def on_success(self, filename):
- gerber = Gerber()
+ name = filename.split('/')[-1].split('\\')[-1]
+ gerber = CirkuixGerber(name)
gerber.parse_file(filename)
- self.gerbers.append(gerber)
+ self.store.append([name])
+ #self.gerbers.append(gerber)
+ self.stuff[name] = gerber
self.plot_gerber(gerber)
+ self.on_zoom_fit(None)
self.file_chooser_action(on_success)
def on_fileopenexcellon(self, param):
def on_success(self, filename):
- excellon = Excellon()
+ name = filename.split('/')[-1].split('\\')[-1]
+ excellon = CirkuixExcellon(name)
excellon.parse_file(filename)
- self.excellons.append(excellon)
+ self.store.append([name])
+ #self.excellons.append(excellon)
+ self.stuff[name] = excellon
self.plot_excellon(excellon)
+ self.on_zoom_fit(None)
self.file_chooser_action(on_success)
def on_fileopengcode(self, param):
def on_success(self, filename):
+ name = filename.split('/')[-1].split('\\')[-1]
f = open(filename)
gcode = f.read()
f.close()
- job = CNCjob()
+ job = CirkuixCNCjob(name)
job.gcode = gcode
- self.cncjobs.append(job)
+ job.gcode_parse()
+ job.create_geometry()
+ self.store.append([name])
+ #self.cncjobs.append(job)
+ self.stuff[name] = job
self.plot_cncjob(job)
+ self.on_zoom_fit(None)
self.file_chooser_action(on_success)
def on_mouse_move_over_plot(self, event):
@@ -226,7 +457,7 @@ class App:
self.zoom(1/1.5)
def on_zoom_fit(self, event):
- xmin, ymin, xmax, ymax = get_bounds([self.gerbers, self.excellons])
+ xmin, ymin, xmax, ymax = get_bounds(self.stuff)
width = xmax-xmin
height = ymax-ymin
self.axes.set_xlim((xmin-0.05*width, xmax+0.05*width))
diff --git a/cirkuix.pyc b/cirkuix.pyc
new file mode 100644
index 0000000..3732da8
Binary files /dev/null and b/cirkuix.pyc differ
diff --git a/cirkuix.ui b/cirkuix.ui
index 7554388..6ed2297 100644
--- a/cirkuix.ui
+++ b/cirkuix.ui
@@ -16,6 +16,607 @@
False
gtk-open
+
+
+ False
+
+
+ True
+ False
+ 5
+ 5
+ 5
+ 5
+ vertical
+
+
+ True
+ False
+ 3
+ Excellon Object
+
+
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+
+
+ True
+ False
+ 3
+ Name:
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ True
+ ●
+ True
+
+
+ False
+ True
+ 1
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ True
+ False
+ 0
+ 3
+ Plot Options:
+
+
+
+
+
+ False
+ True
+ 2
+
+
+
+
+ Plot
+ True
+ True
+ False
+ 0
+ True
+ True
+
+
+ False
+ True
+ 3
+
+
+
+
+ Solid
+ True
+ True
+ False
+ 0
+ True
+
+
+ False
+ True
+ 4
+
+
+
+
+ Multi-colored
+ True
+ True
+ False
+ 0
+ True
+
+
+ False
+ True
+ 5
+
+
+
+
+ Refresh Plot
+ True
+ True
+ True
+
+
+ False
+ True
+ 6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+
+
+ True
+ False
+ 5
+ 5
+ 5
+ 5
+ vertical
+
+
+ True
+ False
+ 3
+ Gerber Object
+
+
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+
+
+ True
+ False
+ 3
+ Name:
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ True
+ ●
+
+
+ False
+ True
+ 1
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ True
+ False
+ 0
+ 3
+ Plot Options:
+
+
+
+
+
+ False
+ True
+ 2
+
+
+
+
+ Plot
+ True
+ True
+ False
+ 0
+ True
+ True
+
+
+ False
+ True
+ 3
+
+
+
+
+ Merge Polygons
+ True
+ True
+ False
+ 0
+ True
+ True
+
+
+ False
+ True
+ 4
+
+
+
+
+ Solid
+ True
+ True
+ False
+ 0
+ True
+
+
+ False
+ True
+ 5
+
+
+
+
+ Multi-colored
+ True
+ True
+ False
+ 0
+ True
+
+
+ False
+ True
+ 6
+
+
+
+
+ Refresh Plot
+ True
+ True
+ True
+
+
+ False
+ True
+ 7
+
+
+
+
+ True
+ False
+ 0
+ 3
+ Isolation Routing:
+
+
+
+
+
+ False
+ True
+ 8
+
+
+
+
+ True
+ False
+ 2
+ 2
+
+
+ True
+ False
+ 1
+ 3
+ Tool diam:
+
+
+ 0
+ 0
+ 1
+ 1
+
+
+
+
+ True
+ True
+ ●
+ True
+
+
+ 1
+ 0
+ 1
+ 1
+
+
+
+
+ True
+ False
+ 1
+ 3
+ Cut X:
+
+
+ 0
+ 1
+ 1
+ 1
+
+
+
+
+ True
+ True
+ ●
+ True
+
+
+ 1
+ 1
+ 1
+ 1
+
+
+
+
+ True
+ False
+ 1
+ 3
+ Travel X:
+
+
+ 0
+ 2
+ 1
+ 1
+
+
+
+
+ True
+ True
+ ●
+ True
+
+
+ 1
+ 2
+ 1
+ 1
+
+
+
+
+ False
+ True
+ 9
+
+
+
+
+ Generate Geometry
+ True
+ True
+ True
+
+
+ False
+ True
+ 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
600
400
@@ -237,6 +838,48 @@
True
+
+
+ True
+ False
+ Clear Plots
+ True
+ gtk-stop
+
+
+
+ False
+ True
+
+
+
+
+ True
+ False
+ Re-plot
+ True
+ gtk-redo
+
+
+
+ False
+ True
+
+
+
+
+ True
+ False
+ Delete Object
+ True
+ gtk-delete
+
+
+
+ False
+ True
+
+
False