From 5fe50dbcd17f9467d24b008bdff7bc189e0e4996 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 16 Jun 2023 22:37:02 +0300 Subject: [PATCH] - NCC Tool: made a small optimization by adding a simplification in the clearing geometry --- CHANGELOG.md | 4 + appEditors/geo_plugins/GeoPaintPlugin.py | 6 +- appPlugins/ToolMilling.py | 48 +++--- appPlugins/ToolNCC.py | 186 +++++++++++++---------- appPlugins/ToolPaint.py | 126 +++++++-------- camlib.py | 163 ++++++++------------ 6 files changed, 260 insertions(+), 273 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a600bea2..b2feb7fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +16.06.2023 + +- NCC Tool: made a small optimization by adding a simplification in the clearing geometry + 15.06.2023 - a bit of optimization in camblib.clear_polygon() method diff --git a/appEditors/geo_plugins/GeoPaintPlugin.py b/appEditors/geo_plugins/GeoPaintPlugin.py index b12e2e84..32463883 100644 --- a/appEditors/geo_plugins/GeoPaintPlugin.py +++ b/appEditors/geo_plugins/GeoPaintPlugin.py @@ -188,17 +188,17 @@ class PaintOptionsTool(AppToolEditor): poly_buf = Polygon(geo_obj).buffer(-margin) if method == _("Seed"): - cp = Geometry.clear_polygon2( + cp = Geometry.clear_polygon_seed( geo_editor, polygon_to_clear=poly_buf, tooldia=tooldia, steps_per_circle=geo_editor.app.options["geometry_circle_steps"], overlap=overlap, contour=contour, connect=connect) elif method == _("Lines"): - cp = Geometry.clear_polygon3( + cp = Geometry.clear_polygon_lines( geo_editor, polygon=poly_buf, tooldia=tooldia, steps_per_circle=geo_editor.app.options["geometry_circle_steps"], overlap=overlap, contour=contour, connect=connect) else: - cp = Geometry.clear_polygon( + cp = Geometry.clear_polygon_shrink( geo_editor, polygon=poly_buf, tooldia=tooldia, steps_per_circle=geo_editor.app.options["geometry_circle_steps"], overlap=overlap, contour=contour, connect=connect) diff --git a/appPlugins/ToolMilling.py b/appPlugins/ToolMilling.py index fcba89a2..d902347f 100644 --- a/appPlugins/ToolMilling.py +++ b/appPlugins/ToolMilling.py @@ -2869,9 +2869,9 @@ class ToolMilling(AppTool, Excellon): if self.app.abort_flag: # graceful abort requested by the user raise grace - geo_res = self.clear_polygon2(pp, seedpoint=pp.centroid, tooldia=mill_dia, overlap=over, - steps_per_circle=self.app.options['geometry_circle_steps'], - connect=conn, contour=cont, prog_plot=False) + geo_res = self.clear_polygon_seed(pp, seedpoint=pp.centroid, tooldia=mill_dia, overlap=over, + steps_per_circle=self.app.options['geometry_circle_steps'], + connect=conn, contour=cont, prog_plot=False) if geo_res: cp.append(geo_res) pol_nr += 1 @@ -3293,29 +3293,29 @@ class ToolMilling(AppTool, Excellon): # Type(cpoly) == AppRTreeStorage | None cpoly = None if paint_method == 0: # Standard - cpoly = self.clear_polygon(bbox, - tooldia=tooldia_val, - steps_per_circle=self.circle_steps, - overlap=overlap, - contour=True, - connect=True, - prog_plot=False) + cpoly = self.clear_polygon_shrink(bbox, + tooldia=tooldia_val, + steps_per_circle=self.circle_steps, + overlap=overlap, + contour=True, + connect=True, + prog_plot=False) elif paint_method == 1: # Seed - cpoly = self.clear_polygon2(bbox, - tooldia=tooldia_val, - steps_per_circle=self.circle_steps, - overlap=overlap, - contour=True, - connect=True, - prog_plot=False) + cpoly = self.clear_polygon_seed(bbox, + tooldia=tooldia_val, + steps_per_circle=self.circle_steps, + overlap=overlap, + contour=True, + connect=True, + prog_plot=False) elif paint_method == 2: # Lines - cpoly = self.clear_polygon3(bbox, - tooldia=tooldia_val, - steps_per_circle=self.circle_steps, - overlap=overlap, - contour=True, - connect=True, - prog_plot=False) + cpoly = self.clear_polygon_lines(bbox, + tooldia=tooldia_val, + steps_per_circle=self.circle_steps, + overlap=overlap, + contour=True, + connect=True, + prog_plot=False) if not cpoly or not cpoly.objects: self.app.inform.emit('[ERROR_NOTCL] %s' % _('Geometry could not be painted completely')) diff --git a/appPlugins/ToolNCC.py b/appPlugins/ToolNCC.py index 84c78b9b..0b727efc 100644 --- a/appPlugins/ToolNCC.py +++ b/appPlugins/ToolNCC.py @@ -2176,39 +2176,41 @@ class NonCopperClear(AppTool, Gerber): return empty, warning_flag - def clear_polygon_worker(self, pol, tooldia, ncc_method, ncc_overlap, ncc_connect, ncc_contour, prog_plot): + def clear_polygon_worker(self, pol, tooldia, ncc_method, ncc_overlap, ncc_connect, ncc_contour, prog_plot, + simplify_tol=0.0): cp = None if ncc_method == 0: # standard try: - cp = self.clear_polygon(pol, tooldia, - steps_per_circle=self.circle_steps, - overlap=ncc_overlap, contour=ncc_contour, - connect=ncc_connect, - prog_plot=prog_plot) + cp = self.clear_polygon_shrink(pol, tooldia, + steps_per_circle=self.circle_steps, + overlap=ncc_overlap, contour=ncc_contour, + connect=ncc_connect, + simplify_tol=simplify_tol, + prog_plot=prog_plot) except grace: return "fail" except Exception as ee: self.app.log.error("NonCopperClear.clear_polygon_worker() Standard --> %s" % str(ee)) elif ncc_method == 1: # seed try: - cp = self.clear_polygon2(pol, tooldia, - steps_per_circle=self.circle_steps, - overlap=ncc_overlap, contour=ncc_contour, - connect=ncc_connect, - prog_plot=prog_plot) + cp = self.clear_polygon_seed(pol, tooldia, + steps_per_circle=self.circle_steps, + overlap=ncc_overlap, contour=ncc_contour, + connect=ncc_connect, + prog_plot=prog_plot) except grace: return "fail" except Exception as ee: self.app.log.error("NonCopperClear.clear_polygon_worker() Seed --> %s" % str(ee)) elif ncc_method == 2: # Lines try: - cp = self.clear_polygon3(pol, tooldia, - steps_per_circle=self.circle_steps, - overlap=ncc_overlap, contour=ncc_contour, - connect=ncc_connect, - prog_plot=prog_plot) + cp = self.clear_polygon_lines(pol, tooldia, + steps_per_circle=self.circle_steps, + overlap=ncc_overlap, contour=ncc_contour, + connect=ncc_connect, + prog_plot=prog_plot) except grace: return "fail" except Exception as ee: @@ -2216,37 +2218,38 @@ class NonCopperClear(AppTool, Gerber): elif ncc_method == 3: # Combo try: self.app.inform.emit(_("Clearing the polygon with the method: lines.")) - cp = self.clear_polygon3(pol, tooldia, - steps_per_circle=self.circle_steps, - overlap=ncc_overlap, contour=ncc_contour, - connect=ncc_connect, - prog_plot=prog_plot) + cp = self.clear_polygon_lines(pol, tooldia, + steps_per_circle=self.circle_steps, + overlap=ncc_overlap, contour=ncc_contour, + connect=ncc_connect, + prog_plot=prog_plot) if cp and cp.objects: pass else: self.app.inform.emit(_("Failed. Clearing the polygon with the method: seed.")) - cp = self.clear_polygon2(pol, tooldia, - steps_per_circle=self.circle_steps, - overlap=ncc_overlap, contour=ncc_contour, - connect=ncc_connect, - prog_plot=prog_plot) + cp = self.clear_polygon_seed(pol, tooldia, + steps_per_circle=self.circle_steps, + overlap=ncc_overlap, contour=ncc_contour, + connect=ncc_connect, + prog_plot=prog_plot) if cp and cp.objects: pass else: self.app.inform.emit(_("Failed. Clearing the polygon with the method: standard.")) - cp = self.clear_polygon(pol, tooldia, - steps_per_circle=self.circle_steps, - overlap=ncc_overlap, contour=ncc_contour, - connect=ncc_connect, - prog_plot=prog_plot) + cp = self.clear_polygon_shrink(pol, tooldia, + steps_per_circle=self.circle_steps, + overlap=ncc_overlap, contour=ncc_contour, + connect=ncc_connect, + prog_plot=prog_plot) except grace: return "fail" except Exception as ee: self.app.log.error("NonCopperClear.clear_polygon_worker() Combo --> %s" % str(ee)) if cp and cp.objects: - return list(cp.get_objects()) + ret_val = list(cp.get_objects()) + return ret_val else: pt = pol.representative_point() coords = (pt.x, pt.y) @@ -2296,6 +2299,9 @@ class NonCopperClear(AppTool, Gerber): ncc_select = self.ui.select_combo.get_value() rest_machining_choice = self.ui.ncc_rest_cb.get_value() + # TODO this should be in preferences and in the UI + simplification_value = 0.02 + # determine if to use the progressive plotting prog_plot = True if self.app.options["tools_ncc_plotting"] == 'progressive' else False @@ -2408,9 +2414,7 @@ class NonCopperClear(AppTool, Gerber): old_disp_number = 0 self.app.log.warning("Total number of polygons to be cleared. %s" % str(geo_len)) - tool_empty_area = [] - if area.geoms: - tool_empty_area = flatten_shapely_geometry(area.geoms) + tool_empty_area = flatten_shapely_geometry(area.geoms) if tool_empty_area: pol_nr = 0 @@ -2425,7 +2429,7 @@ class NonCopperClear(AppTool, Gerber): # clean the polygon p = p.buffer(0.0000001) - p = flatten_shapely_geometry(p) + p = flatten_shapely_geometry(p, simplify_tolerance=simplification_value) poly_failed = 0 for pol in p: @@ -2438,6 +2442,7 @@ class NonCopperClear(AppTool, Gerber): ncc_overlap=ncc_overlap, ncc_connect=ncc_connect, ncc_contour=ncc_contour, + simplify_tol=simplification_value, prog_plot=prog_plot) if res is not None: cleared_geo += res @@ -2447,17 +2452,23 @@ class NonCopperClear(AppTool, Gerber): self.app.log.warning( "Expected geo is a Polygon. Instead got a %s" % str(type(pol))) + pol_nr += 1 + disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) + # log.debug("Polygons cleared: %d" % pol_nr) + + if old_disp_number < disp_number <= 100: + self.app.proc_container.update_view_text(' %d%%' % disp_number) + old_disp_number = disp_number + # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number)) + if poly_failed > 0: app_obj.poly_not_cleared = True - pol_nr += 1 - disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) - # log.debug("Polygons cleared: %d" % pol_nr) - - if old_disp_number < disp_number <= 100: - self.app.proc_container.update_view_text(' %d%%' % disp_number) - old_disp_number = disp_number - # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number)) + l_coords = 0 + for i in range(len(cleared_geo)): + l_coords += len(cleared_geo[i].coords) + self.app.log.debug( + "NCC Tool.clear_copper.gen_clear_area() -> Number of cleared geo coords: %s" % str(l_coords)) # check if there is a geometry at all in the cleared geometry if cleared_geo: @@ -2667,7 +2678,7 @@ class NonCopperClear(AppTool, Gerber): # cleared with the current tool. this tremendously reduce the clearing time check_dist = -tool / 2 check_buff = p.buffer(check_dist, self.circle_steps) - check_buff = flatten_shapely_geometry(check_buff) + check_buff = flatten_shapely_geometry(check_buff, simplify_tolerance=simplification_value) if not check_buff: continue @@ -2688,6 +2699,7 @@ class NonCopperClear(AppTool, Gerber): ncc_overlap=ncc_overlap, ncc_connect=ncc_connect, ncc_contour=ncc_contour, + simplify_tol=simplification_value, prog_plot=prog_plot) if res is not None: @@ -2724,6 +2736,12 @@ class NonCopperClear(AppTool, Gerber): else: app_obj.log.warning("The area to be cleared has no polygons.") + l_coords = 0 + for i in range(len(cleared_geo)): + l_coords += len(cleared_geo[i].coords) + self.app.log.debug( + "NCC Tool.clear_copper.gen_clear_area_rest() -> Number of cleared geo coords: %s" % str(l_coords)) + # # Area to clear next # try: # # buffered_cleared = unary_union(cleared_geo).buffer(tool / 2.0) @@ -2732,11 +2750,11 @@ class NonCopperClear(AppTool, Gerber): # except Exception as e: # self.app.log.error("Creating new area failed due of: %s" % str(e)) - new_area = MultiPolygon([line.buffer(tool / 1.9999999) for line in cleared_geo]) + new_area = MultiPolygon([line.buffer(tool / 2) for line in cleared_geo]) new_area = new_area.buffer(0.0000001) area = area.difference(new_area) - area = flatten_shapely_geometry(area) + area = flatten_shapely_geometry(area, simplify_tolerance=simplification_value) new_area = [pol for pol in area if pol.is_valid and not pol.is_empty] area = MultiPolygon(new_area) @@ -2799,7 +2817,7 @@ class NonCopperClear(AppTool, Gerber): except TypeError: geo_obj.solid_geometry.append(geo_obj.tools[tool_uid]['solid_geometry']) else: - # I will use this variable for this purpose although it was meant for something else + # I will use this variable for this purpose, although it was meant for something else # signal that we have no geo in the object therefore don't create it app_obj.poly_not_cleared = False return "fail" @@ -3247,17 +3265,17 @@ class NonCopperClear(AppTool, Gerber): poly_processed = [] if isinstance(p, Polygon): if ncc_method == 0: # standard - cp = self.clear_polygon(p, tool, self.circle_steps, - overlap=overlap, contour=contour, connect=connect, - prog_plot=False) + cp = self.clear_polygon_shrink(p, tool, self.circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) elif ncc_method == 1: # seed - cp = self.clear_polygon2(p, tool, self.circle_steps, - overlap=overlap, contour=contour, connect=connect, - prog_plot=False) + cp = self.clear_polygon_seed(p, tool, self.circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) else: - cp = self.clear_polygon3(p, tool, self.circle_steps, - overlap=overlap, contour=contour, connect=connect, - prog_plot=False) + cp = self.clear_polygon_lines(p, tool, self.circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) if cp: cleared_geo += list(cp.get_objects()) poly_processed.append(True) @@ -3614,20 +3632,20 @@ class NonCopperClear(AppTool, Gerber): if isinstance(p, Polygon): try: if ncc_method == 0: # standard - cp = self.clear_polygon(p, tool_used, - self.circle_steps, - overlap=overlap, contour=contour, connect=connect, - prog_plot=False) + cp = self.clear_polygon_shrink(p, tool_used, + self.circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) elif ncc_method == 1: # seed - cp = self.clear_polygon2(p, tool_used, - self.circle_steps, - overlap=overlap, contour=contour, connect=connect, - prog_plot=False) + cp = self.clear_polygon_seed(p, tool_used, + self.circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) else: - cp = self.clear_polygon3(p, tool_used, - self.circle_steps, - overlap=overlap, contour=contour, connect=connect, - prog_plot=False) + cp = self.clear_polygon_lines(p, tool_used, + self.circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) cleared_geo.append(list(cp.get_objects())) except Exception as ee: self.app.log.error("Polygon can't be cleared. %s" % str(ee)) @@ -3643,23 +3661,23 @@ class NonCopperClear(AppTool, Gerber): try: if ncc_method == 0: # 'standard' - cp = self.clear_polygon(poly_p, tool_used, - self.circle_steps, - overlap=overlap, contour=contour, - connect=connect, - prog_plot=False) + cp = self.clear_polygon_shrink(poly_p, tool_used, + self.circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) elif ncc_method == 1: # 'seed' - cp = self.clear_polygon2(poly_p, tool_used, - self.circle_steps, - overlap=overlap, contour=contour, - connect=connect, - prog_plot=False) + cp = self.clear_polygon_seed(poly_p, tool_used, + self.circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) else: - cp = self.clear_polygon3(poly_p, tool_used, - self.circle_steps, - overlap=overlap, contour=contour, - connect=connect, - prog_plot=False) + cp = self.clear_polygon_lines(poly_p, tool_used, + self.circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) cleared_geo.append(list(cp.get_objects())) except Exception as eee: self.app.log.error("Polygon can't be cleared. %s" % str(eee)) diff --git a/appPlugins/ToolPaint.py b/appPlugins/ToolPaint.py index b9689f55..41605877 100644 --- a/appPlugins/ToolPaint.py +++ b/appPlugins/ToolPaint.py @@ -1620,13 +1620,13 @@ class ToolPaint(AppTool, Gerber): if paint_method == 0: # _("Standard") try: # Type(cp) == AppRTreeStorage | None - cpoly = self.clear_polygon(polyg, - tooldia=tooldiameter, - steps_per_circle=self.circle_steps, - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) + cpoly = self.clear_polygon_shrink(polyg, + tooldia=tooldiameter, + steps_per_circle=self.circle_steps, + overlap=over, + contour=cont, + connect=conn, + prog_plot=prog_plot) except grace: return "fail" except Exception as ee: @@ -1634,13 +1634,13 @@ class ToolPaint(AppTool, Gerber): elif paint_method == 1: # _("Seed") try: # Type(cp) == AppRTreeStorage | None - cpoly = self.clear_polygon2(polyg, - tooldia=tooldiameter, - steps_per_circle=self.circle_steps, - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) + cpoly = self.clear_polygon_seed(polyg, + tooldia=tooldiameter, + steps_per_circle=self.circle_steps, + overlap=over, + contour=cont, + connect=conn, + prog_plot=prog_plot) except grace: return "fail" except Exception as ee: @@ -1648,13 +1648,13 @@ class ToolPaint(AppTool, Gerber): elif paint_method == 2: # _("Lines") try: # Type(cp) == AppRTreeStorage | None - cpoly = self.clear_polygon3(polyg, - tooldia=tooldiameter, - steps_per_circle=self.circle_steps, - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) + cpoly = self.clear_polygon_lines(polyg, + tooldia=tooldiameter, + steps_per_circle=self.circle_steps, + overlap=over, + contour=cont, + connect=conn, + prog_plot=prog_plot) except grace: return "fail" except Exception as ee: @@ -1719,36 +1719,36 @@ class ToolPaint(AppTool, Gerber): for elem in flash_el_dict[ap_type]: if 'solid' in elem: if ap_type == 'C': - f_o = self.clear_polygon2(elem['solid'], - tooldia=tooldiameter, - steps_per_circle=self.app.options[ + f_o = self.clear_polygon_seed(elem['solid'], + tooldia=tooldiameter, + steps_per_circle=self.app.options[ "geometry_circle_steps"], - overlap=over, - contour=True, - connect=conn, - prog_plot=prog_plot) + overlap=over, + contour=True, + connect=conn, + prog_plot=prog_plot) pads_lines_list += [p for p in f_o.get_objects() if p] # this is the same as above but I keep it in case I will modify something in the future elif ap_type == 'O': - f_o = self.clear_polygon2(elem['solid'], - tooldia=tooldiameter, - steps_per_circle=self.app.options[ + f_o = self.clear_polygon_seed(elem['solid'], + tooldia=tooldiameter, + steps_per_circle=self.app.options[ "geometry_circle_steps"], - overlap=over, - contour=True, - connect=conn, - prog_plot=prog_plot) + overlap=over, + contour=True, + connect=conn, + prog_plot=prog_plot) pads_lines_list += [p for p in f_o.get_objects() if p] elif ap_type == 'R': - f_o = self.clear_polygon3(elem['solid'], - tooldia=tooldiameter, - steps_per_circle=self.app.options[ + f_o = self.clear_polygon_lines(elem['solid'], + tooldia=tooldiameter, + steps_per_circle=self.app.options[ "geometry_circle_steps"], - overlap=over, - contour=True, - connect=conn, - prog_plot=prog_plot) + overlap=over, + contour=True, + connect=conn, + prog_plot=prog_plot) pads_lines_list += [p for p in f_o.get_objects() if p] except grace: @@ -1801,36 +1801,36 @@ class ToolPaint(AppTool, Gerber): elif paint_method == 4: # _("Combo") try: self.app.inform.emit(_("Painting polygon with method: lines.")) - cpoly = self.clear_polygon3(polyg, - tooldia=tooldiameter, - steps_per_circle=self.circle_steps, - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) + cpoly = self.clear_polygon_lines(polyg, + tooldia=tooldiameter, + steps_per_circle=self.circle_steps, + overlap=over, + contour=cont, + connect=conn, + prog_plot=prog_plot) if cpoly and cpoly.objects: pass else: self.app.inform.emit(_("Failed. Painting polygon with method: seed.")) - cpoly = self.clear_polygon2(polyg, - tooldia=tooldiameter, - steps_per_circle=self.circle_steps, - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) + cpoly = self.clear_polygon_seed(polyg, + tooldia=tooldiameter, + steps_per_circle=self.circle_steps, + overlap=over, + contour=cont, + connect=conn, + prog_plot=prog_plot) if cpoly and cpoly.objects: pass else: self.app.inform.emit(_("Failed. Painting polygon with method: standard.")) - cpoly = self.clear_polygon(polyg, - tooldia=tooldiameter, - steps_per_circle=self.circle_steps, - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) + cpoly = self.clear_polygon_shrink(polyg, + tooldia=tooldiameter, + steps_per_circle=self.circle_steps, + overlap=over, + contour=cont, + connect=conn, + prog_plot=prog_plot) except grace: return "fail" except Exception as ee: diff --git a/camlib.py b/camlib.py index 3d54da0c..21883de9 100644 --- a/camlib.py +++ b/camlib.py @@ -1424,8 +1424,8 @@ class Geometry(object): boundary = self.solid_geometry.envelope return boundary.difference(self.solid_geometry) - def clear_polygon(self, polygon, tooldia, steps_per_circle, overlap=0.15, connect=True, contour=True, - prog_plot=False): + def clear_polygon_shrink(self, polygon, tooldia, steps_per_circle, overlap=0.15, connect=True, contour=True, + simplify_tol=0.0, prog_plot=False): """ Creates geometry inside a polygon for a tool to cover the whole area. @@ -1445,7 +1445,7 @@ class Geometry(object): :return: """ - # log.debug("camlib.clear_polygon()") + # log.debug("camlib.clear_polygon_shrink()") # The toolpaths # Index first and last points in paths @@ -1457,38 +1457,24 @@ class Geometry(object): # Can only result in a Polygon or MultiPolygon # NOTE: The resulting polygon can be "empty". - current = polygon.buffer((-tooldia / 1.999999), int(steps_per_circle)) + current = polygon.buffer((-tooldia / 2), int(steps_per_circle)) current = flatten_shapely_geometry(current) - # if current.area == 0: - # # Otherwise, trying to insert current.exterior == None - # # into the FlatCAMStorage will fail. - # # print("Area is None") - # return None - for p in current: geoms.insert(p.exterior) for i in p.interiors: geoms.insert(i) - if self.app.abort_flag: - # graceful abort requested by the user - raise grace - - # provide the app with a way to process the GUI events when in a blocking loop - QtWidgets.QApplication.processEvents() - for cl_pol in current: while True: if self.app.abort_flag: # graceful abort requested by the user raise grace - # provide the app with a way to process the GUI events when in a blocking loop QtWidgets.QApplication.processEvents() cl_pol = cl_pol.buffer(-tooldia * (1 - overlap), int(steps_per_circle)) - cl_pol_list = flatten_shapely_geometry(cl_pol) + cl_pol_list = flatten_shapely_geometry(cl_pol, simplify_tolerance=simplify_tol) added_flag = False for tiny_pol in cl_pol_list: @@ -1505,7 +1491,7 @@ class Geometry(object): if added_flag is False: break - cl_pol = MultiPolygon(cl_pol_list) + cl_pol = unary_union(cl_pol_list) # if isinstance(cl_pol, MultiPolygon): # cl_pol = flatten_shapely_geometry(cl_pol) @@ -1540,7 +1526,7 @@ class Geometry(object): # break if not geoms.objects: - self.app.log.debug("camlib.Geometry.clear_polygon() --> Current Area is zero") + self.app.log.debug("camlib.Geometry.clear_polygon_shrink() --> Current Area is zero") return if prog_plot: @@ -1553,8 +1539,8 @@ class Geometry(object): return geoms - def clear_polygon2(self, polygon_to_clear, tooldia, steps_per_circle, seedpoint=None, overlap=0.15, - connect=True, contour=True, prog_plot=False): + def clear_polygon_seed(self, polygon_to_clear, tooldia, steps_per_circle, seedpoint=None, overlap=0.15, + connect=True, contour=True, simplify_tol=0.0, prog_plot=False): """ Creates geometry inside a polygon for a tool to cover the whole area. @@ -1576,7 +1562,7 @@ class Geometry(object): :rtype: AppRTreeStorage | None """ - # log.debug("camlib.clear_polygon2()") + # log.debug("camlib.clear_polygon_seed()") # Current buffer radius radius = tooldia / 2 * (1 - overlap) @@ -1591,6 +1577,8 @@ class Geometry(object): # Path margin path_margin = polygon_to_clear.buffer(-tooldia / 2, int(steps_per_circle)) + path_margin = flatten_shapely_geometry(path_margin, simplify_tolerance=simplify_tol) + path_margin = MultiPolygon(path_margin) if path_margin.is_empty or path_margin is None: return None @@ -1610,34 +1598,29 @@ class Geometry(object): QtWidgets.QApplication.processEvents() path = Point(seedpoint).buffer(radius, int(steps_per_circle)).exterior + path = path.simplify(simplify_tol) path = path.intersection(path_margin) # Touches polygon? if path.is_empty: break - else: - # geom_elems.append(path) - # geom_elems.insert(path) - # path can be a collection of paths. - path_geometry = path.geoms if isinstance(path, MultiLineString) else path - try: - for p in path_geometry: - geom_elems.insert(p) - if prog_plot: - self.plot_temp_shapes(p) - except TypeError: - geom_elems.insert(path_geometry) - if prog_plot: - self.plot_temp_shapes(path_geometry) + # path can be a collection of paths. + path_geometry = flatten_shapely_geometry(path, simplify_tolerance=simplify_tol) + for p in path_geometry: + geom_elems.insert(p) if prog_plot: - self.temp_shapes.redraw() + self.plot_temp_shapes(p) + + if prog_plot: + self.temp_shapes.redraw() radius += tooldia * (1 - overlap) # Clean inside edges (contours) of the original polygon if contour: buffered_poly = autolist(polygon_to_clear.buffer(-tooldia / 2, int(steps_per_circle))) + buffered_poly = [x.simplify(simplify_tol) for x in buffered_poly] outer_edges = [x.exterior for x in buffered_poly] inner_edges = [] @@ -1645,7 +1628,7 @@ class Geometry(object): for x in buffered_poly: for y in x.interiors: # Over interiors of each polygon inner_edges.append(y) - # geom_elems += outer_edges + inner_edges + for g in outer_edges + inner_edges: if g and not g.is_empty: geom_elems.insert(g) @@ -1668,8 +1651,8 @@ class Geometry(object): return geom_elems - def clear_polygon3(self, polygon, tooldia, steps_per_circle, overlap=0.15, connect=True, contour=True, - prog_plot=False): + def clear_polygon_lines(self, polygon, tooldia, steps_per_circle, overlap=0.15, connect=True, contour=True, + simplify_tol=0.0, prog_plot=False): """ Creates geometry inside a polygon for a tool to cover the whole area. @@ -1687,12 +1670,12 @@ class Geometry(object): :return: """ - # log.debug("camlib.clear_polygon3()") + # log.debug("camlib.clear_polygon_lines()") if not isinstance(polygon, Polygon): - self.app.log.debug("camlib.Geometry.clear_polygon3() --> Not a Polygon but %s" % str(type(polygon))) + self.app.log.debug("camlib.Geometry.clear_polygon_lines() --> Not a Polygon but %s" % str(type(polygon))) return None - # ## The toolpaths + # The toolpaths # Index first and last points in paths def get_pts(o): return [o.coords[0], o.coords[-1]] @@ -1707,8 +1690,9 @@ class Geometry(object): try: margin_poly = polygon.buffer(-tooldia / 1.99999999, (int(steps_per_circle))) + margin_poly = margin_poly.simplify(simplify_tol) except Exception: - self.app.log.debug("camlib.Geometry.clear_polygon3() --> Could not buffer the Polygon") + self.app.log.debug("camlib.Geometry.clear_polygon_lines() --> Could not buffer the Polygon") return None # decide the direction of the lines @@ -1726,7 +1710,8 @@ class Geometry(object): line = LineString([(left, y), (right, y)]) line = line.intersection(margin_poly) - lines_trimmed.append(line) + line = flatten_shapely_geometry(line, simplify_tolerance=simplify_tol) + lines_trimmed += line y -= tooldia * (1 - overlap) if prog_plot: self.plot_temp_shapes(line) @@ -1737,18 +1722,13 @@ class Geometry(object): line = LineString([(left, y), (right, y)]) line = line.intersection(margin_poly) - lines_geometry = line.geoms if isinstance(line, MultiLineString) else line - try: - for ll in lines_geometry: - lines_trimmed.append(ll) - if prog_plot: - self.plot_temp_shapes(ll) - except TypeError: - lines_trimmed.append(lines_geometry) + lines_geometry = flatten_shapely_geometry(line, simplify_tolerance=simplify_tol) + for ll in lines_geometry: + lines_trimmed.append(ll) if prog_plot: - self.plot_temp_shapes(lines_geometry) + self.plot_temp_shapes(ll) except Exception as e: - self.app.log.error('camlib.Geometry.clear_polygon3() Processing poly --> %s' % str(e)) + self.app.log.error('camlib.Geometry.clear_polygon_lines() Processing poly --> %s' % str(e)) return None else: # First line @@ -1764,7 +1744,8 @@ class Geometry(object): line = LineString([(x, top), (x, bot)]) line = line.intersection(margin_poly) - lines_trimmed.append(line) + line = flatten_shapely_geometry(line, simplify_tolerance=simplify_tol) + lines_trimmed += line x += tooldia * (1 - overlap) if prog_plot: self.plot_temp_shapes(line) @@ -1775,18 +1756,13 @@ class Geometry(object): line = LineString([(x, top), (x, bot)]) line = line.intersection(margin_poly) - lines_geometry = line.geoms if isinstance(line, MultiLineString) else line - try: - for ll in lines_geometry: - lines_trimmed.append(ll) - if prog_plot: - self.plot_temp_shapes(ll) - except TypeError: - lines_trimmed.append(lines_geometry) + lines_geometry = flatten_shapely_geometry(line, simplify_tolerance=simplify_tol) + for ll in lines_geometry: + lines_trimmed.append(ll) if prog_plot: - self.plot_temp_shapes(lines_geometry) + self.plot_temp_shapes(ll) except Exception as e: - self.app.log.error('camlib.Geometry.clear_polygon3() Processing poly --> %s' % str(e)) + self.app.log.error('camlib.Geometry.clear_polygon_lines() Processing poly --> %s' % str(e)) return None if prog_plot: @@ -1795,39 +1771,23 @@ class Geometry(object): lines_trimmed = unary_union(lines_trimmed) # Add lines to storage - try: - lines_t_geo = lines_trimmed.geoms if isinstance(lines_trimmed, MultiLineString) else lines_trimmed - for line in lines_t_geo: - if isinstance(line, LineString) or isinstance(line, LinearRing): - if not line.is_empty: - geoms.insert(line) - else: - self.app.log.debug("camlib.Geometry.clear_polygon3(). Not a line: %s" % str(type(line))) - except TypeError: - # in case lines_trimmed are not iterable (Linestring, LinearRing) - if not lines_trimmed.is_empty: - geoms.insert(lines_trimmed) + lines_t_geo = flatten_shapely_geometry(lines_trimmed, simplify_tolerance=simplify_tol) + for line in lines_t_geo: + if isinstance(line, LineString) or isinstance(line, LinearRing): + if not line.is_empty: + geoms.insert(line) + else: + self.app.log.debug("camlib.Geometry.clear_polygon_lines(). Not a line: %s" % str(type(line))) # Add margin (contour) to storage if contour: - try: - margin_poly_geo = margin_poly.geoms if isinstance(margin_poly, MultiPolygon) else margin_poly - for poly in margin_poly_geo: - if isinstance(poly, Polygon) and not poly.is_empty: - geoms.insert(poly.exterior) - if prog_plot: - self.plot_temp_shapes(poly.exterior) - for ints in poly.interiors: - geoms.insert(ints) - if prog_plot: - self.plot_temp_shapes(ints) - except TypeError: - if isinstance(margin_poly, Polygon) and not margin_poly.is_empty: - marg_ext = margin_poly.exterior - geoms.insert(marg_ext) + margin_poly_geo = flatten_shapely_geometry(margin_poly, simplify_tolerance=simplify_tol) + for poly in margin_poly_geo: + if isinstance(poly, Polygon) and not poly.is_empty: + geoms.insert(poly.exterior) if prog_plot: - self.plot_temp_shapes(margin_poly.exterior) - for ints in margin_poly.interiors: + self.plot_temp_shapes(poly.exterior) + for ints in poly.interiors: geoms.insert(ints) if prog_plot: self.plot_temp_shapes(ints) @@ -7825,11 +7785,13 @@ class CNCjob(Geometry): self.app.proc_container.new_text = '' -def flatten_shapely_geometry(geometry): +def flatten_shapely_geometry(geometry, simplify_tolerance=0.0): """ :param geometry: :type geometry: + :param simplify_tolerance: if non-zero then simplify the geometry + :type simplify_tolerance: float :return: :rtype: """ @@ -7840,7 +7802,10 @@ def flatten_shapely_geometry(geometry): flat_list += flatten_shapely_geometry(geo) except TypeError: if geometry and not geometry.is_empty: - flat_list.append(geometry) + if simplify_tolerance > 0.0: + flat_list.append(geometry.simplify(simplify_tolerance)) + else: + flat_list.append(geometry) return flat_list