From c20c6b0abfb730fbda11bdcfde794dae62d581e7 Mon Sep 17 00:00:00 2001 From: jpcaram Date: Wed, 31 Dec 2014 16:45:10 -0500 Subject: [PATCH] Using FlatCAMRTreeStorage in DrawingTool. --- .gitignore | 1 + FlatCAMDraw.py | 222 +++++++++++++++++++++++++++++-------------------- camlib.py | 63 +++++++++++++- 3 files changed, 192 insertions(+), 94 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7e99e367 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc \ No newline at end of file diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index 9918ed97..6314a6fd 100644 --- a/FlatCAMDraw.py +++ b/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 + _, closest_shape = self.storage.nearest(point) - for shape in self.shape_buffer: + if self.draw_app.key != 'control': + self.draw_app.selected = [] - # Remove all if 'control' is not help - if self.draw_app.key != 'control': - #shape["selected"] = False - self.draw_app.set_unselected(shape) + self.draw_app.set_selected(closest_shape) - # TODO: Do this with rtree? - dist = Point(point).distance(cascaded_union(shape.geo)) - if dist < min_distance: - closest_shape = shape - min_distance = dist - - 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 + # 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 - # 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) + 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 = [] diff --git a/camlib.py b/camlib.py index 575cfadc..1985cdcc 100644 --- a/camlib.py +++ b/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