Просмотр исходного кода

Object serialization and project saving.

Juan Pablo Caram 12 лет назад
Родитель
Сommit
deac0a7ef6
4 измененных файлов с 203 добавлено и 19 удалено
  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.geometry import box as shply_box
 from shapely.ops import cascaded_union
 from shapely.ops import cascaded_union
 import shapely.affinity as affinity
 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
 # Used for solid polygons in Matplotlib
 from descartes.patch import PolygonPatch
 from descartes.patch import PolygonPatch
 
 
+import simplejson as json
 
 
 class Geometry:
 class Geometry:
     def __init__(self):
     def __init__(self):
@@ -20,6 +24,9 @@ class Geometry:
         
         
         # Final geometry: MultiPolygon
         # Final geometry: MultiPolygon
         self.solid_geometry = None
         self.solid_geometry = None
+
+        # Attributes to be included in serialization
+        self.ser_attrs = ['units', 'solid_geometry']
         
         
     def isolation_geometry(self, offset):
     def isolation_geometry(self, offset):
         """
         """
@@ -113,7 +120,25 @@ class Geometry:
         self.units = units
         self.units = units
         self.scale(factor)
         self.scale(factor)
         return 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):
 class Gerber (Geometry):
     """
     """
@@ -207,6 +232,13 @@ class Gerber (Geometry):
         # Geometry from flashes
         # Geometry from flashes
         self.flash_geometry = []
         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):
     def scale(self, factor):
         """
         """
         Scales the objects' geometry on the XY plane by a given 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"
         # Trailing "T" or leading "L"
         self.zeros = ""
         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):
     def parse_file(self, filename):
         efile = open(filename, 'r')
         efile = open(filename, 'r')
@@ -592,6 +629,13 @@ class CNCjob(Geometry):
         self.input_geometry_bounds = None
         self.input_geometry_bounds = None
         self.gcode_parsed = None
         self.gcode_parsed = None
         self.steps_per_circ = 20  # Used when parsing G-code arcs
         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):
     def generate_from_excellon(self, exobj):
         """
         """
@@ -949,6 +993,7 @@ class CNCjob(Geometry):
         return gcode
         return gcode
 
 
     def point2gcode(self, point):
     def point2gcode(self, point):
+        # TODO: This is not doing anything.
         gcode = ""
         gcode = ""
         t = "G0%d X%.4fY%.4f\n"
         t = "G0%d X%.4fY%.4f\n"
         path = list(point.coords)
         path = list(point.coords)
@@ -1067,6 +1112,22 @@ def find_polygon(poly_set, point):
             return poly
             return poly
     return None
     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 ####################
 ############### cam.py ####################
 def coord(gstr, digits, fraction):
 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
         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
         them to figure if not part of the figure. 4) Sets transparent
         background. 5) Sets 1:1 scale aspect ratio.
         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:
         if self.axes is None:
             print "New axes"
             print "New axes"
@@ -63,19 +65,21 @@ class CirkuixObj:
         self.axes.patch.set_visible(False)  # No background
         self.axes.patch.set_visible(False)  # No background
         self.axes.set_aspect(1)
         self.axes.set_aspect(1)
 
 
-    def set_options(self, options):
-        for name in options:
-            self.options[name] = options[name]
-        return
-
     def to_form(self):
     def to_form(self):
+        """
+        Copies options to the UI form.
+
+        :return: None
+        """
         for option in self.options:
         for option in self.options:
             self.set_form_item(option)
             self.set_form_item(option)
 
 
     def read_form(self):
     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:
         for option in self.options:
             self.read_form_item(option)
             self.read_form_item(option)
@@ -83,8 +87,9 @@ class CirkuixObj:
     def build_ui(self):
     def build_ui(self):
         """
         """
         Sets up the UI/form for this object.
         Sets up the UI/form for this object.
-        @return: None
-        @rtype : None
+
+        :return: None
+        :rtype : None
         """
         """
 
 
         # Where the UI for this object is drawn
         # Where the UI for this object is drawn
@@ -110,6 +115,13 @@ class CirkuixObj:
         sw.show()
         sw.show()
 
 
     def set_form_item(self, option):
     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]
         fkind = self.form_kinds[option]
         fname = fkind + "_" + self.kind + "_" + 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 = {"gaps": {"rb_2tb": "tb", "rb_2lr": "lr", "rb_4": "4"}}
         self.radios_inv = {"gaps": {"tb": "rb_2tb", "lr": "rb_2lr", "4": "rb_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):
     def convert_units(self, units):
         factor = Gerber.convert_units(self, units)
         factor = Gerber.convert_units(self, units)
 
 
@@ -290,8 +307,14 @@ class CirkuixExcellon(CirkuixObj, Excellon):
             "toolselection": "entry_text"
             "toolselection": "entry_text"
         })
         })
 
 
+        # TODO: Document this.
         self.tool_cbs = {}
         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):
     def convert_units(self, units):
         factor = Excellon.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("activate", on_accept)
         button.connect("clicked", 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):
 class CirkuixCNCjob(CirkuixObj, CNCjob):
     """
     """
@@ -371,6 +390,11 @@ class CirkuixCNCjob(CirkuixObj, CNCjob):
             "tooldia": "entry_eval"
             "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):
     def plot(self, figure):
         CirkuixObj.plot(self, figure)
         CirkuixObj.plot(self, figure)
         #self.setup_axes(figure)
         #self.setup_axes(figure)
@@ -417,6 +441,11 @@ class CirkuixGeometry(CirkuixObj, Geometry):
             "paintmargin": "entry_eval"
             "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):
     def scale(self, factor):
         if type(self.solid_geometry) == list:
         if type(self.solid_geometry) == list:
             self.solid_geometry = [affinity.scale(g, factor, factor, origin=(0, 0))
             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")
         self.units_label = self.builder.get_object("label_units")
 
 
         # White (transparent) background on the "Options" tab.
         # 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.
         # Combo box to choose between project and application options.
         self.combo_options = self.builder.get_object("combo_options")
         self.combo_options = self.builder.get_object("combo_options")
@@ -1100,9 +1130,51 @@ class App:
             return
             return
         print "Unknown kind of form item:", fkind
         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             ##
     ##         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):
     def on_options_app2project(self, param):
         """
         """
         Callback for Options->Transfer Options->App=>Project. Copies options
         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="can_focus">False</property>
     <property name="stock">gtk-jump-to</property>
     <property name="stock">gtk-jump-to</property>
   </object>
   </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">
   <object class="GtkOffscreenWindow" id="offscrwindow_cncjob">
     <property name="can_focus">False</property>
     <property name="can_focus">False</property>
     <child>
     <child>
@@ -2063,6 +2078,42 @@
                         <property name="can_focus">False</property>
                         <property name="can_focus">False</property>
                       </object>
                       </object>
                     </child>
                     </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>
                     <child>
                       <object class="GtkImageMenuItem" id="imagemenuitem5">
                       <object class="GtkImageMenuItem" id="imagemenuitem5">
                         <property name="label">gtk-quit</property>
                         <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}