Przeglądaj źródła

Added OS-X installation instructions to manual.

jpcaram 11 lat temu
rodzic
commit
a0d6d1a6a9
5 zmienionych plików z 141 dodań i 61 usunięć
  1. 9 7
      FlatCAMApp.py
  2. 6 9
      FlatCAMDraw.py
  3. 72 36
      camlib.py
  4. 2 1
      doc/source/planning.rst
  5. 52 8
      manual/installation.rst

+ 9 - 7
FlatCAMApp.py

@@ -2419,16 +2419,18 @@ class App(QtCore.QObject):
         try:
         try:
             f = open(filename, 'w')
             f = open(filename, 'w')
         except IOError:
         except IOError:
-            App.log.error("[error] Failed to open file for saving:", filename)
+            App.log.error("[error] Failed to open file for saving: %s", filename)
             return
             return
 
 
         # Write
         # Write
-        try:
-            json.dump(d, f, default=to_dict)
-        except:
-            App.log.error("[error] File open but failed to write:", filename)
-            f.close()
-            return
+        json.dump(d, f, default=to_dict)
+        # try:
+        #     json.dump(d, f, default=to_dict)
+        # except Exception, e:
+        #     print str(e)
+        #     App.log.error("[error] File open but failed to write: %s", filename)
+        #     f.close()
+        #     return
 
 
         f.close()
         f.close()
 
 

+ 6 - 9
FlatCAMDraw.py

@@ -800,7 +800,6 @@ class FlatCAMDraw(QtCore.QObject):
                     self.shape_buffer.remove(shape)
                     self.shape_buffer.remove(shape)
 
 
             # Add the new utility shape
             # Add the new utility shape
-            #self.shape_buffer.append(geo)
             self.add_shape(geo)
             self.add_shape(geo)
 
 
             # Efficient plotting for fast animation
             # Efficient plotting for fast animation
@@ -811,13 +810,12 @@ class FlatCAMDraw(QtCore.QObject):
                 self.axes.draw_artist(el)
                 self.axes.draw_artist(el)
             #self.canvas.canvas.blit(self.axes.bbox)
             #self.canvas.canvas.blit(self.axes.bbox)
 
 
-            #self.replot()
-
-            elements = self.axes.plot(x, y, 'bo', animated=True)
-            for el in elements:
-                    self.axes.draw_artist(el)
+        # Pointer (snapped)
+        elements = self.axes.plot(x, y, 'bo', animated=True)
+        for el in elements:
+                self.axes.draw_artist(el)
 
 
-            self.canvas.canvas.blit(self.axes.bbox)
+        self.canvas.canvas.blit(self.axes.bbox)
 
 
     def on_canvas_key(self, event):
     def on_canvas_key(self, event):
         """
         """
@@ -905,7 +903,7 @@ class FlatCAMDraw(QtCore.QObject):
 
 
     def plot_shape(self, geometry=None, linespec='b-', linewidth=1, animated=False):
     def plot_shape(self, geometry=None, linespec='b-', linewidth=1, animated=False):
         """
         """
-        Plots a geometric object or list of objects without rendeting. Plotted objects
+        Plots a geometric object or list of objects without rendering. Plotted objects
         are returned as a list. This allows for efficient/animated rendering.
         are returned as a list. This allows for efficient/animated rendering.
 
 
         :param geometry: Geometry to be plotted (Any Shapely.geom kind or list of such)
         :param geometry: Geometry to be plotted (Any Shapely.geom kind or list of such)
@@ -971,7 +969,6 @@ class FlatCAMDraw(QtCore.QObject):
         :type shape: DrawToolShape
         :type shape: DrawToolShape
         :return: None
         :return: None
         """
         """
-        print "add_shape()"
 
 
         # List of DrawToolShape?
         # List of DrawToolShape?
         if isinstance(shape, list):
         if isinstance(shape, list):

+ 72 - 36
camlib.py

@@ -154,26 +154,31 @@ class Geometry(object):
         if reset:
         if reset:
             self.flat_geometry = []
             self.flat_geometry = []
 
 
+        ## If iterable, expand recursively.
         try:
         try:
             for geo in geometry:
             for geo in geometry:
                 self.flatten_to_paths(geometry=geo, reset=False)
                 self.flatten_to_paths(geometry=geo, reset=False)
+
+        ## Not iterable, do the actual indexing and add.
         except TypeError:
         except TypeError:
             if type(geometry) == Polygon:
             if type(geometry) == Polygon:
                 g = geometry.exterior
                 g = geometry.exterior
                 self.flat_geometry.append(g)
                 self.flat_geometry.append(g)
-                self.flat_geometry_rtree.insert(len(self.flat_geometry)-1, g.coords[0])
-                self.flat_geometry_rtree.insert(len(self.flat_geometry)-1, g.coords[-1])
+
+                ## Add first and last points of the path to the index.
+                self.flat_geometry_rtree.insert(len(self.flat_geometry) - 1, g.coords[0])
+                self.flat_geometry_rtree.insert(len(self.flat_geometry) - 1, g.coords[-1])
 
 
                 for interior in geometry.interiors:
                 for interior in geometry.interiors:
                     g = interior
                     g = interior
                     self.flat_geometry.append(g)
                     self.flat_geometry.append(g)
-                    self.flat_geometry_rtree.insert(len(self.flat_geometry)-1, g.coords[0])
-                    self.flat_geometry_rtree.insert(len(self.flat_geometry)-1, g.coords[-1])
+                    self.flat_geometry_rtree.insert(len(self.flat_geometry) - 1, g.coords[0])
+                    self.flat_geometry_rtree.insert(len(self.flat_geometry) - 1, g.coords[-1])
             else:
             else:
                 g = geometry
                 g = geometry
                 self.flat_geometry.append(g)
                 self.flat_geometry.append(g)
-                self.flat_geometry_rtree.insert(len(self.flat_geometry)-1, g.coords[0])
-                self.flat_geometry_rtree.insert(len(self.flat_geometry)-1, g.coords[-1])
+                self.flat_geometry_rtree.insert(len(self.flat_geometry) - 1, g.coords[0])
+                self.flat_geometry_rtree.insert(len(self.flat_geometry) - 1, g.coords[-1])
 
 
         return self.flat_geometry, self.flat_geometry_rtree
         return self.flat_geometry, self.flat_geometry_rtree
 
 
@@ -258,17 +263,18 @@ class Geometry(object):
         :return:
         :return:
         """
         """
 
 
+        # Estimate good seedpoint if not provided.
         if seedpoint is None:
         if seedpoint is None:
             seedpoint = polygon.representative_point()
             seedpoint = polygon.representative_point()
 
 
         # Current buffer radius
         # Current buffer radius
-        radius = tooldia/2*(1-overlap)
+        radius = tooldia / 2 * (1 - overlap)
 
 
         # The toolpaths
         # The toolpaths
-        geoms = [Point(seedpoint).buffer(radius).exterior]
+        geoms = []
 
 
         # Path margin
         # Path margin
-        path_margin = polygon.buffer(-tooldia/2)
+        path_margin = polygon.buffer(-tooldia / 2)
 
 
         # Grow from seed until outside the box.
         # Grow from seed until outside the box.
         while 1:
         while 1:
@@ -281,12 +287,12 @@ class Geometry(object):
             else:
             else:
                 geoms.append(path)
                 geoms.append(path)
 
 
-            radius += tooldia*(1-overlap)
+            radius += tooldia * (1 - overlap)
 
 
         # Clean edges
         # Clean edges
-        outer_edges = [x.exterior for x in autolist(polygon.buffer(-tooldia/2))]
+        outer_edges = [x.exterior for x in autolist(polygon.buffer(-tooldia / 2))]
         inner_edges = []
         inner_edges = []
-        for x in autolist(polygon.buffer(-tooldia/2)):  # Over resulting polygons
+        for x in autolist(polygon.buffer(-tooldia / 2)):  # Over resulting polygons
             for y in x.interiors:  # Over interiors of each polygon
             for y in x.interiors:  # Over interiors of each polygon
                 inner_edges.append(y)
                 inner_edges.append(y)
         geoms += outer_edges + inner_edges
         geoms += outer_edges + inner_edges
@@ -2198,6 +2204,11 @@ class CNCjob(Geometry):
         """
         """
         Generates G-Code from a Geometry object. Stores in ``self.gcode``.
         Generates G-Code from a Geometry object. Stores in ``self.gcode``.
 
 
+        Algorithm description:
+        ----------------------
+        Follow geometry paths in the order they are being read. No attempt
+        to optimize.
+
         :param geometry: Geometry defining the toolpath
         :param geometry: Geometry defining the toolpath
         :type geometry: Geometry
         :type geometry: Geometry
         :param append: Wether to append to self.gcode or re-write it.
         :param append: Wether to append to self.gcode or re-write it.
@@ -2259,14 +2270,21 @@ class CNCjob(Geometry):
         """
         """
         Second algorithm to generate from Geometry.
         Second algorithm to generate from Geometry.
 
 
+        ALgorithm description:
+        ----------------------
+        Uses RTree to find the nearest path to follow.
+
         :param geometry:
         :param geometry:
         :param append:
         :param append:
         :param tooldia:
         :param tooldia:
         :param tolerance:
         :param tolerance:
-        :return:
+        :return: None
         """
         """
         assert isinstance(geometry, Geometry)
         assert isinstance(geometry, Geometry)
-        flat_geometry, rtindex = geometry.flatten_to_paths()
+
+        ## Flatten the geometry and get rtree index
+        flat_geometry, rti = geometry.flatten_to_paths()
+        log.debug("%d paths" % len(flat_geometry))
 
 
         if tooldia is not None:
         if tooldia is not None:
             self.tooldia = tooldia
             self.tooldia = tooldia
@@ -2285,24 +2303,28 @@ class CNCjob(Geometry):
         self.gcode += "M03\n"  # Spindle start
         self.gcode += "M03\n"  # Spindle start
         self.gcode += self.pausecode + "\n"
         self.gcode += self.pausecode + "\n"
 
 
-        # Iterate over geometry and run individual methods
-        # depending on type
-        # for geo in flat_geometry:
-        #
-        #     if type(geo) == LineString or type(geo) == LinearRing:
-        #         self.gcode += self.linear2gcode(geo, tolerance=tolerance)
-        #         continue
-        #
-        #     if type(geo) == Point:
-        #         self.gcode += self.point2gcode(geo)
-        #         continue
-        #
-        #     log.warning("G-code generation not implemented for %s" % (str(type(geo))))
-
-        hits = list(rtindex.nearest((0, 0), 1))
+        ## Iterate over geometry paths getting the nearest each time.
+        path_count = 0
+        current_pt = (0, 0)
+        hits = list(rti.nearest(current_pt, 1))
         while len(hits) > 0:
         while len(hits) > 0:
+            path_count += 1
+            print "Current: ", "(%.3f, %.3f)" % current_pt
             geo = flat_geometry[hits[0]]
             geo = flat_geometry[hits[0]]
 
 
+            # Determine which end of the path is closest.
+            distance2start = distance(current_pt, geo.coords[0])
+            distance2stop = distance(current_pt, geo.coords[-1])
+            print "  Path index =", hits[0]
+            print "  Start: ", "(%.3f, %.3f)" % geo.coords[0], "  D(Start): %.3f" % distance2start
+            print "  Stop : ", "(%.3f, %.3f)" % geo.coords[-1], "  D(Stop): %.3f" % distance2stop
+
+            # Reverse if end is closest.
+            if distance2start > distance2stop:
+                print "  Reversing!"
+                geo.coords = list(geo.coords)[::-1]
+
+            # G-code
             if type(geo) == LineString or type(geo) == LinearRing:
             if type(geo) == LineString or type(geo) == LinearRing:
                 self.gcode += self.linear2gcode(geo, tolerance=tolerance)
                 self.gcode += self.linear2gcode(geo, tolerance=tolerance)
             elif type(geo) == Point:
             elif type(geo) == Point:
@@ -2310,12 +2332,13 @@ class CNCjob(Geometry):
             else:
             else:
                 log.warning("G-code generation not implemented for %s" % (str(type(geo))))
                 log.warning("G-code generation not implemented for %s" % (str(type(geo))))
 
 
-            start_pt = geo.coords[0]
-            stop_pt = geo.coords[-1]
-            rtindex.delete(hits[0], start_pt)
-            rtindex.delete(hits[0], stop_pt)
-            hits = list(rtindex.nearest(stop_pt, 1))
+            # Delete from index, update current location and continue.
+            rti.delete(hits[0], geo.coords[0])
+            rti.delete(hits[0], geo.coords[-1])
+            current_pt = geo.coords[-1]
+            hits = list(rti.nearest(current_pt, 1))
 
 
+        log.debug("%s paths traced." % path_count)
 
 
         # Finish
         # Finish
         self.gcode += "G00 Z%.4f\n" % self.z_move  # Stop cutting
         self.gcode += "G00 Z%.4f\n" % self.z_move  # Stop cutting
@@ -2518,6 +2541,8 @@ class CNCjob(Geometry):
         :param tool_tolerance: Tolerance when drawing the toolshape.
         :param tool_tolerance: Tolerance when drawing the toolshape.
         :return: None
         :return: None
         """
         """
+        path_num = 0
+
         if tooldia is None:
         if tooldia is None:
             tooldia = self.tooldia
             tooldia = self.tooldia
         
         
@@ -2531,7 +2556,11 @@ class CNCjob(Geometry):
                 axes.plot(x, y, linespec, color=linecolor)
                 axes.plot(x, y, linespec, color=linecolor)
         else:
         else:
             for geo in self.gcode_parsed:
             for geo in self.gcode_parsed:
-                poly = geo['geom'].buffer(tooldia/2.0).simplify(tool_tolerance)
+                path_num += 1
+                axes.annotate(str(path_num), xy=geo['geom'].coords[0],
+                              xycoords='data')
+
+                poly = geo['geom'].buffer(tooldia / 2.0).simplify(tool_tolerance)
                 patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0],
                 patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0],
                                      edgecolor=color[geo['kind'][0]][1],
                                      edgecolor=color[geo['kind'][0]][1],
                                      alpha=alpha[geo['kind'][0]], zorder=2)
                                      alpha=alpha[geo['kind'][0]], zorder=2)
@@ -2812,7 +2841,10 @@ def find_polygon(poly_set, point):
 
 
 def to_dict(obj):
 def to_dict(obj):
     """
     """
-    Makes a Shapely geometry object into serializeable form.
+    Makes the following types into serializable form:
+
+    * ApertureMacro
+    * BaseGeometry
 
 
     :param obj: Shapely geometry.
     :param obj: Shapely geometry.
     :type obj: BaseGeometry
     :type obj: BaseGeometry
@@ -3149,3 +3181,7 @@ def three_point_circle(p1, p2, p3):
     radius = norm(center - p1)
     radius = norm(center - p1)
 
 
     return center, radius, T[0]
     return center, radius, T[0]
+
+
+def distance(pt1, pt2):
+    return sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)

+ 2 - 1
doc/source/planning.rst

@@ -7,7 +7,8 @@ Drawing
 * [DONE] Arcs
 * [DONE] Arcs
 * [DONE] Subtract Shapes
 * [DONE] Subtract Shapes
   * [DONE] Selected objects must be kept onlist to preserve order.
   * [DONE] Selected objects must be kept onlist to preserve order.
-* Polygon to outline
+* [DONE] Cut Path
+  * Polygon to outline
 * Force perpendicular
 * Force perpendicular
 * Un-group (Union creates group)
 * Un-group (Union creates group)
 * Group (But not union)
 * Group (But not union)

+ 52 - 8
manual/installation.rst

@@ -4,16 +4,27 @@ Installation
 Windows Installer
 Windows Installer
 -----------------
 -----------------
 
 
-Download the installer from the repository_ and run it in your machine. It includes everything you need.
+Download the installer from the repository_ and run it in your machine.
+It includes everything you need.
 
 
 .. _repository: https://bitbucket.org/jpcgt/flatcam/downloads
 .. _repository: https://bitbucket.org/jpcgt/flatcam/downloads
 
 
 Ubuntu
 Ubuntu
 ------
 ------
 
 
-FlatCAM should work on most Linux distributions but Ubuntu has been chosen as the test platform.
+FlatCAM should work on most Linux distributions but Ubuntu has been
+chosen as the test platform.
+
+There are several dependencies required to run FlatCAM. These are
+listed in the following section. Before attempting a manual installation,
+try running the provided setup script ``setup_ubuntu.sh`` that will
+download and install required packages.
+
+OS-X
+----
+
+See manual instructions below.
 
 
-There are several dependencies required to run FlatCAM. These are listed in the following section. Before attempting a manual installation, try running the provided setup script ``setup_ubuntu.sh`` that will download and install required packages.
 
 
 Manual Installation
 Manual Installation
 -------------------
 -------------------
@@ -26,6 +37,9 @@ Requirements
 * Matplotlib 1.3.1
 * Matplotlib 1.3.1
 * Numpy 1.8
 * Numpy 1.8
 * `Shapely 1.3`_
 * `Shapely 1.3`_
+  * GEOS
+* RTree
+  * SpatialIndex
 
 
 .. _Shapely 1.3: https://pypi.python.org/pypi/Shapely
 .. _Shapely 1.3: https://pypi.python.org/pypi/Shapely
 
 
@@ -34,9 +48,14 @@ These packages might have their own dependencies.
 Linux
 Linux
 ~~~~~
 ~~~~~
 
 
-Under Linux, most modern package installers like **yum** or **apt-get** will attempt to locate and install the whole tree of dependencies for a specified package automatically. Refer to the provided setup script ``setup_ubuntu.sh`` for the names and installation order.
+Under Linux, most modern package installers like **yum** or **apt-get**
+will attempt to locate and install the whole tree of dependencies for a
+specified package automatically. Refer to the provided setup script
+``setup_ubuntu.sh`` for the names and installation order.
 
 
-Once the dependencies are installed, download the latest .zip release (or the latest source, although it is not garanteed to work), unpack it, change into the created folder and run::
+Once the dependencies are installed, download the latest .zip release
+(or the latest source, although it is not garanteed to work), unpack it,
+change into the created folder and run::
 
 
     Python FlatCAM.py
     Python FlatCAM.py
 
 
@@ -44,11 +63,36 @@ Once the dependencies are installed, download the latest .zip release (or the la
 Windows
 Windows
 ~~~~~~~
 ~~~~~~~
 
 
-An easy way to get the requirements in your system is to install WinPython_. This is a standalone distribution of Python which includes all of FlatCAM's dependencies, except for Shapely.
+An easy way to get the requirements in your system is to install WinPython_.
+This is a standalone distribution of Python which includes all of FlatCAM's
+dependencies, except for Shapely.
 
 
 .. _WinPython: http://winpython.sourceforge.net/
 .. _WinPython: http://winpython.sourceforge.net/
 
 
-Once the dependencies are installed, download the latest .zip release (or the latest source, although it is not garanteed to work), unpack it, change into the created folder and run::
+Once the dependencies are installed, download the latest .zip
+release (or the latest source, although it is not garanteed to work),
+unpack it, change into the created folder and run::
 
 
-    Python FlatCAM.py
+    python FlatCAM.py
+
+
+OS-X
+~~~~
+
+Start by installing binary packages: pyqt, geos, spatialindex.
+One way to do this is using Homebrew_::
+
+    brew install name_of_package
+
+.. _Homebrew: http://brew.sh
+
+Now you can install all Python packages (numpy, matplotlib, rtree, scipy,
+shapely, simplejson) using pip::
+
+    pip install name_of_package
+
+Finally, download the latest FlatCAM .zip package or source code. Change into
+its directory and launch it by running::
+
+    python FlatCAM.py