From dea367b18511bbae1f5f36cc13f9efd9841f7a05 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Tue, 16 May 2023 04:34:32 +0300 Subject: [PATCH] - NCC Plugin: fixed the Standard method of copper clearing (both normal and with Rest machining) --- CHANGELOG.md | 4 ++ appPlugins/ToolNCC.py | 153 +++++++++++++++++++++--------------------- camlib.py | 101 +++++++++++++++------------- 3 files changed, 135 insertions(+), 123 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d076648a..cdbdbf94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +16.05.2023 + +- NCC Plugin: fixed the Standard method of copper clearing (both normal and with Rest machining) + 14.05.2023 - Isolation Plugin, NCC Plugin and Find Optimal Plugin: fixed not finding the optimal tool diameter (the one that will isolate completely a Gerber object) diff --git a/appPlugins/ToolNCC.py b/appPlugins/ToolNCC.py index 4d523cd6..9ffb3c01 100644 --- a/appPlugins/ToolNCC.py +++ b/appPlugins/ToolNCC.py @@ -2408,87 +2408,74 @@ 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: - if len(area.geoms) > 0: - pol_nr = 0 - for p in area.geoms: + tool_empty_area = flatten_shapely_geometry(area.geoms) + + if tool_empty_area: + pol_nr = 0 + for p in tool_empty_area: + # provide the app with a way to process the GUI events when in a blocking loop + if not run_threaded: + QtWidgets.QApplication.processEvents() + + if self.app.abort_flag: + # graceful abort requested by the user + raise grace + + # clean the polygon + p = p.buffer(0.0000001) + p = flatten_shapely_geometry(p) + + poly_failed = 0 + for pol in p: # provide the app with a way to process the GUI events when in a blocking loop - if not run_threaded: - QtWidgets.QApplication.processEvents() + QtWidgets.QApplication.processEvents() - if self.app.abort_flag: - # graceful abort requested by the user - raise grace + if pol is not None and pol.is_valid and isinstance(pol, Polygon): + res = self.clear_polygon_worker(pol=pol, tooldia=tool, + ncc_method=ncc_method, + ncc_overlap=ncc_overlap, + ncc_connect=ncc_connect, + ncc_contour=ncc_contour, + prog_plot=prog_plot) + if res is not None: + cleared_geo += res + else: + poly_failed += 1 + else: + self.app.log.warning( + "Expected geo is a Polygon. Instead got a %s" % str(type(pol))) - # clean the polygon - p = p.buffer(0) + if poly_failed > 0: + app_obj.poly_not_cleared = True - if p is not None and p.is_valid: - poly_failed = 0 - try: - for pol in p: - # provide the app with a way to process the GUI events when in a blocking loop - QtWidgets.QApplication.processEvents() + pol_nr += 1 + disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) + # log.debug("Polygons cleared: %d" % pol_nr) - if pol is not None and isinstance(pol, Polygon): - res = self.clear_polygon_worker(pol=pol, tooldia=tool, - ncc_method=ncc_method, - ncc_overlap=ncc_overlap, - ncc_connect=ncc_connect, - ncc_contour=ncc_contour, - prog_plot=prog_plot) - if res is not None: - cleared_geo += res - else: - poly_failed += 1 - else: - self.app.log.warning( - "Expected geo is a Polygon. Instead got a %s" % str(type(pol))) - except TypeError: - if isinstance(p, Polygon): - res = self.clear_polygon_worker(pol=p, tooldia=tool, - ncc_method=ncc_method, - ncc_overlap=ncc_overlap, - ncc_connect=ncc_connect, - ncc_contour=ncc_contour, - prog_plot=prog_plot) - if res is not None: - cleared_geo += res - else: - poly_failed += 1 - else: - self.app.log.warning( - "Expected geo is a Polygon. Instead got a %s" % str(type(p))) + 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 + # check if there is a geometry at all in the cleared geometry + if cleared_geo: + formatted_tool = self.app.dec_format(tool, self.decimals) + # find the tooluid associated with the current tool_dia so we know where to add the tool + # solid_geometry + for k, v in tools_storage.items(): + if self.app.dec_format(v['tooldia'], self.decimals) == formatted_tool: + current_uid = int(k) - 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)) - - # check if there is a geometry at all in the cleared geometry - if cleared_geo: - formatted_tool = self.app.dec_format(tool, self.decimals) - # find the tooluid associated with the current tool_dia so we know where to add the tool - # solid_geometry - for k, v in tools_storage.items(): - if self.app.dec_format(v['tooldia'], self.decimals) == formatted_tool: - current_uid = int(k) - - # add the solid_geometry to the current too in self.paint_tools dictionary - # and then reset the temporary list that stored that solid_geometry - v['solid_geometry'] = deepcopy(cleared_geo) - v['data']['name'] = name - geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) - break - else: - self.app.log.debug("There are no geometries in the cleared polygon.") + # add the solid_geometry to the current too in self.paint_tools dictionary + # and then reset the temporary list that stored that solid_geometry + v['solid_geometry'] = deepcopy(cleared_geo) + v['data']['name'] = name + geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) + break + else: + self.app.log.debug("There are no geometries in the cleared polygon.") # clean the progressive plotted shapes if it was used if self.app.options["tools_ncc_plotting"] == 'progressive': @@ -2656,10 +2643,18 @@ class NonCopperClear(AppTool, Gerber): # store here the geometry generated by clear operation cleared_geo = [] - poly_failed = 0 - if area.geoms and len(area.geoms) > 0: + tool_empty_area = [] + if area.geoms: + tool_empty_area = flatten_shapely_geometry(area.geoms) + + if tool_empty_area: + poly_failed = 0 pol_nr = 0 - for p in area.geoms: + for p in tool_empty_area: + # provide the app with a way to process the GUI events when in a blocking loop + if not run_threaded: + QtWidgets.QApplication.processEvents() + if self.app.abort_flag: # graceful abort requested by the user raise grace @@ -2668,11 +2663,12 @@ class NonCopperClear(AppTool, Gerber): # provide the app with a way to process the GUI events when in a blocking loop QtWidgets.QApplication.processEvents() - # speedup the clearing by not trying to clear polygons that is clear they can't be + # speedup the clearing by not trying to clear polygons that is obvious they can't be # cleared with the current tool. this tremendously reduce the clearing time check_dist = -tool / 2 check_buff = p.buffer(check_dist, self.circle_steps) - if not check_buff or check_buff.is_empty: + check_buff = flatten_shapely_geometry(check_buff) + if not check_buff: continue # if self.app.dec_format(float(tool), self.decimals) == 0.15: @@ -2740,6 +2736,7 @@ class NonCopperClear(AppTool, Gerber): new_area = new_area.buffer(0.0000001) area = area.difference(new_area) + area = flatten_shapely_geometry(area) new_area = [pol for pol in area if pol.is_valid and not pol.is_empty] area = MultiPolygon(new_area) diff --git a/camlib.py b/camlib.py index 1a2f3c05..676b5e55 100644 --- a/camlib.py +++ b/camlib.py @@ -1446,10 +1446,8 @@ class Geometry(object): """ # log.debug("camlib.clear_polygon()") - assert type(polygon) == Polygon or type(polygon) == MultiPolygon, \ - "Expected a Polygon or MultiPolygon, got %s" % type(polygon) - # ## The toolpaths + # The toolpaths # Index first and last points in paths def get_pts(o): return [o.coords[0], o.coords[-1]] @@ -1460,58 +1458,71 @@ 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)) - if current.area == 0: - # Otherwise, trying to to insert current.exterior == None - # into the FlatCAMStorage will fail. - # print("Area is None") - return None + current = flatten_shapely_geometry(current) - # current can be a MultiPolygon - try: - for p in current: - geoms.insert(p.exterior) - for i in p.interiors: - geoms.insert(i) + # if current.area == 0: + # # Otherwise, trying to insert current.exterior == None + # # into the FlatCAMStorage will fail. + # # print("Area is None") + # return None - # Not a Multipolygon. Must be a Polygon - except TypeError: - geoms.insert(current.exterior) - for i in current.interiors: + for p in current: + geoms.insert(p.exterior) + for i in p.interiors: geoms.insert(i) - while True: - if self.app.abort_flag: - # graceful abort requested by the user - raise grace + 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() + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() - # Can only result in a Polygon or MultiPolygon - current = current.buffer(-tooldia * (1 - overlap), int(steps_per_circle)) - if current.area > 0: + for cl_pol in current: + while True: + if self.app.abort_flag: + # graceful abort requested by the user + raise grace - # current can be a MultiPolygon - try: - for p in current.geoms: - geoms.insert(p.exterior) - for i in p.interiors: + # 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)) + if isinstance(cl_pol, MultiPolygon): + cl_pol = flatten_shapely_geometry(cl_pol) + + added_flag = False + for tiny_pol in cl_pol: + if tiny_pol.area > 0: + added_flag = True + geoms.insert(tiny_pol.exterior) + if prog_plot: + self.plot_temp_shapes(tiny_pol.exterior) + + for i in tiny_pol.interiors: + geoms.insert(i) + if prog_plot: + self.plot_temp_shapes(i) + if added_flag is False: + break + + cl_pol = MultiPolygon(cl_pol) + else: + if cl_pol.area > 0: + geoms.insert(cl_pol.exterior) + if prog_plot: + self.plot_temp_shapes(cl_pol.exterior) + + for i in cl_pol.interiors: geoms.insert(i) if prog_plot: - self.plot_temp_shapes(p) + self.plot_temp_shapes(i) + else: + break - # Not a Multipolygon. Must be a Polygon - except (TypeError, AttributeError): - geoms.insert(current.exterior) - if prog_plot: - self.plot_temp_shapes(current.exterior) - for i in current.interiors: - geoms.insert(i) - if prog_plot: - self.plot_temp_shapes(i) - else: - self.app.log.debug("camlib.Geometry.clear_polygon() --> Current Area is zero") - break + if not geoms.objects: + self.app.log.debug("camlib.Geometry.clear_polygon() --> Current Area is zero") + return if prog_plot: self.temp_shapes.redraw()