From f956373ad0cfd7cfb747700319600cc218475be5 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 28 May 2020 02:53:11 +0300 Subject: [PATCH] - code cleanup in Isolation Tool --- AppTools/ToolIsolation.py | 908 +------------------------------------- 1 file changed, 7 insertions(+), 901 deletions(-) diff --git a/AppTools/ToolIsolation.py b/AppTools/ToolIsolation.py index feb89438..b8c26c20 100644 --- a/AppTools/ToolIsolation.py +++ b/AppTools/ToolIsolation.py @@ -12,20 +12,17 @@ from AppGUI.GUIElements import FCCheckBox, FCDoubleSpinner, RadioSet, FCTable, F FCComboBox, OptionalHideInputSection, FCSpinner from AppParsers.ParseGerber import Gerber -from camlib import grace - from copy import deepcopy import numpy as np import math -from shapely.geometry import base + from shapely.ops import cascaded_union from shapely.geometry import MultiPolygon, Polygon, MultiLineString, LineString, LinearRing from matplotlib.backend_bases import KeyEvent as mpl_key_event import logging -import traceback import gettext import AppTranslation as fcTranslate import builtins @@ -1603,110 +1600,6 @@ class ToolIsolation(AppTool, Gerber): self.app.worker_task.emit({'fcn': buffer_task, 'params': []}) - def on_isolate_click(self): - """ - Slot for clicking signal of the self.generate_iso_button - - :return: None - """ - - # init values for the next usage - self.reset_usage() - - self.app.defaults.report_usage("on_paint_button_click") - - self.grb_circle_steps = int(self.app.defaults["gerber_circle_steps"]) - self.obj_name = self.object_combo.currentText() - - # Get source object. - try: - self.grb_obj = self.app.collection.get_by_name(self.obj_name) - except Exception as e: - self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(self.obj_name))) - return "Could not retrieve object: %s with error: %s" % (self.obj_name, str(e)) - - if self.grb_obj is None: - self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(self.obj_name))) - return - - # use the selected tools in the tool table; get diameters for isolation - self.iso_dia_list = [] - # use the selected tools in the tool table; get diameters for non-copper clear - self.ncc_dia_list = [] - - if self.tools_table.selectedItems(): - for x in self.tools_table.selectedItems(): - try: - self.tooldia = float(self.tools_table.item(x.row(), 1).text()) - except ValueError: - # try to convert comma to decimal point. if it's still not working error message and return - try: - self.tooldia = float(self.tools_table.item(x.row(), 1).text().replace(',', '.')) - except ValueError: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong Tool Dia value format entered, " - "use a number.")) - continue - - self.iso_dia_list.append(self.tooldia) - else: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table.")) - return - - self.o_name = '%s_ncc' % self.obj_name - - self.select_method = self.select_combo.get_value() - if self.select_method == _('Itself'): - self.bound_obj_name = self.object_combo.currentText() - # Get source object. - try: - self.bound_obj = self.app.collection.get_by_name(self.bound_obj_name) - except Exception as e: - self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), self.bound_obj_name)) - return "Could not retrieve object: %s with error: %s" % (self.bound_obj_name, str(e)) - - self.clear_copper(ncc_obj=self.grb_obj, - ncctooldia=self.ncc_dia_list, - isotooldia=self.iso_dia_list, - outname=self.o_name) - elif self.select_method == _("Area Selection"): - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area.")) - - if self.app.is_legacy is False: - self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot) - self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot) - self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot) - else: - self.app.plotcanvas.graph_event_disconnect(self.app.mp) - self.app.plotcanvas.graph_event_disconnect(self.app.mm) - self.app.plotcanvas.graph_event_disconnect(self.app.mr) - - self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release) - self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move) - self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press) - - elif self.select_method == _("Reference Object"): - self.bound_obj_name = self.reference_combo.currentText() - # Get source object. - try: - self.bound_obj = self.app.collection.get_by_name(self.bound_obj_name) - except Exception as e: - self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), self.bound_obj_name)) - return "Could not retrieve object: %s. Error: %s" % (self.bound_obj_name, str(e)) - - self.clear_copper(ncc_obj=self.grb_obj, - sel_obj=self.bound_obj, - ncctooldia=self.ncc_dia_list, - isotooldia=self.iso_dia_list, - outname=self.o_name) - - - - - # ########################################### - # ########################################### - # ########################################### - # ########################################### - def on_iso_button_click(self, *args): self.obj_name = self.object_combo.currentText() @@ -1799,7 +1692,7 @@ class ToolIsolation(AppTool, Gerber): else: self.grid_status_memory = False - self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release) + self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_poly_mouse_click_release) self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press) if self.app.is_legacy is False: @@ -1941,8 +1834,8 @@ class ToolIsolation(AppTool, Gerber): if len(self.iso_tools) > 1: geo_obj.multigeo = True else: - passes = float(self.iso_tools[0]['data']['tools_iso_passes']) - geo_obj.multigeo = True if passes > 1 else False + passes_no = float(self.iso_tools[0]['data']['tools_iso_passes']) + geo_obj.multigeo = True if passes_no > 1 else False # detect if solid_geometry is empty and this require list flattening which is "heavy" # or just looking in the lists (they are one level depth) and if any is not empty @@ -2096,7 +1989,7 @@ class ToolIsolation(AppTool, Gerber): return 'fail' else: fc_obj.inform.emit('[success] %s: %s' % - (_("Isolation geometry created"), geo_obj.options["name"])) + (_("Isolation geometry created"), geo_obj.options["name"])) geo_obj.multigeo = False # TODO: Do something if this is None. Offer changing name? @@ -2215,7 +2108,7 @@ class ToolIsolation(AppTool, Gerber): new_geometry.append(new_geo) return new_geometry - def on_mouse_click_release(self, event): + def on_poly_mouse_click_release(self, event): if self.app.is_legacy is False: event_pos = event.pos right_button = 2 @@ -2278,7 +2171,7 @@ class ToolIsolation(AppTool, Gerber): self.app.ui.grid_snap_btn.trigger() if self.app.is_legacy is False: - self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release) + self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_poly_mouse_click_release) self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_pres) else: self.app.plotcanvas.graph_event_disconnect(self.mr) @@ -2365,14 +2258,6 @@ class ToolIsolation(AppTool, Gerber): else: self.app.inform.emit(_("No polygon in selection.")) - # ########################################### - # ########################################### - # ########################################### - # ########################################### - - - - # To be called after clicking on the plot. def on_mouse_release(self, event): if self.app.is_legacy is False: @@ -2605,785 +2490,6 @@ class ToolIsolation(AppTool, Gerber): self.delete_moving_selection_shape() self.delete_tool_selection_shape() - def get_tool_empty_area(self, name, ncc_obj, geo_obj, isotooldia, has_offset, ncc_offset, ncc_margin, - bounding_box, tools_storage): - """ - Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry. - - :param name: - :param ncc_obj: - :param geo_obj: - :param isotooldia: - :param has_offset: - :param ncc_offset: - :param ncc_margin: - :param bounding_box: - :param tools_storage: - :return: - """ - - log.debug("NCC Tool. Calculate 'empty' area.") - self.app.inform.emit(_("NCC Tool. Calculate 'empty' area.")) - - # 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 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. - # TODO 'buffering status' should be a property of the object not the project property - 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.milling_type_radio.get_value() - - 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': - self.app.inform.emit('[ERROR_NOTCL] %s %s' % - (_("Isolation geometry could not be generated."), str(tool_iso))) - continue - - if ncc_margin < tool_iso: - self.app.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 grace - - 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 - - current_uid = 0 - 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: - self.app.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) - sol_geo = sol_geo.buffer(distance=ncc_offset) - self.app.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: - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("Isolation geometry is broken. Margin is less than isolation tool diameter.")) - return 'fail' - else: - self.app.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.")) - - return empty, warning_flag - - def clear_copper(self, ncc_obj, sel_obj=None, ncctooldia=None, isotooldia=None, outname=None, order=None, - tools_storage=None, run_threaded=True): - """ - Clear the excess copper from the entire object. - - :param ncc_obj: ncc cleared object - :param sel_obj: - :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 outname: name of the resulting object - :param order: Tools order - :param tools_storage: whether to use the current tools_storage self.iso_tools or a different one. - Usage of the different one is related to when this function is called - from a TcL command. - - :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: - """ - log.debug("Executing the handler ...") - - 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'] - order = order if order else self.order_radio.get_value() - ncc_select = self.select_combo.get_value() - rest_machining_choice = self.rest_cb.get_value() - - # determine if to use the progressive plotting - prog_plot = True if self.app.defaults["tools_iso_plotting"] == 'progressive' else False - tools_storage = tools_storage if tools_storage is not None else self.iso_tools - - # ###################################################################################################### - # # Read the tooldia parameter and create a sorted list out them - they may be more than one diameter ## - # ###################################################################################################### - sorted_clear_tools = [] - if ncctooldia is not None: - try: - sorted_clear_tools = [float(eval(dia)) for dia in ncctooldia.split(",") if dia != ''] - except AttributeError: - if not isinstance(ncctooldia, list): - sorted_clear_tools = [float(ncctooldia)] - else: - sorted_clear_tools = ncctooldia - else: - # for row in range(self.tools_table.rowCount()): - # if self.tools_table.cellWidget(row, 1).currentText() == 'clear_op': - # sorted_clear_tools.append(float(self.tools_table.item(row, 1).text())) - for tooluid in self.iso_tools: - if self.iso_tools[tooluid]['data']['tools_nccoperation'] == 'clear': - sorted_clear_tools.append(self.iso_tools[tooluid]['tooldia']) - - # ######################################################################################################## - # 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 - # ######################################################################################################## - 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): - log.debug("NCC Tool. Normal copper clearing task started.") - self.app.inform.emit(_("NCC Tool. Finished non-copper polygons. Normal 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 - - if order == 'fwd': - sorted_clear_tools.sort(reverse=False) - elif order == 'rev': - sorted_clear_tools.sort(reverse=True) - else: - pass - - cleared_geo = [] - cleared = MultiPolygon() # Already cleared area - app_obj.poly_not_cleared = False # flag for polygons not cleared - - # Generate area for each tool - offset = sum(sorted_clear_tools) - current_uid = int(1) - # try: - # tool = eval(self.app.defaults["tools_ncctools"])[0] - # except TypeError: - # tool = eval(self.app.defaults["tools_ncctools"]) - - if ncc_select == _("Reference Object"): - bbox_geo, bbox_kind = self.calculate_bounding_box( - ncc_obj=ncc_obj, box_obj=sel_obj, ncc_select=ncc_select) - else: - bbox_geo, bbox_kind = self.calculate_bounding_box(ncc_obj=ncc_obj, ncc_select=ncc_select) - - if bbox_geo is None and bbox_kind is None: - self.app.inform.emit("[ERROR_NOTCL] %s" % _("NCC Tool failed creating bounding box.")) - return "fail" - - # COPPER CLEARING with tools marked for CLEAR# - for tool in sorted_clear_tools: - log.debug("Starting geometry processing for tool: %s" % str(tool)) - 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 - if not run_threaded: - 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) - - tool_uid = 0 # find the current tool_uid - for k, v in self.iso_tools.items(): - if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool)): - tool_uid = int(k) - break - - # parameters that are particular to the current tool - ncc_overlap = float(self.iso_tools[tool_uid]["data"]["tools_nccoverlap"]) / 100.0 - ncc_margin = float(self.iso_tools[tool_uid]["data"]["tools_nccmargin"]) - ncc_method = self.iso_tools[tool_uid]["data"]["tools_nccmethod"] - ncc_connect = self.iso_tools[tool_uid]["data"]["tools_nccconnect"] - ncc_contour = self.iso_tools[tool_uid]["data"]["tools_ncccontour"] - has_offset = self.iso_tools[tool_uid]["data"]["tools_ncc_offset_choice"] - ncc_offset = float(self.iso_tools[tool_uid]["data"]["tools_ncc_offset_value"]) - - # Get remaining tools offset - offset -= (tool - 1e-12) - - # Bounding box for current tool - bbox = self.apply_margin_to_bounding_box(bbox=bbox_geo, box_kind=bbox_kind, - ncc_select=ncc_select, ncc_margin=ncc_margin) - - # Area to clear - empty, warning_flag = self.get_tool_empty_area(name=name, ncc_obj=ncc_obj, geo_obj=geo_obj, - isotooldia=isotooldia, ncc_margin=ncc_margin, - has_offset=has_offset, ncc_offset=ncc_offset, - tools_storage=tools_storage, bounding_box=bbox) - - area = empty.buffer(-offset) - try: - area = area.difference(cleared) - except Exception: - continue - - # Transform area to MultiPolygon - if isinstance(area, 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)) - - cleared_geo[:] = [] - 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 - 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) - - if p is not None and p.is_valid: - poly_failed = 0 - try: - for pol in p: - 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: - 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: - log.warning("Expected geo is a Polygon. Instead got a %s" % str(type(p))) - - 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)) - - # check if there is a geometry at all in the cleared geometry - if cleared_geo: - cleared = empty.buffer(-offset * (1 + ncc_overlap)) # Overall cleared area - cleared = cleared.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.") - # clean the progressive plotted shapes if it was used - if self.app.defaults["tools_iso_plotting"] == 'progressive': - self.temp_shapes.clear(update=True) - - # 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 tid in geo_obj.tools: - if geo_obj.tools[tid]['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 = [] - for tool_id in geo_obj.tools: - if geo_obj.tools[tool_id]['solid_geometry']: - try: - for geo in geo_obj.tools[tool_id]['solid_geometry']: - geo_obj.solid_geometry.append(geo) - except TypeError: - geo_obj.solid_geometry.append(geo_obj.tools[tool_id]['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" - - # # Experimental... - # # print("Indexing...", end=' ') - # # geo_obj.make_index() - - # ########################################################################################### - # 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 GeometryObject, 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_clear_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 - - if ncc_select == _("Reference Object"): - env_obj, box_obj_kind = self.calculate_bounding_box( - ncc_obj=ncc_obj, box_obj=sel_obj, ncc_select=ncc_select) - else: - env_obj, box_obj_kind = self.calculate_bounding_box(ncc_obj=ncc_obj, ncc_select=ncc_select) - - if env_obj is None and box_obj_kind is None: - self.app.inform.emit("[ERROR_NOTCL] %s" % _("NCC Tool failed creating bounding box.")) - return "fail" - - log.debug("NCC Tool. Calculate 'empty' area.") - app_obj.inform.emit("NCC Tool. Calculate 'empty' area.") - - # Generate area for each tool - while sorted_clear_tools: - log.debug("Starting geometry processing for tool: %s" % str(tool)) - 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() - - 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 = sorted_clear_tools.pop(0) - - tool_uid = 0 - for k, v in self.iso_tools.items(): - if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool)): - tool_uid = int(k) - break - - ncc_overlap = float(self.iso_tools[tool_uid]["data"]["tools_nccoverlap"]) / 100.0 - ncc_margin = float(self.iso_tools[tool_uid]["data"]["tools_nccmargin"]) - ncc_method = self.iso_tools[tool_uid]["data"]["tools_nccmethod"] - ncc_connect = self.iso_tools[tool_uid]["data"]["tools_nccconnect"] - ncc_contour = self.iso_tools[tool_uid]["data"]["tools_ncccontour"] - has_offset = self.iso_tools[tool_uid]["data"]["tools_ncc_offset_choice"] - ncc_offset = float(self.iso_tools[tool_uid]["data"]["tools_ncc_offset_value"]) - - tool_used = tool - 1e-12 - cleared_geo[:] = [] - - # Bounding box for current tool - bbox = self.apply_margin_to_bounding_box(bbox=env_obj, box_kind=box_obj_kind, - ncc_select=ncc_select, ncc_margin=ncc_margin) - - # Area to clear - empty, warning_flag = self.get_tool_empty_area(name=name, ncc_obj=ncc_obj, geo_obj=geo_obj, - isotooldia=isotooldia, - has_offset=has_offset, ncc_offset=ncc_offset, - ncc_margin=ncc_margin, tools_storage=tools_storage, - bounding_box=bbox) - - area = empty.buffer(0) - - # 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 grace - try: - area = area.difference(poly) - except Exception: - 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) - 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 grace - - # 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=ncc_overlap, contour=ncc_contour, - connect=ncc_connect, - prog_plot=prog_plot) - elif ncc_method == _("Seed"): - cp = self.clear_polygon2(p, tool_used, - self.grb_circle_steps, - overlap=ncc_overlap, contour=ncc_contour, - connect=ncc_connect, - prog_plot=prog_plot) - else: - cp = self.clear_polygon3(p, tool_used, - self.grb_circle_steps, - overlap=ncc_overlap, contour=ncc_contour, - connect=ncc_connect, - prog_plot=prog_plot) - 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=ncc_overlap, contour=ncc_contour, - connect=ncc_connect, - prog_plot=prog_plot) - elif ncc_method == _("Seed"): - cp = self.clear_polygon2(poly, tool_used, - self.grb_circle_steps, - overlap=ncc_overlap, contour=ncc_contour, - connect=ncc_connect, - prog_plot=prog_plot) - else: - cp = self.clear_polygon3(poly, tool_used, - self.grb_circle_steps, - overlap=ncc_overlap, contour=ncc_contour, - connect=ncc_connect, - prog_plot=prog_plot) - 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 grace - - # 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 grace - - poly = p.buffer(buffer_value) - cleared_by_last_tool.append(poly) - - # find the tool uid 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) - - # clean the progressive plotted shapes if it was used - if self.app.defaults["tools_iso_plotting"] == 'progressive': - self.temp_shapes.clear(update=True) - - # 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 = [] - for tool_uid in geo_obj.tools: - if geo_obj.tools[tool_uid]['solid_geometry']: - try: - for geo in geo_obj.tools[tool_uid]['solid_geometry']: - geo_obj.solid_geometry.append(geo) - 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 - # 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(a_obj): - try: - if rest_machining_choice is True: - a_obj.app_obj.new_object("geometry", name, gen_clear_area_rest) - else: - a_obj.app_obj.new_object("geometry", name, gen_clear_area) - except grace: - if run_threaded: - proc.done() - return - except Exception: - if run_threaded: - proc.done() - traceback.print_stack() - return - - if run_threaded: - proc.done() - else: - a_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) - @staticmethod def poly2rings(poly): return [poly.exterior] + [interior for interior in poly.interiors]