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

Serialization of ApertureMacro. Change scale, offset and mirror in Gerber to act only upon its resulting geometry, not its source data.

Juan Pablo Caram 12 лет назад
Родитель
Сommit
9d9c3f819d

+ 11 - 4
FlatCAM.py

@@ -16,6 +16,7 @@ from gi.repository import GLib
 from gi.repository import GObject
 from gi.repository import GObject
 import simplejson as json
 import simplejson as json
 
 
+import matplotlib
 from matplotlib.figure import Figure
 from matplotlib.figure import Figure
 from numpy import arange, sin, pi
 from numpy import arange, sin, pi
 from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
 from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
@@ -482,7 +483,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
             return
             return
 
 
         self.plot2(self.axes, tooldia=self.options["tooldia"])
         self.plot2(self.axes, tooldia=self.options["tooldia"])
-        self.app.plotcanvas.auto_adjust_axes()
+
+        #self.app.plotcanvas.auto_adjust_axes()
+        GLib.idle_add(self.app.plotcanvas.auto_adjust_axes)
 
 
     def convert_units(self, units):
     def convert_units(self, units):
         factor = CNCjob.convert_units(self, units)
         factor = CNCjob.convert_units(self, units)
@@ -625,7 +628,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
 
             print "WARNING: Did not plot:", str(type(geo))
             print "WARNING: Did not plot:", str(type(geo))
 
 
-        self.app.plotcanvas.auto_adjust_axes()
+        #self.app.plotcanvas.auto_adjust_axes()
+        GLib.idle_add(self.app.plotcanvas.auto_adjust_axes)
 
 
 
 
 ########################################
 ########################################
@@ -1267,13 +1271,16 @@ class App:
         try:
         try:
             f = open(filename, 'r')
             f = open(filename, 'r')
         except:
         except:
-            print "WARNING: Failed to open project file:", filename
+            #print "WARNING: Failed to open project file:", filename
+            self.info("ERROR: Failed to open project file: %s" % filename)
             return
             return
 
 
         try:
         try:
             d = json.load(f, object_hook=dict2obj)
             d = json.load(f, object_hook=dict2obj)
         except:
         except:
-            print "WARNING: Failed to parse project file:", filename
+            #print sys.exc_info()
+            #print "WARNING: Failed to parse project file:", filename
+            self.info("ERROR: Failed to parse project file: %s" % filename)
             f.close()
             f.close()
             return
             return
 
 

+ 159 - 59
camlib.py

@@ -192,10 +192,38 @@ class ApertureMacro:
     def __init__(self, name=None):
     def __init__(self, name=None):
         self.name = name
         self.name = name
         self.raw = ""
         self.raw = ""
+
+        ## These below are recomputed for every aperture
+        ## definition, in other words, are temporary variables.
         self.primitives = []
         self.primitives = []
         self.locvars = {}
         self.locvars = {}
         self.geometry = None
         self.geometry = None
 
 
+    def to_dict(self):
+        """
+        Returns the object in a serializable form. Only the name and
+        raw are required.
+
+        :return: Dictionary representing the object. JSON ready.
+        :rtype: dict
+        """
+
+        return {
+            'name': self.name,
+            'raw': self.raw
+        }
+
+    def from_dict(self, d):
+        """
+        Populates the object from a serial representation created
+        with ``self.to_dict()``.
+
+        :param d: Serial representation of an ApertureMacro object.
+        :return: None
+        """
+        for attr in ['name', 'raw']:
+            setattr(self, attr, d[attr])
+
     def parse_content(self):
     def parse_content(self):
         """
         """
         Creates numerical lists for all primitives in the aperture
         Creates numerical lists for all primitives in the aperture
@@ -632,7 +660,7 @@ class Gerber (Geometry):
         # from Geometry.
         # from Geometry.
         self.ser_attrs += ['int_digits', 'frac_digits', 'apertures', 'paths',
         self.ser_attrs += ['int_digits', 'frac_digits', 'apertures', 'paths',
                            'buffered_paths', 'regions', 'flashes',
                            'buffered_paths', 'regions', 'flashes',
-                           'flash_geometry']
+                           'flash_geometry', 'aperture_macros']
 
 
         #### Parser patterns ####
         #### Parser patterns ####
         # FS - Format Specification
         # FS - Format Specification
@@ -714,55 +742,74 @@ class Gerber (Geometry):
         Scales the objects' geometry on the XY plane by a given factor.
         Scales the objects' geometry on the XY plane by a given factor.
         These are:
         These are:
 
 
-        * ``apertures``
-        * ``paths``
+        * ``buffered_paths``
+        * ``flash_geometry``
+        * ``solid_geometry``
         * ``regions``
         * ``regions``
-        * ``flashes``
 
 
-        Then ``buffered_paths``, ``flash_geometry`` and ``solid_geometry``
-        are re-created with ``self.create_geometry()``.
+        NOTE:
+        Does not modify the data used to create these elements. If these
+        are recreated, the scaling will be lost. This behavior was modified
+        because of the complexity reached in this class.
+
         :param factor: Number by which to scale.
         :param factor: Number by which to scale.
         :type factor: float
         :type factor: float
         :rtype : None
         :rtype : None
         """
         """
 
 
-        ## Apertures
-        # List of the non-dimension aperture parameters
-        nonDimensions = ["type", "nVertices", "rotation"]
-        for apid in self.apertures:
-            for param in self.apertures[apid]:
-                if param not in nonDimensions:  # All others are dimensions.
-                    print "Tool:", apid, "Parameter:", param
-                    self.apertures[apid][param] *= factor
-
-        ## Paths
-        for path in self.paths:
-            path['linestring'] = affinity.scale(path['linestring'],
-                                                factor, factor, origin=(0, 0))
-
-        ## Flashes
-        for fl in self.flashes:
-            fl['loc'] = affinity.scale(fl['loc'], factor, factor, origin=(0, 0))
+        # ## Apertures
+        # # List of the non-dimension aperture parameters
+        # nonDimensions = ["type", "nVertices", "rotation"]
+        # for apid in self.apertures:
+        #     for param in self.apertures[apid]:
+        #         if param not in nonDimensions:  # All others are dimensions.
+        #             print "Tool:", apid, "Parameter:", param
+        #             self.apertures[apid][param] *= factor
+        #
+        # ## Paths
+        # for path in self.paths:
+        #     path['linestring'] = affinity.scale(path['linestring'],
+        #                                         factor, factor, origin=(0, 0))
+        #
+        # ## Flashes
+        # for fl in self.flashes:
+        #     fl['loc'] = affinity.scale(fl['loc'], factor, factor, origin=(0, 0))
 
 
         ## Regions
         ## Regions
         for reg in self.regions:
         for reg in self.regions:
             reg['polygon'] = affinity.scale(reg['polygon'], factor, factor,
             reg['polygon'] = affinity.scale(reg['polygon'], factor, factor,
                                             origin=(0, 0))
                                             origin=(0, 0))
 
 
-        # Now buffered_paths, flash_geometry and solid_geometry
-        self.create_geometry()
+        ## Flashes
+        for flash in self.flash_geometry:
+            flash = affinity.scale(flash, factor, factor, origin=(0, 0))
+
+        ## Buffered paths
+        for bp in self.buffered_paths:
+            bp = affinity.scale(bp, factor, factor, origin=(0, 0))
+
+        ## solid_geometry ???
+        #  It's a cascaded union of objects.
+        self.solid_geometry = affinity.scale(self.solid_geometry, factor,
+                                             factor, origin=(0, 0))
+
+        # # Now buffered_paths, flash_geometry and solid_geometry
+        # self.create_geometry()
 
 
     def offset(self, vect):
     def offset(self, vect):
         """
         """
         Offsets the objects' geometry on the XY plane by a given vector.
         Offsets the objects' geometry on the XY plane by a given vector.
         These are:
         These are:
 
 
-        * ``paths``
+        * ``buffered_paths``
+        * ``flash_geometry``
+        * ``solid_geometry``
         * ``regions``
         * ``regions``
-        * ``flashes``
 
 
-        Then ``buffered_paths``, ``flash_geometry`` and ``solid_geometry``
-        are re-created with ``self.create_geometry()``.
+        NOTE:
+        Does not modify the data used to create these elements. If these
+        are recreated, the scaling will be lost. This behavior was modified
+        because of the complexity reached in this class.
 
 
         :param vect: (x, y) offset vector.
         :param vect: (x, y) offset vector.
         :type vect: tuple
         :type vect: tuple
@@ -771,25 +818,48 @@ class Gerber (Geometry):
 
 
         dx, dy = vect
         dx, dy = vect
 
 
-        ## Paths
-        for path in self.paths:
-            path['linestring'] = affinity.translate(path['linestring'],
-                                                    xoff=dx, yoff=dy)
-
-        ## Flashes
-        for fl in self.flashes:
-            fl['loc'] = affinity.translate(fl['loc'], xoff=dx, yoff=dy)
+        # ## Paths
+        # for path in self.paths:
+        #     path['linestring'] = affinity.translate(path['linestring'],
+        #                                             xoff=dx, yoff=dy)
+        #
+        # ## Flashes
+        # for fl in self.flashes:
+        #     fl['loc'] = affinity.translate(fl['loc'], xoff=dx, yoff=dy)
 
 
         ## Regions
         ## Regions
         for reg in self.regions:
         for reg in self.regions:
             reg['polygon'] = affinity.translate(reg['polygon'],
             reg['polygon'] = affinity.translate(reg['polygon'],
                                                 xoff=dx, yoff=dy)
                                                 xoff=dx, yoff=dy)
 
 
-        # Now buffered_paths, flash_geometry and solid_geometry
-        self.create_geometry()
+        ## Buffered paths
+        for bp in self.buffered_paths:
+            bp = affinity.translate(bp, xoff=dx, yoff=dy)
+
+        ## Flash geometry
+        for fl in self.flash_geometry:
+            fl = affinity.translate(fl, xoff=dx, yoff=dy)
+
+        ## Solid geometry
+        self.solid_geometry = affinity.translate(self.solid_geometry, xoff=dx, yoff=dy)
+
+        # # Now buffered_paths, flash_geometry and solid_geometry
+        # self.create_geometry()
 
 
     def mirror(self, axis, point):
     def mirror(self, axis, point):
         """
         """
+        Mirrors the object around a specified axis passign through
+        the given point. What is affected:
+
+        * ``buffered_paths``
+        * ``flash_geometry``
+        * ``solid_geometry``
+        * ``regions``
+
+        NOTE:
+        Does not modify the data used to create these elements. If these
+        are recreated, the scaling will be lost. This behavior was modified
+        because of the complexity reached in this class.
 
 
         :param axis: "X" or "Y" indicates around which axis to mirror.
         :param axis: "X" or "Y" indicates around which axis to mirror.
         :type axis: str
         :type axis: str
@@ -801,22 +871,35 @@ class Gerber (Geometry):
         px, py = point
         px, py = point
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
 
 
-        ## Paths
-        for path in self.paths:
-            path['linestring'] = affinity.scale(path['linestring'], xscale, yscale,
-                                                origin=(px, py))
-
-        ## Flashes
-        for fl in self.flashes:
-            fl['loc'] = affinity.scale(fl['loc'], xscale, yscale, origin=(px, py))
+        # ## Paths
+        # for path in self.paths:
+        #     path['linestring'] = affinity.scale(path['linestring'], xscale, yscale,
+        #                                         origin=(px, py))
+        #
+        # ## Flashes
+        # for fl in self.flashes:
+        #     fl['loc'] = affinity.scale(fl['loc'], xscale, yscale, origin=(px, py))
 
 
         ## Regions
         ## Regions
         for reg in self.regions:
         for reg in self.regions:
             reg['polygon'] = affinity.scale(reg['polygon'], xscale, yscale,
             reg['polygon'] = affinity.scale(reg['polygon'], xscale, yscale,
                                             origin=(px, py))
                                             origin=(px, py))
 
 
-        # Now buffered_paths, flash_geometry and solid_geometry
-        self.create_geometry()
+        ## Flashes
+        for flash in self.flash_geometry:
+            flash = affinity.scale(flash, xscale, yscale, origin=(px, py))
+
+        ## Buffered paths
+        for bp in self.buffered_paths:
+            bp = affinity.scale(bp, xscale, yscale, origin=(px, py))
+
+        ## solid_geometry ???
+        #  It's a cascaded union of objects.
+        self.solid_geometry = affinity.scale(self.solid_geometry,
+                                             xscale, yscale, origin=(px, py))
+
+        # # Now buffered_paths, flash_geometry and solid_geometry
+        # self.create_geometry()
 
 
     def fix_regions(self):
     def fix_regions(self):
         """
         """
@@ -2266,27 +2349,44 @@ def find_polygon(poly_set, point):
     return None
     return None
 
 
 
 
-def to_dict(geo):
+def to_dict(obj):
     """
     """
     Makes a Shapely geometry object into serializeable form.
     Makes a Shapely geometry object into serializeable form.
 
 
-    :param geo: Shapely geometry.
-    :type geo: BaseGeometry
-    :return: Dictionary with serializable form if ``geo`` was
-        BaseGeometry, otherwise returns ``geo``.
+    :param obj: Shapely geometry.
+    :type obj: BaseGeometry
+    :return: Dictionary with serializable form if ``obj`` was
+        BaseGeometry or ApertureMacro, otherwise returns ``obj``.
     """
     """
-    if isinstance(geo, BaseGeometry):
+    if isinstance(obj, ApertureMacro):
+        return {
+            "__class__": "ApertureMacro",
+            "__inst__": obj.to_dict()
+        }
+    if isinstance(obj, BaseGeometry):
         return {
         return {
             "__class__": "Shply",
             "__class__": "Shply",
-            "__inst__": sdumps(geo)
+            "__inst__": sdumps(obj)
         }
         }
-    return geo
+    return obj
 
 
 
 
 def dict2obj(d):
 def dict2obj(d):
+    """
+    Default deserializer.
+
+    :param d:  Serializable dictionary representation of an object
+        to be reconstructed.
+    :return: Reconstructed object.
+    """
     if '__class__' in d and '__inst__' in d:
     if '__class__' in d and '__inst__' in d:
-        # For now assume all classes are Shapely geometry.
-        return sloads(d['__inst__'])
+        if d['__class__'] == "Shply":
+            return sloads(d['__inst__'])
+        if d['__class__'] == "ApertureMacro":
+            am = ApertureMacro()
+            am.from_dict(d['__inst__'])
+            return am
+        return d
     else:
     else:
         return d
         return d
 
 

BIN
doc/build/.doctrees/environment.pickle


+ 1 - 0
doc/build/genindex.html

@@ -103,6 +103,7 @@
 </li>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
 </ul>
 </ul>
 </li>
 </li>
 </ul>
 </ul>

+ 2 - 0
doc/build/index.html

@@ -103,6 +103,7 @@
 </li>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
 </ul>
 </ul>
 </li>
 </li>
 </ul>
 </ul>
@@ -167,6 +168,7 @@
 </li>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
 </ul>
 </ul>
 </li>
 </li>
 </ul>
 </ul>

BIN
doc/build/objects.inv


+ 1 - 0
doc/build/py-modindex.html

@@ -109,6 +109,7 @@
 </li>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
 </ul>
 </ul>
 </li>
 </li>
 </ul>
 </ul>

+ 1 - 0
doc/build/search.html

@@ -110,6 +110,7 @@
 </li>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
 </ul>
 </ul>
 </li>
 </li>
 </ul>
 </ul>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
doc/build/searchindex.js


+ 44 - 1
doc/source/devman.rst

@@ -12,4 +12,47 @@ There are **Application Defaults**, **Project Options** and **Object Options** i
 
 
 **Object Options** for each object are inherited from Project Options upon creation of each new object. They can be modified independently from the Project's options thereafter through the UI, where the widget containing the option is identified by name: ``type + kind + "_" + option``. They are stored in ``object.options``. They are saved along the Project options when saving the project.
 **Object Options** for each object are inherited from Project Options upon creation of each new object. They can be modified independently from the Project's options thereafter through the UI, where the widget containing the option is identified by name: ``type + kind + "_" + option``. They are stored in ``object.options``. They are saved along the Project options when saving the project.
 
 
-The syntax of UI widget names contain a ``type``, which identifies what *type of widget* it is and how its value is supposed to be fetched, and a ``kind``, which refer to what *kind of FlatCAM Object* it is for.
+The syntax of UI widget names contain a ``type``, which identifies what *type of widget* it is and how its value is supposed to be fetched, and a ``kind``, which refer to what *kind of FlatCAM Object* it is for.
+
+Serialization
+~~~~~~~~~~~~~
+
+Serialization refers to converting objects into a form that can be saved in a text file and recontructing objects from a text file.
+
+Saving and loading projects require serialization. These are done in ``App.save_project(filename)`` and ``App.open_project(filename)``.
+
+Serialization in FlatCAM takes 2 forms. The first is calling objects' ``to_dict()`` method, which is inherited from ``Geometry.to_dict()``::
+
+    def to_dict(self):
+        """
+        Returns a respresentation of the object as a dictionary.
+        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
+
+
+This creates a dictionary with attributes specified in the object's ``ser_attrs`` list. If these are not in a serialized form, they will be processed later by the function ``to_dict()``::
+
+    def to_dict(geo):
+        """
+        Makes a Shapely geometry object into serializeable form.
+
+        :param geo: Shapely geometry.
+        :type geo: BaseGeometry
+        :return: Dictionary with serializable form if ``geo`` was
+            BaseGeometry, otherwise returns ``geo``.
+        """
+        if isinstance(geo, BaseGeometry):
+            return {
+                "__class__": "Shply",
+                "__inst__": sdumps(geo)
+            }
+        return geo
+
+This is used in ``json.dump(d, f, default=to_dict)`` and is applied to objects that json encounters to be in a non-serialized form.

Некоторые файлы не были показаны из-за большого количества измененных файлов