diff --git a/camlib.py b/camlib.py
index b8740b5b..1ec0dd9a 100644
--- a/camlib.py
+++ b/camlib.py
@@ -8,10 +8,14 @@ from shapely.geometry import MultiPoint, MultiPolygon
from shapely.geometry import box as shply_box
from shapely.ops import cascaded_union
import shapely.affinity as affinity
+from shapely.wkt import loads as sloads
+from shapely.wkt import dumps as sdumps
+from shapely.geometry.base import BaseGeometry
# Used for solid polygons in Matplotlib
from descartes.patch import PolygonPatch
+import simplejson as json
class Geometry:
def __init__(self):
@@ -20,6 +24,9 @@ class Geometry:
# Final geometry: MultiPolygon
self.solid_geometry = None
+
+ # Attributes to be included in serialization
+ self.ser_attrs = ['units', 'solid_geometry']
def isolation_geometry(self, offset):
"""
@@ -113,7 +120,25 @@ class Geometry:
self.units = units
self.scale(factor)
return factor
-
+
+ def to_dict(self):
+ """
+ Returns a respresentation of the object as a dictionary.
+ ``self.solid_geometry`` has been converted using the ``to_dict``
+ function. Attributes to include are listed in
+ ``self.ser_attrs``.
+
+ :return: A dictionary-encoded copy of the object.
+ :rtype: dict
+ """
+ d = {}
+ for attr in self.ser_attrs:
+ d[attr] = getattr(self, attr)
+ return d
+
+ def from_dict(self, d):
+ return
+
class Gerber (Geometry):
"""
@@ -207,6 +232,13 @@ class Gerber (Geometry):
# Geometry from flashes
self.flash_geometry = []
+ # Attributes to be included in serialization
+ # Always append to it because it carries contents
+ # from Geometry.
+ self.ser_attrs += ['digits', 'fraction', 'apertures', 'paths',
+ 'buffered_paths', 'regions', 'flashes',
+ 'flash_geometry']
+
def scale(self, factor):
"""
Scales the objects' geometry on the XY plane by a given factor.
@@ -458,6 +490,11 @@ class Excellon(Geometry):
# Trailing "T" or leading "L"
self.zeros = ""
+
+ # Attributes to be included in serialization
+ # Always append to it because it carries contents
+ # from Geometry.
+ self.ser_attrs += ['tools', 'drills', 'zeros']
def parse_file(self, filename):
efile = open(filename, 'r')
@@ -592,6 +629,13 @@ class CNCjob(Geometry):
self.input_geometry_bounds = None
self.gcode_parsed = None
self.steps_per_circ = 20 # Used when parsing G-code arcs
+
+ # Attributes to be included in serialization
+ # Always append to it because it carries contents
+ # from Geometry.
+ self.ser_attrs += ['kind', 'z_cut', 'z_move', 'feedrate', 'tooldia',
+ 'gcode', 'input_geometry_bounds', 'gcode_parsed',
+ 'steps_per_circ']
def generate_from_excellon(self, exobj):
"""
@@ -949,6 +993,7 @@ class CNCjob(Geometry):
return gcode
def point2gcode(self, point):
+ # TODO: This is not doing anything.
gcode = ""
t = "G0%d X%.4fY%.4f\n"
path = list(point.coords)
@@ -1067,6 +1112,22 @@ def find_polygon(poly_set, point):
return poly
return None
+def to_dict(geo):
+ output = ''
+ if isinstance(geo, BaseGeometry):
+ return {
+ "__class__": "Shply",
+ "__inst__": sdumps(geo)
+ }
+ return geo
+
+def dict2obj(d):
+ if '__class__' in d and '__inst__' in d:
+ # For now assume all classes are Shapely geometry.
+ return sloads(d['__inst__'])
+ else:
+ return d
+
############### cam.py ####################
def coord(gstr, digits, fraction):
"""
diff --git a/cirkuix.py b/cirkuix.py
index c6914116..d5722cb4 100644
--- a/cirkuix.py
+++ b/cirkuix.py
@@ -39,9 +39,11 @@ class CirkuixObj:
1) Creates axes if they don't exist. 2) Clears axes. 3) Attaches
them to figure if not part of the figure. 4) Sets transparent
background. 5) Sets 1:1 scale aspect ratio.
- @param figure: A Matplotlib.Figure on which to add/configure axes.
- @type figure: matplotlib.figure.Figure
- @return: None
+
+ :param figure: A Matplotlib.Figure on which to add/configure axes.
+ :type figure: matplotlib.figure.Figure
+ :return: None
+ :rtype: None
"""
if self.axes is None:
print "New axes"
@@ -63,19 +65,21 @@ class CirkuixObj:
self.axes.patch.set_visible(False) # No background
self.axes.set_aspect(1)
- def set_options(self, options):
- for name in options:
- self.options[name] = options[name]
- return
-
def to_form(self):
+ """
+ Copies options to the UI form.
+
+ :return: None
+ """
for option in self.options:
self.set_form_item(option)
def read_form(self):
"""
- Reads form into self.options
- @rtype : None
+ Reads form into ``self.options``.
+
+ :return: None
+ :rtype : None
"""
for option in self.options:
self.read_form_item(option)
@@ -83,8 +87,9 @@ class CirkuixObj:
def build_ui(self):
"""
Sets up the UI/form for this object.
- @return: None
- @rtype : None
+
+ :return: None
+ :rtype : None
"""
# Where the UI for this object is drawn
@@ -110,6 +115,13 @@ class CirkuixObj:
sw.show()
def set_form_item(self, option):
+ """
+ Copies the specified options to the UI form.
+
+ :param option: Name of the option (Key in ``self.options``).
+ :type option: str
+ :return: None
+ """
fkind = self.form_kinds[option]
fname = fkind + "_" + self.kind + "_" + option
@@ -217,6 +229,11 @@ class CirkuixGerber(CirkuixObj, Gerber):
self.radios = {"gaps": {"rb_2tb": "tb", "rb_2lr": "lr", "rb_4": "4"}}
self.radios_inv = {"gaps": {"tb": "rb_2tb", "lr": "rb_2lr", "4": "rb_4"}}
+ # Attributes to be included in serialization
+ # Always append to it because it carries contents
+ # from predecessors.
+ self.ser_attrs += ['options']
+
def convert_units(self, units):
factor = Gerber.convert_units(self, units)
@@ -290,8 +307,14 @@ class CirkuixExcellon(CirkuixObj, Excellon):
"toolselection": "entry_text"
})
+ # TODO: Document this.
self.tool_cbs = {}
+ # Attributes to be included in serialization
+ # Always append to it because it carries contents
+ # from predecessors.
+ self.ser_attrs += ['options']
+
def convert_units(self, units):
factor = Excellon.convert_units(self, units)
@@ -339,10 +362,6 @@ class CirkuixExcellon(CirkuixObj, Excellon):
button.connect("activate", on_accept)
button.connect("clicked", on_accept)
- # def parse_lines(self, elines):
- # Excellon.parse_lines(self, elines)
- # self.options["units"] = self.units
-
class CirkuixCNCjob(CirkuixObj, CNCjob):
"""
@@ -371,6 +390,11 @@ class CirkuixCNCjob(CirkuixObj, CNCjob):
"tooldia": "entry_eval"
})
+ # Attributes to be included in serialization
+ # Always append to it because it carries contents
+ # from predecessors.
+ self.ser_attrs += ['options']
+
def plot(self, figure):
CirkuixObj.plot(self, figure)
#self.setup_axes(figure)
@@ -417,6 +441,11 @@ class CirkuixGeometry(CirkuixObj, Geometry):
"paintmargin": "entry_eval"
})
+ # Attributes to be included in serialization
+ # Always append to it because it carries contents
+ # from predecessors.
+ self.ser_attrs += ['options']
+
def scale(self, factor):
if type(self.solid_geometry) == list:
self.solid_geometry = [affinity.scale(g, factor, factor, origin=(0, 0))
@@ -510,7 +539,8 @@ class App:
self.units_label = self.builder.get_object("label_units")
# White (transparent) background on the "Options" tab.
- self.builder.get_object("vp_options").override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
+ self.builder.get_object("vp_options").override_background_color(Gtk.StateType.NORMAL,
+ Gdk.RGBA(1, 1, 1, 1))
# Combo box to choose between project and application options.
self.combo_options = self.builder.get_object("combo_options")
@@ -1100,9 +1130,51 @@ class App:
return
print "Unknown kind of form item:", fkind
+ def save_project(self, filename):
+ """
+ Saves the current project to the specified file.
+ :param filename: Name of the file in which to save.
+ :type filename: str
+ :return: None
+ """
+
+ d = {"objs": [self.stuff[o].to_dict() for o in self.stuff],
+ "options": self.options}
+
+ try:
+ f = open(filename, 'w')
+ except:
+ print "ERROR: Failed to open file for saving:", filename
+ return
+
+ try:
+ json.dump(d, f, default=to_dict)
+ except:
+ print "ERROR: File open but failed to write:", filename
+ f.close()
+ return
+
+ f.close()
+
+
########################################
## EVENT HANDLERS ##
########################################
+ def on_file_saveproject(self, param):
+ return
+
+ def on_file_saveprojectas(self, param):
+ def on_success(app_obj, filename):
+ assert isinstance(app_obj, App)
+ app_obj.save_project(filename)
+ app_obj.info("Project saved to: " + filename)
+
+ self.file_chooser_save_action(on_success)
+ return
+
+ def on_file_saveprojectcopy(self, param):
+ return
+
def on_options_app2project(self, param):
"""
Callback for Options->Transfer Options->App=>Project. Copies options
diff --git a/cirkuix.ui b/cirkuix.ui
index cd2651a2..b11a3920 100644
--- a/cirkuix.ui
+++ b/cirkuix.ui
@@ -26,6 +26,21 @@
False
gtk-jump-to
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+