- code cleanup in Isolation Tool

This commit is contained in:
Marius Stanciu 2020-05-28 02:53:11 +03:00 committed by Marius
parent 5d813c1224
commit f956373ad0
1 changed files with 7 additions and 901 deletions

View File

@ -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]