diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index 409e46b2..60eb99be 100644 --- a/FlatCAMDraw.py +++ b/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 = [] diff --git a/camlib.py b/camlib.py index 1985cdcc..416ff517 100644 --- a/camlib.py +++ b/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]] + pt, geo = storage.nearest(current_pt) + try: + while True: + path_count += 1 + print "Current: ", "(%.3f, %.3f)" % current_pt - # 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 + # TODO: There shoudn't be any None in geometry.flatten() + # if geo is None: + # storage.remove(geo) + # continue - # Reverse if end is closest. - if distance2start > distance2stop: - print " Reversing!" - geo.coords = list(geo.coords)[::-1] + # Remove before modifying, otherwise + # deletion will fail. + storage.remove(geo) - # 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)))) + if list(pt) == list(geo.coords[-1]): + print "Reversing" + geo.coords = list(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)) + # 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] + + # 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): diff --git a/doc/source/planning.rst b/doc/source/planning.rst index 7a3a87f4..4d9ea451 100644 --- a/doc/source/planning.rst +++ b/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. diff --git a/tests/test_fcrts.py b/tests/test_fcrts.py new file mode 100644 index 00000000..7c4afbbb --- /dev/null +++ b/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)