浏览代码

Using FlatCAMRTreeStorage in DrawingTool.

jpcaram 11 年之前
父节点
当前提交
c20c6b0abf
共有 3 个文件被更改,包括 192 次插入94 次删除
  1. 1 0
      .gitignore
  2. 131 91
      FlatCAMDraw.py
  3. 60 3
      camlib.py

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+*.pyc

+ 131 - 91
FlatCAMDraw.py

@@ -21,12 +21,52 @@ from rtree import index as rtindex
 
 class DrawToolShape(object):
 
+    @staticmethod
+    def get_pts(o):
+        """
+        Returns a list of all points in the object, where
+        the object can be a Polygon, Not a polygon, or a list
+        of such. Search is done recursively.
+
+        :param: geometric object
+        :return: List of points
+        :rtype: list
+        """
+        pts = []
+
+        ## Iterable: descend into each item.
+        try:
+            for subo in o:
+                pts += DrawToolShape.get_pts(subo)
+
+        ## Non-iterable
+        except TypeError:
+
+            ## DrawToolShape: descend into .geo.
+            if isinstance(o, DrawToolShape):
+                pts += DrawToolShape.get_pts(o.geo)
+
+            ## Descend into .exerior and .interiors
+            elif type(o) == Polygon:
+                pts += DrawToolShape.get_pts(o.exterior)
+                for i in o.interiors:
+                    pts += DrawToolShape.get_pts(i)
+
+            ## Has .coords: list them.
+            else:
+                pts += list(o.coords)
+
+        return pts
+
     def __init__(self, geo=[]):
 
         # Shapely type or list of such
         self.geo = geo
         self.utility = False
 
+    def get_all_points(self):
+        return DrawToolShape.get_pts(self)
+
 
 class DrawToolUtilityShape(DrawToolShape):
 
@@ -383,39 +423,26 @@ class FCPath(FCPolygon):
 class FCSelect(DrawTool):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
-        self.shape_buffer = self.draw_app.shape_buffer
+        self.storage = self.draw_app.storage
+        #self.shape_buffer = self.draw_app.shape_buffer
         self.selected = self.draw_app.selected
         self.start_msg = "Click on geometry to select"
 
     def click(self, point):
-        min_distance = Inf
-        closest_shape = None
-
-        for shape in self.shape_buffer:
+        _, closest_shape = self.storage.nearest(point)
 
-            # Remove all if 'control' is not help
-            if self.draw_app.key != 'control':
-                #shape["selected"] = False
-                self.draw_app.set_unselected(shape)
+        if self.draw_app.key != 'control':
+            self.draw_app.selected = []
 
-            # TODO: Do this with rtree?
-            dist = Point(point).distance(cascaded_union(shape.geo))
-            if dist < min_distance:
-                closest_shape = shape
-                min_distance = dist
+        self.draw_app.set_selected(closest_shape)
 
-        if closest_shape is not None:
-            #closest_shape["selected"] = True
-            self.draw_app.set_selected(closest_shape)
-            return "Shape selected."
-
-        return "Nothing selected."
+        return ""
 
 
 class FCMove(FCShapeTool):
     def __init__(self, draw_app):
         FCShapeTool.__init__(self, draw_app)
-        self.shape_buffer = self.draw_app.shape_buffer
+        #self.shape_buffer = self.draw_app.shape_buffer
         self.origin = None
         self.destination = None
         self.start_msg = "Click on reference point."
@@ -565,12 +592,8 @@ class FlatCAMDraw(QtCore.QObject):
         ### Data
         self.active_tool = None
 
-        ## List of shapes, None for removed ones. List
-        ## never decreases size.
-        self.main_index = []
-
-        ## List of shapes.
-        self.shape_buffer = []
+        self.storage = FlatCAMDraw.make_storage()
+        self.utility = []
 
         ## List of selected shapes.
         self.selected = []
@@ -580,9 +603,9 @@ class FlatCAMDraw(QtCore.QObject):
 
         self.key = None  # Currently pressed key
 
-        def make_callback(tool):
+        def make_callback(thetool):
             def f():
-                self.on_tool_select(tool)
+                self.on_tool_select(thetool)
             return f
 
         for tool in self.tools:
@@ -624,11 +647,42 @@ class FlatCAMDraw(QtCore.QObject):
     def activate(self):
         pass
 
+    def add_shape(self, shape):
+        """
+        Adds a shape to the shape storage.
+
+        :param shape: Shape to be added.
+        :type shape: DrawToolShape
+        :return: None
+        """
+
+        # List of DrawToolShape?
+        if isinstance(shape, list):
+            for subshape in shape:
+                self.add_shape(subshape)
+            return
+
+        assert isinstance(shape, DrawToolShape)
+        assert shape.geo is not None
+        assert (isinstance(shape.geo, list) and len(shape.geo) > 0) or not isinstance(shape.geo, list)
+
+        if isinstance(shape, DrawToolUtilityShape):
+            self.utility.append(shape)
+        else:
+            self.storage.insert(shape)
+
     def deactivate(self):
         self.clear()
         self.drawing_toolbar.setDisabled(True)
         self.snap_toolbar.setDisabled(True)  # TODO: Combine and move into tool
 
+    def delete_utility_geometry(self):
+        #for_deletion = [shape for shape in self.shape_buffer if shape.utility]
+        #for_deletion = [shape for shape in self.storage.get_objects() if shape.utility]
+        for_deletion = [shape for shape in self.utility]
+        for shape in for_deletion:
+            self.delete_shape(shape)
+
     def cutpath(self):
         selected = self.get_selected()
         tools = selected[1:]
@@ -653,7 +707,9 @@ class FlatCAMDraw(QtCore.QObject):
 
     def clear(self):
         self.active_tool = None
-        self.shape_buffer = []
+        #self.shape_buffer = []
+        self.selected = []
+        self.storage = FlatCAMDraw.make_storage()
         self.replot()
 
     def edit_fcgeometry(self, fcgeometry):
@@ -675,14 +731,13 @@ class FlatCAMDraw(QtCore.QObject):
                 geometry = [fcgeometry.solid_geometry]
 
         # Delete contents of editor.
-        self.shape_buffer = []
+        #self.shape_buffer = []
+        self.clear()
 
         # Link shapes into editor.
         for shape in geometry:
-            # self.shape_buffer.append({'geometry': shape,
-            #                           # 'selected': False,
-            #                           'utility': False})
-            self.shape_buffer.append(DrawToolShape(geometry))
+            #self.shape_buffer.append(DrawToolShape(geometry))
+            self.add_shape(DrawToolShape(shape.flatten()))
 
         self.replot()
         self.drawing_toolbar.setDisabled(False)
@@ -795,9 +850,7 @@ class FlatCAMDraw(QtCore.QObject):
         if isinstance(geo, DrawToolShape) and geo.geo is not None:
 
             # Remove any previous utility shape
-            for shape in self.shape_buffer:
-                if shape.utility:
-                    self.shape_buffer.remove(shape)
+            self.delete_utility_geometry()
 
             # Add the new utility shape
             self.add_shape(geo)
@@ -841,9 +894,8 @@ class FlatCAMDraw(QtCore.QObject):
             # TODO: ...?
             self.on_tool_select("select")
             self.app.info("Cancelled.")
-            for_deletion = [shape for shape in self.shape_buffer if shape.utility]
-            for shape in for_deletion:
-                self.shape_buffer.remove(shape)
+
+            self.delete_utility_geometry()
 
             self.replot()
             self.select_btn.setChecked(True)
@@ -961,49 +1013,24 @@ class FlatCAMDraw(QtCore.QObject):
         return plot_elements
         # self.canvas.auto_adjust_axes()
 
-    def add_shape(self, shape):
-        """
-        Adds a shape to the shape buffer and the rtree index.
-
-        :param shape: Shape to be added.
-        :type shape: DrawToolShape
-        :return: None
-        """
-
-        # List of DrawToolShape?
-        if isinstance(shape, list):
-            for subshape in shape:
-                self.add_shape(subshape)
-            return
-
-        assert isinstance(shape, DrawToolShape)
-        assert shape.geo is not None
-        assert (isinstance(shape.geo, list) and len(shape.geo) > 0) or not isinstance(shape.geo, list)
-
-        self.shape_buffer.append(shape)
-
-        # Do not add utility shapes to the index.
-        if not isinstance(shape, DrawToolUtilityShape):
-            self.main_index.append(shape)
-            self.add2index(len(self.main_index) - 1, shape)
-
     def plot_all(self):
         self.app.log.debug("plot_all()")
         self.axes.cla()
-        for shape in self.shape_buffer:
+        #for shape in self.shape_buffer:
+        for shape in self.storage.get_objects():
             if shape.geo is None:  # TODO: This shouldn't have happened
                 continue
 
-            if shape.utility:
-                self.plot_shape(geometry=shape.geo, linespec='k--', linewidth=1)
-                continue
-
             if shape in self.selected:
                 self.plot_shape(geometry=shape.geo, linespec='k-', linewidth=2)
                 continue
 
             self.plot_shape(geometry=shape.geo)
 
+        for shape in self.utility:
+            self.plot_shape(geometry=shape.geo, linespec='k--', linewidth=1)
+            continue
+
         self.canvas.auto_adjust_axes()
 
     def add2index(self, id, geo):
@@ -1076,26 +1103,29 @@ class FlatCAMDraw(QtCore.QObject):
         self.add_shape(self.active_tool.geometry)
 
         # Remove any utility shapes
-        for shape in self.shape_buffer:
-            if shape.utility:
-                self.shape_buffer.remove(shape)
+        self.delete_utility_geometry()
 
         self.replot()
         self.active_tool = type(self.active_tool)(self)
 
     def delete_shape(self, shape):
-        try:
-            # Remove from index list
-            shp_idx = self.main_index.index(shape)
-            self.main_index[shp_idx] = None
-
-            # Remove from rtree index
-            self.remove_from_index(shp_idx, shape)
-        except ValueError:
-            pass
+        # try:
+        #     # Remove from index list
+        #     shp_idx = self.main_index.index(shape)
+        #     self.main_index[shp_idx] = None
+        #
+        #     # Remove from rtree index
+        #     self.remove_from_index(shp_idx, shape)
+        # except ValueError:
+        #     pass
+        #
+        # if shape in self.shape_buffer:
+        #     self.shape_buffer.remove(shape)
+        if shape in self.utility:
+            self.utility.remove(shape)
+            return
 
-        if shape in self.shape_buffer:
-            self.shape_buffer.remove(shape)
+        self.storage.remove(shape)
 
         if shape in self.selected:
             self.selected.remove(shape)
@@ -1105,6 +1135,15 @@ class FlatCAMDraw(QtCore.QObject):
         self.axes = self.canvas.new_axes("draw")
         self.plot_all()
 
+    @staticmethod
+    def make_storage():
+
+        ## Shape storage.
+        storage = FlatCAMRTreeStorage()
+        storage.get_points = DrawToolShape.get_pts
+
+        return storage
+
     def set_selected(self, shape):
 
         # Remove and add to the end.
@@ -1134,14 +1173,13 @@ class FlatCAMDraw(QtCore.QObject):
         ### in the index.
         if self.options["corner_snap"]:
             try:
-                bbox = self.rtree_index.nearest((x, y), objects=True).next().bbox
-                nearest_pt = (bbox[0], bbox[1])
+                nearest_pt, shape = self.storage.nearest((x, y))
 
                 nearest_pt_distance = distance((x, y), nearest_pt)
                 if nearest_pt_distance <= self.options["snap_max"]:
                     snap_distance = nearest_pt_distance
                     snap_x, snap_y = nearest_pt
-            except StopIteration:
+            except (StopIteration, AssertionError):
                 pass
 
         ### Grid snap
@@ -1170,7 +1208,8 @@ class FlatCAMDraw(QtCore.QObject):
         :return: None
         """
         fcgeometry.solid_geometry = []
-        for shape in self.shape_buffer:
+        #for shape in self.shape_buffer:
+        for shape in self.storage.get_objects():
             fcgeometry.solid_geometry.append(shape.geo)
 
     def union(self):
@@ -1185,7 +1224,8 @@ class FlatCAMDraw(QtCore.QObject):
 
         # Delete originals.
         for shape in self.get_selected():
-            self.shape_buffer.remove(shape)
+            #self.shape_buffer.remove(shape)
+            self.delete_shape(shape)  # TODO: This will crash
 
         # Selected geometry is now gone!
         self.selected = []

+ 60 - 3
camlib.py

@@ -138,6 +138,47 @@ class Geometry(object):
         else:
             return self.solid_geometry.bounds
 
+    def flatten(self, geometry=None, reset=True):
+        if geometry is None:
+            geometry = self.solid_geometry
+
+        if reset:
+            self.flat_geometry = []
+
+        ## If iterable, expand recursively.
+        try:
+            for geo in geometry:
+                self.flatten(geometry=geo, reset=False)
+
+        ## Not iterable, do the actual indexing and add.
+        except TypeError:
+            if type(geometry) == Polygon:
+                self.flat_geometry.append(geometry)
+
+        return self.flat_geometry
+
+    def make2Dindex(self):
+
+        self.flatten()
+
+        def get_pts(o):
+            pts = []
+            if type(o) == Polygon:
+                g = o.exterior
+                pts += list(g.coords)
+                for i in o.interiors:
+                    pts += list(i.coords)
+            else:
+                pts += list(o.coords)
+            return pts
+
+        idx = FlatCAMRTreeStorage()
+        idx.get_points = get_pts
+        for shape in self.flat_geometry:
+            idx.insert(shape)
+        return idx
+
+
     def flatten_to_paths(self, geometry=None, reset=True):
         """
         Creates a list of non-iterable linear geometry elements and
@@ -3188,11 +3229,14 @@ def distance(pt1, pt2):
 
 
 class FlatCAMRTree(object):
+
     def __init__(self):
         self.rti = rtindex.Index()
         self.obj2points = []
         self.points2obj = []
 
+        self.get_points = lambda go: go.coords
+
     def grow_obj2points(self, idx):
         if len(self.obj2points) > idx:
             # len == 2, idx == 1, ok.
@@ -3207,15 +3251,18 @@ class FlatCAMRTree(object):
         self.grow_obj2points(objid)
         self.obj2points[objid] = []
 
-        for pt in obj.coords:
+        #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))
             self.points2obj.append(objid)
 
     def remove_obj(self, objid, obj):
         # Use all ptids to delete from index
-        for i in range(len(self.obj2points[objid])):
-            pt = obj.coords[i]
+        #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):
@@ -3241,9 +3288,19 @@ class FlatCAMRTreeStorage(FlatCAMRTree):
         return (o for o in self.objects if o is not None)
 
     def nearest(self, pt):
+        """
+        Returns the nearest matching points and the object
+        it belongs to.
+
+        :param pt: Query point.
+        :return: (match_x, match_y), Object owner of
+          matching point.
+        :rtype: tuple
+        """
         tidx = super(FlatCAMRTreeStorage, self).nearest(pt)
         return (tidx.bbox[0], tidx.bbox[1]), self.objects[tidx.object]
 
+
 class myO:
     def __init__(self, coords):
         self.coords = coords