diff --git a/README.md b/README.md index 8a6ecb75..9909fb6d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing. ================================================= +8.01.2019 + +- working in NCC Tool + 7.01.2019 - solved issue #368 - when using the Enable/Disable prj context menu entries the plotted status is not updated in the object properties diff --git a/flatcamTools/ToolNonCopperClear.py b/flatcamTools/ToolNonCopperClear.py index 93afb74d..19732939 100644 --- a/flatcamTools/ToolNonCopperClear.py +++ b/flatcamTools/ToolNonCopperClear.py @@ -9,7 +9,7 @@ from PyQt5 import QtWidgets, QtCore, QtGui from FlatCAMTool import FlatCAMTool from flatcamGUI.GUIElements import FCCheckBox, FCDoubleSpinner, RadioSet, FCTable, FCInputDialog, FCButton from flatcamParsers.ParseGerber import Gerber -from FlatCAMObj import FlatCAMGeometry, FlatCAMGerber + import FlatCAMApp from copy import deepcopy @@ -1184,32 +1184,30 @@ class NonCopperClear(FlatCAMTool, Gerber): current_uid = int(self.tools_table.item(cw_row, 3).text()) - hide_iso_type = True - for row in range(self.tools_table.rowCount()): - if self.tools_table.cellWidget(row, 4).currentText() == 'iso_op': - hide_iso_type = False - break - - if hide_iso_type is False: - self.milling_type_label.show() - self.milling_type_radio.show() - else: - self.milling_type_label.hide() - self.milling_type_radio.hide() - # if the sender is in the column with index 2 then we update the tool_type key if cw_col == 2: tt = cw.currentText() - if tt == 'V': - typ = 'Iso' - else: - typ = "Rough" + typ = 'Iso' if tt == 'V' else "Rough" self.ncc_tools[current_uid].update({ 'type': typ, 'tool_type': tt, }) + if cw_col == 4: + op = cw.currentText() + + if op == 'iso_op': + self.milling_type_label.show() + self.milling_type_radio.show() + else: + self.milling_type_label.hide() + self.milling_type_radio.hide() + + self.ncc_tools[current_uid].update({ + 'operation': op + }) + def on_tool_type(self, val): if val == 'V': self.addtool_entry_lbl.setDisabled(True) @@ -1726,7 +1724,6 @@ class NonCopperClear(FlatCAMTool, Gerber): ncc_select = self.reference_radio.get_value() overlap = overlap if overlap is not None else float(self.app.defaults["tools_nccoverlap"]) / 100.0 - connect = connect if connect else self.app.defaults["tools_nccconnect"] contour = contour if contour else self.app.defaults["tools_ncccontour"] order = order if order else self.ncc_order_radio.get_value() @@ -1824,7 +1821,7 @@ class NonCopperClear(FlatCAMTool, Gerber): elif ncc_select == 'box': geo_n = ncc_sel_obj.solid_geometry - if isinstance(ncc_sel_obj, FlatCAMGeometry): + if ncc_sel_obj.kind == 'geometry': try: __ = iter(geo_n) except Exception as e: @@ -1839,7 +1836,7 @@ class NonCopperClear(FlatCAMTool, Gerber): geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)) bounding_box = cascaded_union(geo_buff_list) - elif isinstance(ncc_sel_obj, FlatCAMGerber): + elif ncc_sel_obj.kind == 'gerber': geo_n = cascaded_union(geo_n).convex_hull bounding_box = cascaded_union(self.ncc_obj.solid_geometry).convex_hull.intersection(geo_n) bounding_box = bounding_box.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre) @@ -1862,7 +1859,7 @@ class NonCopperClear(FlatCAMTool, Gerber): # Initializes the new geometry object ###################################################### # ########################################################################################## def gen_clear_area(geo_obj, app_obj): - assert isinstance(geo_obj, FlatCAMGeometry), \ + assert geo_obj.kind == 'geometry', \ "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) # provide the app with a way to process the GUI events when in a blocking loop @@ -1904,7 +1901,7 @@ class NonCopperClear(FlatCAMTool, Gerber): log.debug("NCC Tool. Calculate 'empty' area.") self.app.inform.emit(_("NCC Tool. Calculate 'empty' area.")) - if isinstance(ncc_obj, FlatCAMGerber) and not isotooldia: + if ncc_obj.kind == 'gerber' and not isotooldia: # unfortunately for this function to work time efficient, # if the Gerber was loaded without buffering then it require the buffering now. if self.app.defaults['gerber_buffering'] == 'no': @@ -1925,7 +1922,7 @@ class NonCopperClear(FlatCAMTool, Gerber): app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Could not get the extent of the area to be non copper cleared.")) return 'fail' - elif isinstance(ncc_obj, FlatCAMGerber) and isotooldia: + elif ncc_obj.kind == 'gerber' and isotooldia: isolated_geo = [] # unfortunately for this function to work time efficient, @@ -2030,7 +2027,7 @@ class NonCopperClear(FlatCAMTool, Gerber): _("Isolation geometry is broken. Margin is less than isolation tool diameter.")) return 'fail' - elif isinstance(ncc_obj, FlatCAMGeometry): + elif ncc_obj.kind == 'geometry': sol_geo = cascaded_union(ncc_obj.solid_geometry) if has_offset is True: app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) @@ -2301,7 +2298,7 @@ class NonCopperClear(FlatCAMTool, Gerber): # Initializes the new geometry object for the case of the rest-machining #################### # ########################################################################################### def gen_clear_area_rest(geo_obj, app_obj): - assert isinstance(geo_obj, FlatCAMGeometry), \ + assert geo_obj.kind == 'geometry', \ "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) log.debug("NCC Tool. Rest machining copper clearing task started.") @@ -2334,7 +2331,7 @@ class NonCopperClear(FlatCAMTool, Gerber): # ################################################################################################### # Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry ## # ################################################################################################### - if isinstance(ncc_obj, FlatCAMGerber) and not isotooldia: + if ncc_obj.kind == 'gerber' and not isotooldia: sol_geo = ncc_obj.solid_geometry if has_offset is True: app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) @@ -2348,7 +2345,7 @@ class NonCopperClear(FlatCAMTool, Gerber): app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Could not get the extent of the area to be non copper cleared.")) return 'fail' - elif isinstance(ncc_obj, FlatCAMGerber) and isotooldia: + elif ncc_obj.kind == 'gerber' and isotooldia: isolated_geo = [] self.solid_geometry = ncc_obj.solid_geometry @@ -2450,7 +2447,7 @@ class NonCopperClear(FlatCAMTool, Gerber): _("Isolation geometry is broken. Margin is less than isolation tool diameter.")) return 'fail' - elif isinstance(ncc_obj, FlatCAMGeometry): + elif ncc_obj.kind == 'geometry': sol_geo = cascaded_union(ncc_obj.solid_geometry) if has_offset is True: app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) @@ -2721,359 +2718,1003 @@ class NonCopperClear(FlatCAMTool, Gerber): else: job_thread(app_obj=self.app) - # def on_ncc(self): - # - # # Prepare non-copper polygons - # if self.reference_radio.get_value() == 'area': - # geo_n = self.sel_rect - # - # geo_buff_list = [] - # for poly in geo_n: - # geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)) - # bounding_box = cascaded_union(geo_buff_list) - # else: - # geo_n = self.bound_obj.solid_geometry - # - # try: - # if isinstance(geo_n, MultiPolygon): - # env_obj = geo_n.convex_hull - # elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \ - # (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon): - # env_obj = cascaded_union(geo_n) - # else: - # env_obj = cascaded_union(geo_n) - # env_obj = env_obj.convex_hull - # bounding_box = env_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre) - # except Exception as e: - # log.debug("NonCopperClear.on_ncc() --> %s" % str(e)) - # self.app.inform.emit(_("[ERROR_NOTCL] No object available.")) - # return - # - # # calculate the empty area by subtracting the solid_geometry from the object bounding box geometry - # if isinstance(self.ncc_obj, FlatCAMGerber): - # if self.ncc_choice_offset_cb.isChecked(): - # self.app.inform.emit(_("[WARNING_NOTCL] Buffering ...")) - # offseted_geo = self.ncc_obj.solid_geometry.buffer(distance=ncc_offset_value) - # self.app.inform.emit(_("[success] Buffering finished ...")) - # empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box) - # else: - # empty = self.get_ncc_empty_area(target=self.ncc_obj.solid_geometry, boundary=bounding_box) - # elif isinstance(self.ncc_obj, FlatCAMGeometry): - # sol_geo = cascaded_union(self.ncc_obj.solid_geometry) - # if self.ncc_choice_offset_cb.isChecked(): - # self.app.inform.emit(_("[WARNING_NOTCL] Buffering ...")) - # offseted_geo = sol_geo.buffer(distance=ncc_offset_value) - # self.app.inform.emit(_("[success] Buffering finished ...")) - # empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box) - # else: - # empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) - # else: - # self.inform.emit(_('[ERROR_NOTCL] The selected object is not suitable for copper clearing.')) - # return - # - # if type(empty) is Polygon: - # empty = MultiPolygon([empty]) - # - # if empty.is_empty: - # self.app.inform.emit(_("[ERROR_NOTCL] Could not get the extent of the area to be non copper cleared.")) - # return - # - # # clear non copper using standard algorithm - # if clearing_method is False: - # self.clear_non_copper( - # empty=empty, - # over=over, - # pol_method=pol_method, - # connect=connect, - # contour=contour - # ) - # # clear non copper using rest machining algorithm - # else: - # self.clear_non_copper_rest( - # empty=empty, - # over=over, - # pol_method=pol_method, - # connect=connect, - # contour=contour - # ) - # - # def clear_non_copper(self, empty, over, pol_method, outname=None, connect=True, contour=True): - # - # name = outname if outname else self.obj_name + "_ncc" - # - # # Sort tools in descending order - # sorted_tools = [] - # for k, v in self.ncc_tools.items(): - # sorted_tools.append(float('%.4f' % float(v['tooldia']))) - # - # order = self.ncc_order_radio.get_value() - # if order == 'fwd': - # sorted_tools.sort(reverse=False) - # elif order == 'rev': - # sorted_tools.sort(reverse=True) - # else: - # pass - # - # # Do job in background - # proc = self.app.proc_container.new(_("Clearing Non-Copper areas.")) - # - # def initialize(geo_obj, app_obj): - # assert isinstance(geo_obj, FlatCAMGeometry), \ - # "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) - # - # cleared_geo = [] - # # Already cleared area - # cleared = MultiPolygon() - # - # # flag for polygons not cleared - # app_obj.poly_not_cleared = False - # - # # Generate area for each tool - # offset = sum(sorted_tools) - # current_uid = int(1) - # tool = eval(self.app.defaults["tools_ncctools"])[0] - # - # for tool in sorted_tools: - # self.app.inform.emit(_('[success] Non-Copper Clearing with ToolDia = %s started.') % str(tool)) - # cleared_geo[:] = [] - # - # # Get remaining tools offset - # offset -= (tool - 1e-12) - # - # # Area to clear - # area = empty.buffer(-offset) - # try: - # area = area.difference(cleared) - # except Exception as e: - # continue - # - # # Transform area to MultiPolygon - # if type(area) is Polygon: - # area = MultiPolygon([area]) - # - # if area.geoms: - # if len(area.geoms) > 0: - # for p in area.geoms: - # try: - # if pol_method == 'standard': - # cp = self.clear_polygon(p, tool, self.app.defaults["gerber_circle_steps"], - # overlap=over, contour=contour, connect=connect) - # elif pol_method == 'seed': - # cp = self.clear_polygon2(p, tool, self.app.defaults["gerber_circle_steps"], - # overlap=over, contour=contour, connect=connect) - # else: - # cp = self.clear_polygon3(p, tool, self.app.defaults["gerber_circle_steps"], - # overlap=over, contour=contour, connect=connect) - # if cp: - # cleared_geo += list(cp.get_objects()) - # except Exception as e: - # log.warning("Polygon can not be cleared. %s" % str(e)) - # app_obj.poly_not_cleared = True - # continue - # - # # check if there is a geometry at all in the cleared geometry - # if cleared_geo: - # # Overall cleared area - # cleared = empty.buffer(-offset * (1 + over)).buffer(-tool / 1.999999).buffer( - # tool / 1.999999) - # - # # clean-up cleared geo - # cleared = cleared.buffer(0) - # - # # find the tooluid associated with the current tool_dia so we know where to add the tool - # # solid_geometry - # for k, v in self.ncc_tools.items(): - # if float('%.4f' % v['tooldia']) == float('%.4f' % 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 - # break - # geo_obj.tools[current_uid] = dict(self.ncc_tools[current_uid]) - # else: - # log.debug("There are no geometries in the cleared polygon.") - # - # geo_obj.options["cnctooldia"] = str(tool) - # geo_obj.multigeo = True - # - # def job_thread(app_obj): - # try: - # app_obj.new_object("geometry", name, initialize) - # except Exception as e: - # proc.done() - # self.app.inform.emit(_('[ERROR_NOTCL] NCCTool.clear_non_copper() --> %s') % str(e)) - # return - # proc.done() - # - # if app_obj.poly_not_cleared is False: - # self.app.inform.emit(_('[success] NCC Tool finished.')) - # else: - # self.app.inform.emit(_('[WARNING_NOTCL] NCC Tool finished but some PCB features could not be cleared. ' - # 'Check the result.')) - # # reset the variable for next use - # app_obj.poly_not_cleared = False - # - # # focus on Selected Tab - # self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab) - # - # # Promise object with the new name - # self.app.collection.promise(name) - # - # # Background - # self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) - # - # # clear copper with 'rest-machining' algorithm - # def clear_non_copper_rest(self, empty, over, pol_method, outname=None, connect=True, contour=True): - # - # name = outname if outname is not None else self.obj_name + "_ncc_rm" - # - # # Sort tools in descending order - # sorted_tools = [] - # for k, v in self.ncc_tools.items(): - # sorted_tools.append(float('%.4f' % float(v['tooldia']))) - # sorted_tools.sort(reverse=True) - # - # # Do job in background - # proc = self.app.proc_container.new(_("Clearing Non-Copper areas.")) - # - # def initialize_rm(geo_obj, app_obj): - # assert isinstance(geo_obj, FlatCAMGeometry), \ - # "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) - # - # cleared_geo = [] - # cleared_by_last_tool = [] - # rest_geo = [] - # current_uid = 1 - # tool = eval(self.app.defaults["tools_ncctools"])[0] - # - # # repurposed flag for final object, geo_obj. True if it has any solid_geometry, False if not. - # app_obj.poly_not_cleared = True - # - # area = empty.buffer(0) - # # Generate area for each tool - # while sorted_tools: - # tool = sorted_tools.pop(0) - # self.app.inform.emit(_('[success] Non-Copper Rest Clearing with ToolDia = %s started.') % str(tool)) - # - # tool_used = tool - 1e-12 - # cleared_geo[:] = [] - # - # # Area to clear - # for poly in cleared_by_last_tool: - # try: - # area = area.difference(poly) - # except Exception as e: - # pass - # cleared_by_last_tool[:] = [] - # - # # Transform area to MultiPolygon - # if type(area) is Polygon: - # area = MultiPolygon([area]) - # - # # add the rest that was not able to be cleared previously; area is a MultyPolygon - # # and rest_geo it's a list - # allparts = [p.buffer(0) for p in area.geoms] - # allparts += deepcopy(rest_geo) - # rest_geo[:] = [] - # area = MultiPolygon(deepcopy(allparts)) - # allparts[:] = [] - # - # if area.geoms: - # if len(area.geoms) > 0: - # for p in area.geoms: - # try: - # if pol_method == 'standard': - # cp = self.clear_polygon(p, tool_used, self.app.defaults["gerber_circle_steps"], - # overlap=over, contour=contour, connect=connect) - # elif pol_method == 'seed': - # cp = self.clear_polygon2(p, tool_used, - # self.app.defaults["gerber_circle_steps"], - # overlap=over, contour=contour, connect=connect) - # else: - # cp = self.clear_polygon3(p, tool_used, - # self.app.defaults["gerber_circle_steps"], - # overlap=over, contour=contour, connect=connect) - # cleared_geo.append(list(cp.get_objects())) - # except: - # log.warning("Polygon can't be cleared.") - # # this polygon should be added to a list and then try clear it with a smaller tool - # rest_geo.append(p) - # - # # check if there is a geometry at all in the cleared geometry - # if cleared_geo: - # # Overall cleared area - # cleared_area = list(self.flatten_list(cleared_geo)) - # - # # cleared = MultiPolygon([p.buffer(tool_used / 2).buffer(-tool_used / 2) - # # for p in cleared_area]) - # - # # here we store the poly's already processed in the original geometry by the current tool - # # into cleared_by_last_tool list - # # this will be sustracted from the original geometry_to_be_cleared and make data for - # # the next tool - # buffer_value = tool_used / 2 - # for p in cleared_area: - # poly = p.buffer(buffer_value) - # cleared_by_last_tool.append(poly) - # - # # find the tooluid associated with the current tool_dia so we know - # # where to add the tool solid_geometry - # for k, v in self.ncc_tools.items(): - # if float('%.4f' % v['tooldia']) == float('%.4f' % 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_area) - # v['data']['name'] = name - # cleared_area[:] = [] - # break - # - # geo_obj.tools[current_uid] = dict(self.ncc_tools[current_uid]) - # else: - # log.debug("There are no geometries in the cleared polygon.") - # - # geo_obj.multigeo = True - # geo_obj.options["cnctooldia"] = str(tool) - # - # # check to see if geo_obj.tools is empty - # # it will be updated only if there is a solid_geometry for tools - # if geo_obj.tools: - # return - # 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" - # - # def job_thread(app_obj): - # try: - # app_obj.new_object("geometry", name, initialize_rm) - # except Exception as e: - # proc.done() - # app_obj.inform.emit(_('[ERROR_NOTCL] NCCTool.clear_non_copper_rest() --> %s') % str(e)) - # return - # - # if app_obj.poly_not_cleared is True: - # app_obj.inform.emit('[success] NCC Tool finished.') - # # focus on Selected Tab - # app_obj.ui.notebook.setCurrentWidget(self.app.ui.selected_tab) - # else: - # app_obj.inform.emit(_('[ERROR_NOTCL] NCC Tool finished but could not clear the object ' - # 'with current settings.')) - # # focus on Project Tab - # app_obj.ui.notebook.setCurrentWidget(self.app.ui.project_tab) - # proc.done() - # # reset the variable for next use - # app_obj.poly_not_cleared = False - # - # # Promise object with the new name - # self.app.collection.promise(name) - # - # # Background - # self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) + def clear_copper_tcl(self, ncc_obj, + sel_obj=None, + ncctooldia=None, + isotooldia=None, + margin=None, + has_offset=None, + offset=None, + select_method=None, + outname=None, + overlap=None, + connect=None, + contour=None, + order=None, + method=None, + rest=None, + tools_storage=None, + plot=True, + run_threaded=False): + """ + Clear the excess copper from the entire object. To be used only in a TCL command. + + :param ncc_obj: ncc cleared object + :param ncctooldia: a tuple or single element made out of diameters of the tools to be used to ncc clear + :param isotooldia: a tuple or single element made out of diameters of the tools to be used for isolation + :param overlap: value by which the paths will overlap + :param order: if the tools are ordered and how + :param select_method: if to do ncc on the whole object, on an defined area or on an area defined by + another object + :param has_offset: True if an offset is needed + :param offset: distance from the copper features where the copper clearing is stopping + :param margin: a border around cleared area + :param outname: name of the resulting object + :param connect: Connect lines to avoid tool lifts. + :param contour: Paint around the edges. + :param method: choice out of 'seed', 'normal', 'lines' + :param rest: True if to use rest-machining + :param tools_storage: whether to use the current tools_storage self.ncc_tools or a different one. + Usage of the different one is related to when this function is called from a TcL command. + :param plot: if True after the job is finished the result will be plotted, else it will not. + :param run_threaded: If True the method will be run in a threaded way suitable for GUI usage; if False it will + run non-threaded for TclShell usage + :return: + """ + if run_threaded: + proc = self.app.proc_container.new(_("Non-Copper clearing ...")) + else: + self.app.proc_container.view.set_busy(_("Non-Copper clearing ...")) + QtWidgets.QApplication.processEvents() + + # ##################################################################### + # ####### Read the parameters ######################################### + # ##################################################################### + + units = self.app.defaults['units'] + + log.debug("NCC Tool started. Reading parameters.") + self.app.inform.emit(_("NCC Tool started. Reading parameters.")) + + ncc_method = method + ncc_margin = margin + ncc_select = select_method + overlap = overlap + + connect = connect + contour = contour + order = order + + if tools_storage is not None: + tools_storage = tools_storage + else: + tools_storage = self.ncc_tools + + ncc_offset = 0.0 + if has_offset is True: + ncc_offset = offset + + # ###################################################################################################### + # # Read the tooldia parameter and create a sorted list out them - they may be more than one diameter ## + # ###################################################################################################### + sorted_tools = [] + try: + sorted_tools = [float(eval(dia)) for dia in ncctooldia.split(",") if dia != ''] + except AttributeError: + if not isinstance(ncctooldia, list): + sorted_tools = [float(ncctooldia)] + else: + sorted_tools = ncctooldia + + # ############################################################################################################## + # Prepare non-copper polygons. Create the bounding box area from which the copper features will be subtracted ## + # ############################################################################################################## + log.debug("NCC Tool. Preparing non-copper polygons.") + self.app.inform.emit(_("NCC Tool. Preparing non-copper polygons.")) + + try: + if sel_obj is None or sel_obj == 'itself': + ncc_sel_obj = ncc_obj + else: + ncc_sel_obj = sel_obj + except Exception as e: + log.debug("NonCopperClear.clear_copper() --> %s" % str(e)) + return 'fail' + + bounding_box = None + if ncc_select == 'itself': + geo_n = ncc_sel_obj.solid_geometry + + try: + if isinstance(geo_n, MultiPolygon): + env_obj = geo_n.convex_hull + elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \ + (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon): + env_obj = cascaded_union(geo_n) + else: + env_obj = cascaded_union(geo_n) + env_obj = env_obj.convex_hull + + bounding_box = env_obj.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre) + except Exception as e: + log.debug("NonCopperClear.clear_copper() 'itself' --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("No object available.")) + return 'fail' + + elif ncc_select == 'area': + geo_n = cascaded_union(self.sel_rect) + try: + __ = iter(geo_n) + except Exception as e: + log.debug("NonCopperClear.clear_copper() 'area' --> %s" % str(e)) + geo_n = [geo_n] + + geo_buff_list = [] + for poly in geo_n: + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)) + + bounding_box = cascaded_union(geo_buff_list) + + elif ncc_select == 'box': + geo_n = ncc_sel_obj.solid_geometry + if ncc_sel_obj.kind == 'geometry': + try: + __ = iter(geo_n) + except Exception as e: + log.debug("NonCopperClear.clear_copper() 'box' --> %s" % str(e)) + geo_n = [geo_n] + + geo_buff_list = [] + for poly in geo_n: + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)) + + bounding_box = cascaded_union(geo_buff_list) + elif ncc_sel_obj.kind == 'gerber': + geo_n = cascaded_union(geo_n).convex_hull + bounding_box = cascaded_union(self.ncc_obj.solid_geometry).convex_hull.intersection(geo_n) + bounding_box = bounding_box.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre) + else: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("The reference object type is not supported.")) + return 'fail' + + log.debug("NCC Tool. Finished non-copper polygons.") + # ######################################################################################################## + # set the name for the future Geometry object + # I do it here because it is also stored inside the gen_clear_area() and gen_clear_area_rest() methods + # ######################################################################################################## + rest_machining_choice = rest + if rest_machining_choice is True: + name = outname if outname is not None else self.obj_name + "_ncc_rm" + else: + name = outname if outname is not None else self.obj_name + "_ncc" + + # ########################################################################################## + # Initializes the new geometry object ###################################################### + # ########################################################################################## + def gen_clear_area(geo_obj, app_obj): + assert geo_obj.kind == 'geometry', \ + "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) + + # provide the app with a way to process the GUI events when in a blocking loop + if not run_threaded: + QtWidgets.QApplication.processEvents() + + log.debug("NCC Tool. Normal copper clearing task started.") + self.app.inform.emit(_("NCC Tool. Finished non-copper polygons. Normal copper clearing task started.")) + + # a flag to signal that the isolation is broken by the bounding box in 'area' and 'box' cases + # will store the number of tools for which the isolation is broken + warning_flag = 0 + + if order == 'fwd': + sorted_tools.sort(reverse=False) + elif order == 'rev': + sorted_tools.sort(reverse=True) + else: + pass + + cleared_geo = [] + # Already cleared area + cleared = MultiPolygon() + + # flag for polygons not cleared + app_obj.poly_not_cleared = False + + # Generate area for each tool + offset = sum(sorted_tools) + current_uid = int(1) + try: + tool = eval(self.app.defaults["tools_ncctools"])[0] + except TypeError: + tool = eval(self.app.defaults["tools_ncctools"]) + + # ################################################################################################### + # Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry ## + # ################################################################################################### + log.debug("NCC Tool. Calculate 'empty' area.") + self.app.inform.emit(_("NCC Tool. Calculate 'empty' area.")) + + if ncc_obj.kind == 'gerber' and not isotooldia: + # unfortunately for this function to work time efficient, + # if the Gerber was loaded without buffering then it require the buffering now. + if self.app.defaults['gerber_buffering'] == 'no': + sol_geo = ncc_obj.solid_geometry.buffer(0) + else: + sol_geo = ncc_obj.solid_geometry + + if has_offset is True: + app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) + sol_geo = sol_geo.buffer(distance=ncc_offset) + app_obj.inform.emit('[success] %s ...' % _("Buffering finished")) + + empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) + if empty == 'fail': + return 'fail' + + if empty.is_empty: + app_obj.inform.emit('[ERROR_NOTCL] %s' % + _("Could not get the extent of the area to be non copper cleared.")) + return 'fail' + elif ncc_obj.kind == 'gerber' and isotooldia: + isolated_geo = [] + + # unfortunately for this function to work time efficient, + # if the Gerber was loaded without buffering then it require the buffering now. + if self.app.defaults['gerber_buffering'] == 'no': + self.solid_geometry = ncc_obj.solid_geometry.buffer(0) + else: + self.solid_geometry = ncc_obj.solid_geometry + + # if milling type is climb then the move is counter-clockwise around features + milling_type = self.app.defaults["tools_nccmilling_type"] + + for tool_iso in isotooldia: + new_geometry = [] + + if milling_type == 'cl': + isolated_geo = self.generate_envelope(tool_iso / 2, 1) + else: + isolated_geo = self.generate_envelope(tool_iso / 2, 0) + + if isolated_geo == 'fail': + app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated.")) + else: + if ncc_margin < tool_iso: + app_obj.inform.emit('[WARNING_NOTCL] %s' % _("Isolation geometry is broken. Margin is less " + "than isolation tool diameter.")) + try: + for geo_elem in isolated_geo: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + if isinstance(geo_elem, Polygon): + for ring in self.poly2rings(geo_elem): + new_geo = ring.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, MultiPolygon): + for poly in geo_elem: + for ring in self.poly2rings(poly): + new_geo = ring.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, LineString): + new_geo = geo_elem.intersection(bounding_box) + if new_geo: + if not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, MultiLineString): + for line_elem in geo_elem: + new_geo = line_elem.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + except TypeError: + if isinstance(isolated_geo, Polygon): + for ring in self.poly2rings(isolated_geo): + new_geo = ring.intersection(bounding_box) + if new_geo: + if not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(isolated_geo, LineString): + new_geo = isolated_geo.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(isolated_geo, MultiLineString): + for line_elem in isolated_geo: + new_geo = line_elem.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + + # a MultiLineString geometry element will show that the isolation is broken for this tool + for geo_e in new_geometry: + if type(geo_e) == MultiLineString: + warning_flag += 1 + break + + for k, v in tools_storage.items(): + if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, + tool_iso)): + 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(new_geometry) + v['data']['name'] = name + break + geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) + + sol_geo = cascaded_union(isolated_geo) + if has_offset is True: + app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) + sol_geo = sol_geo.buffer(distance=ncc_offset) + app_obj.inform.emit('[success] %s ...' % _("Buffering finished")) + empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) + if empty == 'fail': + return 'fail' + + if empty.is_empty: + app_obj.inform.emit('[ERROR_NOTCL] %s' % + _("Isolation geometry is broken. Margin is less than isolation tool diameter.")) + return 'fail' + + elif ncc_obj.kind == 'geometry': + sol_geo = cascaded_union(ncc_obj.solid_geometry) + if has_offset is True: + app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) + sol_geo = sol_geo.buffer(distance=ncc_offset) + app_obj.inform.emit('[success] %s ...' % _("Buffering finished")) + empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) + if empty == 'fail': + return 'fail' + + if empty.is_empty: + app_obj.inform.emit('[ERROR_NOTCL] %s' % + _("Could not get the extent of the area to be non copper cleared.")) + return 'fail' + + else: + app_obj.inform.emit('[ERROR_NOTCL] %s' % _('The selected object is not suitable for copper clearing.')) + return 'fail' + + if type(empty) is Polygon: + empty = MultiPolygon([empty]) + + log.debug("NCC Tool. Finished calculation of 'empty' area.") + self.app.inform.emit(_("NCC Tool. Finished calculation of 'empty' area.")) + + # COPPER CLEARING # + cp = None + for tool in sorted_tools: + log.debug("Starting geometry processing for tool: %s" % str(tool)) + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + + app_obj.inform.emit('[success] %s = %s%s %s' % ( + _('NCC Tool clearing with tool diameter'), str(tool), units.lower(), _('started.')) + ) + app_obj.proc_container.update_view_text(' %d%%' % 0) + + cleared_geo[:] = [] + + # Get remaining tools offset + offset -= (tool - 1e-12) + + # Area to clear + area = empty.buffer(-offset) + try: + area = area.difference(cleared) + except Exception as e: + continue + + # Transform area to MultiPolygon + if type(area) is Polygon: + area = MultiPolygon([area]) + + # variables to display the percentage of work done + geo_len = len(area.geoms) + + old_disp_number = 0 + log.warning("Total number of polygons to be cleared. %s" % str(geo_len)) + + if area.geoms: + if len(area.geoms) > 0: + pol_nr = 0 + for p in area.geoms: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + # clean the polygon + p = p.buffer(0) + + if p is not None and p.is_valid: + poly_processed = list() + try: + for pol in p: + if pol is not None and isinstance(pol, Polygon): + if ncc_method == 'standard': + cp = self.clear_polygon(pol, tool, + self.grb_circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) + elif ncc_method == 'seed': + cp = self.clear_polygon2(pol, tool, + self.grb_circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) + else: + cp = self.clear_polygon3(pol, tool, + self.grb_circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) + if cp: + cleared_geo += list(cp.get_objects()) + poly_processed.append(True) + else: + poly_processed.append(False) + log.warning("Polygon in MultiPolygon can not be cleared.") + else: + log.warning("Geo in Iterable can not be cleared because it is not Polygon. " + "It is: %s" % str(type(pol))) + except TypeError: + if isinstance(p, Polygon): + if ncc_method == 'standard': + cp = self.clear_polygon(p, tool, self.grb_circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) + elif ncc_method == 'seed': + cp = self.clear_polygon2(p, tool, self.grb_circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) + else: + cp = self.clear_polygon3(p, tool, self.grb_circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) + if cp: + cleared_geo += list(cp.get_objects()) + poly_processed.append(True) + else: + poly_processed.append(False) + log.warning("Polygon can not be cleared.") + else: + log.warning("Geo can not be cleared because it is: %s" % str(type(p))) + + p_cleared = poly_processed.count(True) + p_not_cleared = poly_processed.count(False) + + if p_not_cleared: + app_obj.poly_not_cleared = True + + if p_cleared == 0: + continue + + 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: + # Overall cleared area + cleared = empty.buffer(-offset * (1 + overlap)).buffer(-tool / 1.999999).buffer( + tool / 1.999999) + + # clean-up cleared geo + cleared = cleared.buffer(0) + + # 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 float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, + 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 + break + geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) + else: + log.debug("There are no geometries in the cleared polygon.") + + # delete tools with empty geometry + # look for keys in the tools_storage dict that have 'solid_geometry' values empty + for uid, uid_val in list(tools_storage.items()): + try: + # if the solid_geometry (type=list) is empty + if not uid_val['solid_geometry']: + tools_storage.pop(uid, None) + except KeyError: + tools_storage.pop(uid, None) + + geo_obj.options["cnctooldia"] = str(tool) + + geo_obj.multigeo = True + geo_obj.tools.clear() + geo_obj.tools = dict(tools_storage) + + # test if at least one tool has solid_geometry. If no tool has solid_geometry we raise an Exception + has_solid_geo = 0 + for tooluid in geo_obj.tools: + if geo_obj.tools[tooluid]['solid_geometry']: + has_solid_geo += 1 + if has_solid_geo == 0: + app_obj.inform.emit('[ERROR] %s' % + _("There is no NCC Geometry in the file.\n" + "Usually it means that the tool diameter is too big for the painted geometry.\n" + "Change the painting parameters and try again.")) + return 'fail' + + # check to see if geo_obj.tools is empty + # it will be updated only if there is a solid_geometry for tools + if geo_obj.tools: + if warning_flag == 0: + self.app.inform.emit('[success] %s' % _("NCC Tool clear all done.")) + else: + self.app.inform.emit('[WARNING] %s: %s %s.' % ( + _("NCC Tool clear all done but the copper features isolation is broken for"), + str(warning_flag), + _("tools"))) + return + + # create the solid_geometry + geo_obj.solid_geometry = list() + for tooluid in geo_obj.tools: + if geo_obj.tools[tooluid]['solid_geometry']: + try: + for geo in geo_obj.tools[tooluid]['solid_geometry']: + geo_obj.solid_geometry.append(geo) + except TypeError: + geo_obj.solid_geometry.append(geo_obj.tools[tooluid]['solid_geometry']) + 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" + + # ########################################################################################### + # Initializes the new geometry object for the case of the rest-machining #################### + # ########################################################################################### + def gen_clear_area_rest(geo_obj, app_obj): + assert geo_obj.kind == 'geometry', \ + "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) + + log.debug("NCC Tool. Rest machining copper clearing task started.") + app_obj.inform.emit('_(NCC Tool. Rest machining copper clearing task started.') + + # provide the app with a way to process the GUI events when in a blocking loop + if not run_threaded: + QtWidgets.QApplication.processEvents() + + # a flag to signal that the isolation is broken by the bounding box in 'area' and 'box' cases + # will store the number of tools for which the isolation is broken + warning_flag = 0 + + sorted_tools.sort(reverse=True) + + cleared_geo = [] + cleared_by_last_tool = [] + rest_geo = [] + current_uid = 1 + try: + tool = eval(self.app.defaults["tools_ncctools"])[0] + except TypeError: + tool = eval(self.app.defaults["tools_ncctools"]) + + # repurposed flag for final object, geo_obj. True if it has any solid_geometry, False if not. + app_obj.poly_not_cleared = True + log.debug("NCC Tool. Calculate 'empty' area.") + app_obj.inform.emit("NCC Tool. Calculate 'empty' area.") + + # ################################################################################################### + # Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry ## + # ################################################################################################### + if ncc_obj.kind == 'gerber' and not isotooldia: + sol_geo = ncc_obj.solid_geometry + if has_offset is True: + app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) + sol_geo = sol_geo.buffer(distance=ncc_offset) + app_obj.inform.emit('[success] %s ...' % _("Buffering finished")) + empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) + if empty == 'fail': + return 'fail' + + if empty.is_empty: + app_obj.inform.emit('[ERROR_NOTCL] %s' % + _("Could not get the extent of the area to be non copper cleared.")) + return 'fail' + elif ncc_obj.kind == 'gerber' and isotooldia: + isolated_geo = [] + self.solid_geometry = ncc_obj.solid_geometry + + # if milling type is climb then the move is counter-clockwise around features + milling_type = self.app.defaults["tools_nccmilling_type"] + + for tool_iso in isotooldia: + new_geometry = [] + + if milling_type == 'cl': + isolated_geo = self.generate_envelope(tool_iso, 1) + else: + isolated_geo = self.generate_envelope(tool_iso, 0) + + if isolated_geo == 'fail': + app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated.")) + else: + app_obj.inform.emit('[WARNING_NOTCL] %s' % _("Isolation geometry is broken. Margin is less " + "than isolation tool diameter.")) + + try: + for geo_elem in isolated_geo: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + if isinstance(geo_elem, Polygon): + for ring in self.poly2rings(geo_elem): + new_geo = ring.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, MultiPolygon): + for poly in geo_elem: + for ring in self.poly2rings(poly): + new_geo = ring.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, LineString): + new_geo = geo_elem.intersection(bounding_box) + if new_geo: + if not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, MultiLineString): + for line_elem in geo_elem: + new_geo = line_elem.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + except TypeError: + try: + if isinstance(isolated_geo, Polygon): + for ring in self.poly2rings(isolated_geo): + new_geo = ring.intersection(bounding_box) + if new_geo: + if not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(isolated_geo, LineString): + new_geo = isolated_geo.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(isolated_geo, MultiLineString): + for line_elem in isolated_geo: + new_geo = line_elem.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + except Exception as e: + pass + + # a MultiLineString geometry element will show that the isolation is broken for this tool + for geo_e in new_geometry: + if type(geo_e) == MultiLineString: + warning_flag += 1 + break + + for k, v in tools_storage.items(): + if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, + tool_iso)): + 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(new_geometry) + v['data']['name'] = name + break + geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) + + sol_geo = cascaded_union(isolated_geo) + if has_offset is True: + app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) + sol_geo = sol_geo.buffer(distance=ncc_offset) + app_obj.inform.emit('[success] %s ...' % _("Buffering finished")) + empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) + if empty == 'fail': + return 'fail' + + if empty.is_empty: + app_obj.inform.emit('[ERROR_NOTCL] %s' % + _("Isolation geometry is broken. Margin is less than isolation tool diameter.")) + return 'fail' + + elif ncc_obj.kind == 'geometry': + sol_geo = cascaded_union(ncc_obj.solid_geometry) + if has_offset is True: + app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) + sol_geo = sol_geo.buffer(distance=ncc_offset) + app_obj.inform.emit('[success] %s ...' % _("Buffering finished")) + empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) + if empty == 'fail': + return 'fail' + + if empty.is_empty: + app_obj.inform.emit('[ERROR_NOTCL] %s' % + _("Could not get the extent of the area to be non copper cleared.")) + return 'fail' + else: + app_obj.inform.emit('[ERROR_NOTCL] %s' % _('The selected object is not suitable for copper clearing.')) + return + + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + if type(empty) is Polygon: + empty = MultiPolygon([empty]) + + area = empty.buffer(0) + + log.debug("NCC Tool. Finished calculation of 'empty' area.") + app_obj.inform.emit("NCC Tool. Finished calculation of 'empty' area.") + + # Generate area for each tool + while sorted_tools: + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + tool = sorted_tools.pop(0) + log.debug("Starting geometry processing for tool: %s" % str(tool)) + + app_obj.inform.emit('[success] %s = %s%s %s' % ( + _('NCC Tool clearing with tool diameter'), str(tool), units.lower(), _('started.')) + ) + app_obj.proc_container.update_view_text(' %d%%' % 0) + + tool_used = tool - 1e-12 + cleared_geo[:] = [] + + # Area to clear + for poly in cleared_by_last_tool: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + try: + area = area.difference(poly) + except Exception as e: + pass + cleared_by_last_tool[:] = [] + + # Transform area to MultiPolygon + if type(area) is Polygon: + area = MultiPolygon([area]) + + # add the rest that was not able to be cleared previously; area is a MultyPolygon + # and rest_geo it's a list + allparts = [p.buffer(0) for p in area.geoms] + allparts += deepcopy(rest_geo) + rest_geo[:] = [] + area = MultiPolygon(deepcopy(allparts)) + allparts[:] = [] + + # variables to display the percentage of work done + geo_len = len(area.geoms) + disp_number = 0 + old_disp_number = 0 + log.warning("Total number of polygons to be cleared. %s" % str(geo_len)) + + if area.geoms: + if len(area.geoms) > 0: + pol_nr = 0 + for p in area.geoms: + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + # clean the polygon + p = p.buffer(0) + + if p is not None and p.is_valid: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + + if isinstance(p, Polygon): + try: + if ncc_method == 'standard': + cp = self.clear_polygon(p, tool_used, + self.grb_circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) + elif ncc_method == 'seed': + cp = self.clear_polygon2(p, tool_used, + self.grb_circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) + else: + cp = self.clear_polygon3(p, tool_used, + self.grb_circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) + cleared_geo.append(list(cp.get_objects())) + except Exception as e: + log.warning("Polygon can't be cleared. %s" % str(e)) + # this polygon should be added to a list and then try clear it with + # a smaller tool + rest_geo.append(p) + elif isinstance(p, MultiPolygon): + for poly in p: + if poly is not None: + # provide the app with a way to process the GUI events when + # in a blocking loop + QtWidgets.QApplication.processEvents() + + try: + if ncc_method == 'standard': + cp = self.clear_polygon(poly, tool_used, + self.grb_circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) + elif ncc_method == 'seed': + cp = self.clear_polygon2(poly, tool_used, + self.grb_circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) + else: + cp = self.clear_polygon3(poly, tool_used, + self.grb_circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) + cleared_geo.append(list(cp.get_objects())) + except Exception as e: + log.warning("Polygon can't be cleared. %s" % str(e)) + # this polygon should be added to a list and then try clear it with + # a smaller tool + rest_geo.append(poly) + + 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 self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + # check if there is a geometry at all in the cleared geometry + if cleared_geo: + # Overall cleared area + cleared_area = list(self.flatten_list(cleared_geo)) + + # cleared = MultiPolygon([p.buffer(tool_used / 2).buffer(-tool_used / 2) + # for p in cleared_area]) + + # here we store the poly's already processed in the original geometry by the current tool + # into cleared_by_last_tool list + # this will be sutracted from the original geometry_to_be_cleared and make data for + # the next tool + buffer_value = tool_used / 2 + for p in cleared_area: + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + poly = p.buffer(buffer_value) + cleared_by_last_tool.append(poly) + + # 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 float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, + 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_area) + v['data']['name'] = name + cleared_area[:] = [] + break + + geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) + else: + log.debug("There are no geometries in the cleared polygon.") + + geo_obj.multigeo = True + geo_obj.options["cnctooldia"] = str(tool) + + # check to see if geo_obj.tools is empty + # it will be updated only if there is a solid_geometry for tools + if geo_obj.tools: + if warning_flag == 0: + self.app.inform.emit('[success] %s' % _("NCC Tool Rest Machining clear all done.")) + else: + self.app.inform.emit( + '[WARNING] %s: %s %s.' % (_("NCC Tool Rest Machining clear all done but the copper features " + "isolation is broken for"), str(warning_flag), _("tools"))) + return + + # create the solid_geometry + geo_obj.solid_geometry = list() + for tooluid in geo_obj.tools: + if geo_obj.tools[tooluid]['solid_geometry']: + try: + for geo in geo_obj.tools[tooluid]['solid_geometry']: + geo_obj.solid_geometry.append(geo) + except TypeError: + geo_obj.solid_geometry.append(geo_obj.tools[tooluid]['solid_geometry']) + 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" + + # ########################################################################################### + # Create the Job function and send it to the worker to be processed in another thread ####### + # ########################################################################################### + def job_thread(app_obj): + try: + if rest_machining_choice is True: + app_obj.new_object("geometry", name, gen_clear_area_rest, plot=plot) + else: + app_obj.new_object("geometry", name, gen_clear_area, plot=plot) + except FlatCAMApp.GracefulException: + if run_threaded: + proc.done() + return + except Exception: + if run_threaded: + proc.done() + traceback.print_stack() + return + + if run_threaded: + proc.done() + else: + app_obj.proc_container.view.set_idle() + + # focus on Selected Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab) + + if run_threaded: + # Promise object with the new name + self.app.collection.promise(name) + + # Background + self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) + else: + job_thread(app_obj=self.app) def get_ncc_empty_area(self, target, boundary=None): """ diff --git a/tclCommands/TclCommandCopperClear.py b/tclCommands/TclCommandCopperClear.py index d7f8e612..c57aa8a2 100644 --- a/tclCommands/TclCommandCopperClear.py +++ b/tclCommands/TclCommandCopperClear.py @@ -219,22 +219,22 @@ class TclCommandCopperClear(TclCommand): # Non-Copper clear all polygons in the non-copper clear object if 'all' in args and bool(args['all']): - self.app.ncclear_tool.clear_copper(ncc_obj=obj, - select_method='itself', - ncctooldia=tooldia, - overlap=overlap, - order=order, - margin=margin, - has_offset=has_offset, - offset=offset, - method=method, - outname=outname, - connect=connect, - contour=contour, - rest=rest, - tools_storage=ncc_tools, - plot=False, - run_threaded=False) + self.app.ncclear_tool.clear_copper_tcl(ncc_obj=obj, + select_method='itself', + ncctooldia=tooldia, + overlap=overlap, + order=order, + margin=margin, + has_offset=has_offset, + offset=offset, + method=method, + outname=outname, + connect=connect, + contour=contour, + rest=rest, + tools_storage=ncc_tools, + plot=False, + run_threaded=False) return # Non-Copper clear all polygons found within the box object from the the non_copper cleared object @@ -252,23 +252,23 @@ class TclCommandCopperClear(TclCommand): self.raise_tcl_error("%s: %s" % (_("Could not retrieve box object"), name)) return "Could not retrieve object: %s" % name - self.app.ncclear_tool.clear_copper(ncc_obj=obj, - sel_obj=box_obj, - select_method='box', - ncctooldia=tooldia, - overlap=overlap, - order=order, - margin=margin, - has_offset=has_offset, - offset=offset, - method=method, - outname=outname, - connect=connect, - contour=contour, - rest=rest, - tools_storage=ncc_tools, - plot=False, - run_threaded=False) + self.app.ncclear_tool.clear_copper_tcl(ncc_obj=obj, + sel_obj=box_obj, + select_method='box', + ncctooldia=tooldia, + overlap=overlap, + order=order, + margin=margin, + has_offset=has_offset, + offset=offset, + method=method, + outname=outname, + connect=connect, + contour=contour, + rest=rest, + tools_storage=ncc_tools, + plot=False, + run_threaded=False) return else: self.raise_tcl_error("%s:" % _("None of the following args: 'ref', 'all' were found or none was set to 1.\n"