Переглянути джерело

Using FlatCamRTreeStorage in copper clearing algorithm.

jpcaram 11 роки тому
батько
коміт
977b5b1f91
4 змінених файлів з 162 додано та 104 видалено
  1. 7 20
      FlatCAMDraw.py
  2. 117 83
      camlib.py
  3. 1 1
      doc/source/planning.rst
  4. 37 0
      tests/test_fcrts.py

+ 7 - 20
FlatCAMDraw.py

@@ -704,7 +704,6 @@ class FlatCAMDraw(QtCore.QObject):
 
     def toolbar_tool_toggle(self, key):
         self.options[key] = self.sender().isChecked()
-        print "grid_snap", self.options["grid_snap"]
 
     def clear(self):
         self.active_tool = None
@@ -721,24 +720,14 @@ class FlatCAMDraw(QtCore.QObject):
         :param fcgeometry: FlatCAMGeometry
         :return: None
         """
+        assert isinstance(fcgeometry, Geometry)
 
-        if fcgeometry.solid_geometry is None:
-            geometry = []
-        else:
-            try:
-                _ = iter(fcgeometry.solid_geometry)
-                geometry = fcgeometry.solid_geometry
-            except TypeError:
-                geometry = [fcgeometry.solid_geometry]
-
-        # Delete contents of editor.
-        #self.shape_buffer = []
         self.clear()
 
         # Link shapes into editor.
-        for shape in geometry:
-            #self.shape_buffer.append(DrawToolShape(geometry))
-            self.add_shape(DrawToolShape(shape.flatten()))
+        for shape in fcgeometry.flatten():
+            if shape is not None:  # TODO: Make flatten never create a None
+                self.add_shape(DrawToolShape(shape))
 
         self.replot()
         self.drawing_toolbar.setDisabled(False)
@@ -850,8 +839,6 @@ class FlatCAMDraw(QtCore.QObject):
 
         if isinstance(geo, DrawToolShape) and geo.geo is not None:
 
-            print geo.geo
-
             # Remove any previous utility shape
             self.delete_utility_geometry()
 
@@ -1255,9 +1242,9 @@ class FlatCAMDraw(QtCore.QObject):
         results = cascaded_union([t.geo for t in self.get_selected()])
 
         # Delete originals.
-        for shape in self.get_selected():
-            #self.shape_buffer.remove(shape)
-            self.delete_shape(shape)  # TODO: This will crash
+        for_deletion = [s for s in self.get_selected()]
+        for shape in for_deletion:
+            self.delete_shape(shape)
 
         # Selected geometry is now gone!
         self.selected = []

+ 117 - 83
camlib.py

@@ -138,7 +138,7 @@ class Geometry(object):
         else:
             return self.solid_geometry.bounds
 
-    def flatten(self, geometry=None, reset=True):
+    def flatten(self, geometry=None, reset=True, pathonly=False):
         if geometry is None:
             geometry = self.solid_geometry
 
@@ -148,12 +148,21 @@ class Geometry(object):
         ## If iterable, expand recursively.
         try:
             for geo in geometry:
-                self.flatten(geometry=geo, reset=False)
+                self.flatten(geometry=geo,
+                             reset=False,
+                             pathonly=pathonly)
 
         ## Not iterable, do the actual indexing and add.
         except TypeError:
-            if type(geometry) == Polygon:
+            if pathonly and type(geometry) == Polygon:
+                self.flat_geometry.append(geometry.exterior)
+                self.flatten(geometry=geometry.interiors,
+                             reset=False,
+                             pathonly=True)
+            else:
                 self.flat_geometry.append(geometry)
+            # if type(geometry) == Polygon:
+            #     self.flat_geometry.append(geometry)
 
         return self.flat_geometry
 
@@ -178,50 +187,49 @@ class Geometry(object):
             idx.insert(shape)
         return idx
 
-
-    def flatten_to_paths(self, geometry=None, reset=True):
-        """
-        Creates a list of non-iterable linear geometry elements and
-        indexes them in rtree.
-
-        :param geometry: Iterable geometry
-        :param reset: Wether to clear (True) or append (False) to self.flat_geometry
-        :return: self.flat_geometry, self.flat_geometry_rtree
-        """
-
-        if geometry is None:
-            geometry = self.solid_geometry
-
-        if reset:
-            self.flat_geometry = []
-
-        ## If iterable, expand recursively.
-        try:
-            for geo in geometry:
-                self.flatten_to_paths(geometry=geo, reset=False)
-
-        ## Not iterable, do the actual indexing and add.
-        except TypeError:
-            if type(geometry) == Polygon:
-                g = geometry.exterior
-                self.flat_geometry.append(g)
-
-                ## 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:
-                    g = interior
-                    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])
-            else:
-                g = geometry
-                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])
-
-        return self.flat_geometry, self.flat_geometry_rtree
+    # def flatten_to_paths(self, geometry=None, reset=True):
+    #     """
+    #     Creates a list of non-iterable linear geometry elements and
+    #     indexes them in rtree.
+    #
+    #     :param geometry: Iterable geometry
+    #     :param reset: Wether to clear (True) or append (False) to self.flat_geometry
+    #     :return: self.flat_geometry, self.flat_geometry_rtree
+    #     """
+    #
+    #     if geometry is None:
+    #         geometry = self.solid_geometry
+    #
+    #     if reset:
+    #         self.flat_geometry = []
+    #
+    #     ## If iterable, expand recursively.
+    #     try:
+    #         for geo in geometry:
+    #             self.flatten_to_paths(geometry=geo, reset=False)
+    #
+    #     ## Not iterable, do the actual indexing and add.
+    #     except TypeError:
+    #         if type(geometry) == Polygon:
+    #             g = geometry.exterior
+    #             self.flat_geometry.append(g)
+    #
+    #             ## 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:
+    #                 g = interior
+    #                 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])
+    #         else:
+    #             g = geometry
+    #             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])
+    #
+    #     return self.flat_geometry, self.flat_geometry_rtree
 
     def isolation_geometry(self, offset):
         """
@@ -2323,10 +2331,21 @@ class CNCjob(Geometry):
         """
         assert isinstance(geometry, Geometry)
 
-        ## Flatten the geometry and get rtree index
-        flat_geometry, rti = geometry.flatten_to_paths()
+        ## Flatten the geometry
+        flat_geometry = geometry.flatten(pathonly=True)
         log.debug("%d paths" % len(flat_geometry))
 
+        ## Index first and last points in paths
+        def get_pts(o):
+            return [o.coords[0], o.coords[-1]]
+
+        storage = FlatCAMRTreeStorage()
+        storage.get_points = get_pts
+
+        for shape in flat_geometry:
+            if shape is not None:  # TODO: This shouldn't have happened.
+                storage.insert(shape)
+
         if tooldia is not None:
             self.tooldia = tooldia
 
@@ -2347,37 +2366,44 @@ class CNCjob(Geometry):
         ## 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:
-            path_count += 1
-            print "Current: ", "(%.3f, %.3f)" % current_pt
-            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:
-                self.gcode += self.linear2gcode(geo, tolerance=tolerance)
-            elif type(geo) == Point:
-                self.gcode += self.point2gcode(geo)
-            else:
-                log.warning("G-code generation not implemented for %s" % (str(type(geo))))
+        pt, geo = storage.nearest(current_pt)
+        try:
+            while True:
+                path_count += 1
+                print "Current: ", "(%.3f, %.3f)" % current_pt
+
+                # TODO: There shoudn't be any None in geometry.flatten()
+                # if geo is None:
+                #     storage.remove(geo)
+                #     continue
+
+                # Remove before modifying, otherwise
+                # deletion will fail.
+                storage.remove(geo)
+
+                if list(pt) == list(geo.coords[-1]):
+                    print "Reversing"
+                    geo.coords = list(geo.coords)[::-1]
+
+                # G-code
+                if type(geo) == LineString or type(geo) == LinearRing:
+                    self.gcode += self.linear2gcode(geo, tolerance=tolerance)
+                elif type(geo) == Point:
+                    self.gcode += self.point2gcode(geo)
+                else:
+                    log.warning("G-code generation not implemented for %s" % (str(type(geo))))
+
+                # 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]
 
-            # 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))
+                # Next
+                pt, geo = storage.nearest(current_pt)
+
+        except StopIteration:  # Nothing found in storage.
+            pass
 
         log.debug("%s paths traced." % path_count)
 
@@ -3231,8 +3257,15 @@ def distance(pt1, pt2):
 class FlatCAMRTree(object):
 
     def __init__(self):
+        # Python RTree Index
         self.rti = rtindex.Index()
+
+        ## Track object-point relationship
+        # Each is list of points in object.
         self.obj2points = []
+
+        # Index is index in rtree, value is index of
+        # object in obj2points.
         self.points2obj = []
 
         self.get_points = lambda go: go.coords
@@ -3251,7 +3284,6 @@ class FlatCAMRTree(object):
         self.grow_obj2points(objid)
         self.obj2points[objid] = []
 
-        #for pt in obj.coords:
         for pt in self.get_points(obj):
             self.rti.insert(len(self.points2obj), (pt[0], pt[1], pt[0], pt[1]), obj=objid)
             self.obj2points[objid].append(len(self.points2obj))
@@ -3259,10 +3291,7 @@ class FlatCAMRTree(object):
 
     def remove_obj(self, objid, obj):
         # Use all ptids to delete from index
-        #for i in range(len(self.obj2points[objid])):
         for i, pt in enumerate(self.get_points(obj)):
-            #pt = obj.coords[i]
-            #pt = self.get_points(obj)[i]
             self.rti.delete(self.obj2points[objid][i], (pt[0], pt[1], pt[0], pt[1]))
 
     def nearest(self, pt):
@@ -3280,8 +3309,13 @@ class FlatCAMRTreeStorage(FlatCAMRTree):
         super(FlatCAMRTreeStorage, self).insert(len(self.objects) - 1, obj)
 
     def remove(self, obj):
+        # Get index in list
         objidx = self.objects.index(obj)
+
+        # Remove from list
         self.objects[objidx] = None
+
+        # Remove from index
         self.remove_obj(objidx, obj)
 
     def get_objects(self):

+ 1 - 1
doc/source/planning.rst

@@ -12,7 +12,7 @@ Drawing
 * Force perpendicular
 * Un-group (Union creates group)
 * Group (But not union)
-* Remove from index (rebuild index or make deleted instances
+* [DONE] Remove from index (rebuild index or make deleted instances
   equal to None in the list).
 * Better handling/abstraction of geometry types and lists of such.
 

+ 37 - 0
tests/test_fcrts.py

@@ -0,0 +1,37 @@
+from camlib import *
+from shapely.geometry import LineString, LinearRing
+
+s = FlatCAMRTreeStorage()
+
+geoms = [
+    LinearRing(((0.5699056603773586, 0.7216037735849057),
+                (0.9885849056603774, 0.7216037735849057),
+                (0.9885849056603774, 0.6689622641509434),
+                (0.5699056603773586, 0.6689622641509434),
+                (0.5699056603773586, 0.7216037735849057))),
+    LineString(((0.8684952830188680, 0.6952830188679245),
+                (0.8680655198743615, 0.6865349890935113),
+                (0.8667803692948564, 0.6778712076279851),
+                (0.8646522079829676, 0.6693751114229638),
+                (0.8645044888670096, 0.6689622641509434))),
+    LineString(((0.9874952830188680, 0.6952830188679245),
+                (0.9864925023483531, 0.6748709493942936),
+                (0.9856160316877274, 0.6689622641509434))),
+
+]
+
+for geo in geoms:
+    s.insert(geo)
+
+current_pt = (0, 0)
+pt, geo = s.nearest(current_pt)
+while geo is not None:
+    print pt, geo
+    print "OBJECTS BEFORE:", s.objects
+
+    #geo.coords = list(geo.coords[::-1])
+    s.remove(geo)
+
+    print "OBJECTS AFTER:", s.objects
+    current_pt = geo.coords[-1]
+    pt, geo = s.nearest(current_pt)