Explorar el Código

Object serialization and project saving.

Juan Pablo Caram hace 12 años
padre
commit
deac0a7ef6
Se han modificado 4 ficheros con 203 adiciones y 19 borrados
  1. 62 1
      camlib.py
  2. 89 17
      cirkuix.py
  3. 51 0
      cirkuix.ui
  4. 1 1
      defaults.json

+ 62 - 1
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):
     """

+ 89 - 17
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

+ 51 - 0
cirkuix.ui

@@ -26,6 +26,21 @@
     <property name="can_focus">False</property>
     <property name="stock">gtk-jump-to</property>
   </object>
+  <object class="GtkImage" id="image6">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-save</property>
+  </object>
+  <object class="GtkImage" id="image7">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-save-as</property>
+  </object>
+  <object class="GtkImage" id="image8">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-save-as</property>
+  </object>
   <object class="GtkOffscreenWindow" id="offscrwindow_cncjob">
     <property name="can_focus">False</property>
     <child>
@@ -2063,6 +2078,42 @@
                         <property name="can_focus">False</property>
                       </object>
                     </child>
+                    <child>
+                      <object class="GtkImageMenuItem" id="imagemenuitem6">
+                        <property name="label" translatable="yes">Save Project</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="image">image6</property>
+                        <property name="use_stock">False</property>
+                        <signal name="activate" handler="on_file_saveproject" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkImageMenuItem" id="imagemenuitem7">
+                        <property name="label" translatable="yes">Save Project As ...</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="image">image7</property>
+                        <property name="use_stock">False</property>
+                        <signal name="activate" handler="on_file_saveprojectas" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkImageMenuItem" id="imagemenuitem8">
+                        <property name="label" translatable="yes">Save a Project copy ...</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="image">image8</property>
+                        <property name="use_stock">False</property>
+                        <signal name="activate" handler="on_file_saveprojectcopy" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkSeparatorMenuItem" id="separatormenuitem3">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                      </object>
+                    </child>
                     <child>
                       <object class="GtkImageMenuItem" id="imagemenuitem5">
                         <property name="label">gtk-quit</property>

+ 1 - 1
defaults.json

@@ -1 +1 @@
-{"cncjob_multicolored": false, "geometry_paintoverlap": 0.15, "geometry_plot": true, "cncjob_solid": false, "gerber_isotooldia": 0.16, "gerber_plot": true, "gerber_mergepolys": true, "gerber_cutoutgapsize": 0.15, "geometry_feedrate": 5.0, "units": "IN", "excellon_travelz": 0.1, "gerber_multicolored": false, "gerber_solid": false, "excellon_plot": true, "excellon_feedrate": 5.0, "cncjob_tooldia": 0.16, "geometry_travelz": 0.1, "gerber_cutoutmargin": 0.2, "excellon_solid": false, "geometry_paintmargin": 0.01, "geometry_cutz": -0.002, "gerber_noncoppermargin": 0.0, "gerber_gaps": "4", "excellon_multicolored": false, "gerber_bboxmargin": 0.0, "cncjob_plot": true, "excellon_drillz": -0.1, "gerber_bboxrounded": false, "geometry_multicolored": false, "geometry_cnctooldia": 0.16, "geometry_solid": false, "geometry_painttooldia": 0.0625}
+{"cncjob_multicolored": false, "geometry_paintoverlap": 0.15, "geometry_plot": true, "cncjob_solid": false, "excellon_feedrate": 5.0, "gerber_plot": true, "gerber_mergepolys": true, "excellon_drillz": -0.1, "geometry_feedrate": 5.0, "units": "IN", "excellon_travelz": 0.1, "gerber_multicolored": false, "gerber_solid": false, "excellon_plot": true, "gerber_isotooldia": 0.016, "cncjob_tooldia": 0.16, "geometry_travelz": 0.1, "gerber_cutoutmargin": 0.2, "excellon_solid": false, "geometry_paintmargin": 0.01, "geometry_cutz": -0.002, "geometry_cnctooldia": 0.16, "geometry_painttooldia": 0.0625, "gerber_gaps": "4", "excellon_multicolored": false, "gerber_bboxmargin": 0.0, "cncjob_plot": true, "gerber_cutoutgapsize": 0.15, "gerber_bboxrounded": false, "geometry_multicolored": false, "gerber_noncoppermargin": 0.0, "geometry_solid": false}