- fixed open handlers

- fixed issue in NCC Tool where the tool table context menu could be installed multiple times
- added new ability to create simple isolation's in the NCC Tool
This commit is contained in:
Marius Stanciu 2019-09-02 00:14:28 +03:00 committed by Marius
parent bb9c35a527
commit 1295a94af1
4 changed files with 292 additions and 83 deletions

View File

@ -6764,7 +6764,7 @@ class App(QtCore.QObject):
self.report_usage("obj_move()") self.report_usage("obj_move()")
self.move_tool.run(toggle=False) self.move_tool.run(toggle=False)
def on_fileopengerber(self, name=None): def on_fileopengerber(self, checked=None, name=None):
""" """
File menu callback for opening a Gerber. File menu callback for opening a Gerber.
@ -6802,10 +6802,9 @@ class App(QtCore.QObject):
else: else:
for filename in filenames: for filename in filenames:
if filename != '': if filename != '':
self.worker_task.emit({'fcn': self.open_gerber, self.worker_task.emit({'fcn': self.open_gerber, 'params': [filename]})
'params': [filename]})
def on_fileopenexcellon(self, name=None): def on_fileopenexcellon(self, checked=None, name=None):
""" """
File menu callback for opening an Excellon file. File menu callback for opening an Excellon file.
@ -6833,10 +6832,9 @@ class App(QtCore.QObject):
else: else:
for filename in filenames: for filename in filenames:
if filename != '': if filename != '':
self.worker_task.emit({'fcn': self.open_excellon, self.worker_task.emit({'fcn': self.open_excellon, 'params': [filename]})
'params': [filename]})
def on_fileopengcode(self, name=None): def on_fileopengcode(self, checked=None, name=None):
""" """
File menu call back for opening gcode. File menu call back for opening gcode.
@ -6868,10 +6866,9 @@ class App(QtCore.QObject):
else: else:
for filename in filenames: for filename in filenames:
if filename != '': if filename != '':
self.worker_task.emit({'fcn': self.open_gcode, self.worker_task.emit({'fcn': self.open_gcode, 'params': [filename]})
'params': [filename]})
def on_file_openproject(self): def on_file_openproject(self, checked=None):
""" """
File menu callback for opening a project. File menu callback for opening a project.
@ -6901,7 +6898,7 @@ class App(QtCore.QObject):
# thread safe. The new_project() # thread safe. The new_project()
self.open_project(filename) self.open_project(filename)
def on_file_openconfig(self): def on_file_openconfig(self, checked=None):
""" """
File menu callback for opening a config file. File menu callback for opening a config file.

View File

@ -9,6 +9,12 @@ CAD program, and create G-Code for Isolation routing.
================================================= =================================================
1.09.2019
- fixed open handlers
- fixed issue in NCC Tool where the tool table context menu could be installed multiple times
- added new ability to create simple isolation's in the NCC Tool
27.08.2019 27.08.2019
- made FlatCAM so that whenever an associated file is double clicked, if there is an opened instance of FlatCAM, the file will be opened in the first instance without launching a new instance of FlatCAM. If FlatCAM is launched again it will spawn a new process (hopefully it will work when freezed). - made FlatCAM so that whenever an associated file is double clicked, if there is an opened instance of FlatCAM, the file will be opened in the first instance without launching a new instance of FlatCAM. If FlatCAM is launched again it will spawn a new process (hopefully it will work when freezed).

View File

@ -912,7 +912,7 @@ class GeometryObjectUI(ObjectUI):
self.geo_tools_table.horizontalHeaderItem(3).setToolTip( self.geo_tools_table.horizontalHeaderItem(3).setToolTip(
_( _(
"The (Operation) Type has only informative value. Usually the UI form values \n" "The (Operation) Type has only informative value. Usually the UI form values \n"
"are choosed based on the operation type and this will serve as a reminder.\n" "are choose based on the operation type and this will serve as a reminder.\n"
"Can be 'Roughing', 'Finishing' or 'Isolation'.\n" "Can be 'Roughing', 'Finishing' or 'Isolation'.\n"
"For Roughing we may choose a lower Feedrate and multiDepth cut.\n" "For Roughing we may choose a lower Feedrate and multiDepth cut.\n"
"For Finishing we may choose a higher Feedrate, without multiDepth.\n" "For Finishing we may choose a higher Feedrate, without multiDepth.\n"

View File

@ -103,8 +103,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.tools_table = FCTable() self.tools_table = FCTable()
self.tools_box.addWidget(self.tools_table) self.tools_box.addWidget(self.tools_table)
self.tools_table.setColumnCount(4) self.tools_table.setColumnCount(5)
self.tools_table.setHorizontalHeaderLabels(['#', _('Diameter'), _('TT'), '']) self.tools_table.setHorizontalHeaderLabels(['#', _('Diameter'), _('TT'), '', _("Operation")])
self.tools_table.setColumnHidden(3, True) self.tools_table.setColumnHidden(3, True)
self.tools_table.setSortingEnabled(False) self.tools_table.setSortingEnabled(False)
# self.tools_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) # self.tools_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
@ -118,22 +118,28 @@ class NonCopperClear(FlatCAMTool, Gerber):
"this function will not be able to create painting geometry.") "this function will not be able to create painting geometry.")
) )
self.tools_table.horizontalHeaderItem(1).setToolTip( self.tools_table.horizontalHeaderItem(1).setToolTip(
_("Tool Diameter. It's value (in current FlatCAM units) \n" _("Tool Diameter. It's value (in current FlatCAM units)\n"
"is the cut width into the material.")) "is the cut width into the material."))
self.tools_table.horizontalHeaderItem(2).setToolTip( self.tools_table.horizontalHeaderItem(2).setToolTip(
_("The Tool Type (TT) can be:<BR>" _("The Tool Type (TT) can be:\n"
"- <B>Circular</B> with 1 ... 4 teeth -> it is informative only. Being circular, <BR>" "- Circular with 1 ... 4 teeth -> it is informative only. Being circular,\n"
"the cut width in material is exactly the tool diameter.<BR>" "the cut width in material is exactly the tool diameter.\n"
"- <B>Ball</B> -> informative only and make reference to the Ball type endmill.<BR>" "- Ball -> informative only and make reference to the Ball type endmill.\n"
"- <B>V-Shape</B> -> it will disable de Z-Cut parameter in the resulting geometry UI form " "- V-Shape -> it will disable de Z-Cut parameter in the resulting geometry UI form\n"
"and enable two additional UI form fields in the resulting geometry: V-Tip Dia and " "and enable two additional UI form fields in the resulting geometry: V-Tip Dia and\n"
"V-Tip Angle. Adjusting those two values will adjust the Z-Cut parameter such " "V-Tip Angle. Adjusting those two values will adjust the Z-Cut parameter such\n"
"as the cut width into material will be equal with the value in the Tool Diameter " "as the cut width into material will be equal with the value in the Tool Diameter\n"
"column of this table.<BR>" "column of this table.\n"
"Choosing the <B>V-Shape</B> Tool Type automatically will select the Operation Type " "Choosing the 'V-Shape' Tool Type automatically will select the Operation Type\n"
"in the resulting geometry as Isolation.")) "in the resulting geometry as Isolation."))
self.tools_table.horizontalHeaderItem(4).setToolTip(
_("The 'Operation' can be:\n"
"- Isolation -> will ensure that the non-copper clearing is always complete.\n"
"If it's not successful then the non-copper clearing will fail, too.\n"
"- Clear -> the regular non-copper clearing."))
self.ncc_order_label = QtWidgets.QLabel('<b>%s:</b>' % _('Tool order')) self.ncc_order_label = QtWidgets.QLabel('<b>%s:</b>' % _('Tool order'))
self.ncc_order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n" self.ncc_order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
"'No' --> means that the used order is the one in the tool table\n" "'No' --> means that the used order is the one in the tool table\n"
@ -363,6 +369,13 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.tools_box.addWidget(self.generate_ncc_button) self.tools_box.addWidget(self.generate_ncc_button)
self.tools_box.addStretch() self.tools_box.addStretch()
self.tools_table.setupContextMenu()
self.tools_table.addContextMenu(
"Add", lambda: self.on_tool_add(dia=None, muted=None), icon=QtGui.QIcon("share/plus16.png"))
self.tools_table.addContextMenu(
"Delete", lambda:
self.on_tool_delete(rows_to_delete=None, all=None), icon=QtGui.QIcon("share/delete32.png"))
self.units = '' self.units = ''
self.ncc_tools = {} self.ncc_tools = {}
self.tooluid = 0 self.tooluid = 0
@ -381,6 +394,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.cursor_pos = None self.cursor_pos = None
self.mouse_is_dragging = False self.mouse_is_dragging = False
# store here solid_geometry when there are tool with isolation job
self.solid_geometry = []
self.addtool_btn.clicked.connect(self.on_tool_add) self.addtool_btn.clicked.connect(self.on_tool_add)
self.addtool_entry.returnPressed.connect(self.on_tool_add) self.addtool_entry.returnPressed.connect(self.on_tool_add)
self.deltool_btn.clicked.connect(self.on_tool_delete) self.deltool_btn.clicked.connect(self.on_tool_delete)
@ -448,13 +464,6 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.ncc_rest_cb.set_value(self.app.defaults["tools_nccrest"]) self.ncc_rest_cb.set_value(self.app.defaults["tools_nccrest"])
self.reference_radio.set_value(self.app.defaults["tools_nccref"]) self.reference_radio.set_value(self.app.defaults["tools_nccref"])
self.tools_table.setupContextMenu()
self.tools_table.addContextMenu(
"Add", lambda: self.on_tool_add(dia=None, muted=None), icon=QtGui.QIcon("share/plus16.png"))
self.tools_table.addContextMenu(
"Delete", lambda:
self.on_tool_delete(rows_to_delete=None, all=None), icon=QtGui.QIcon("share/delete32.png"))
# init the working variables # init the working variables
self.default_data.clear() self.default_data.clear()
self.default_data.update({ self.default_data.update({
@ -515,6 +524,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
'offset_value': 0.0, 'offset_value': 0.0,
'type': 'Iso', 'type': 'Iso',
'tool_type': 'V', 'tool_type': 'V',
'operation': 'clear',
'data': dict(self.default_data), 'data': dict(self.default_data),
'solid_geometry': [] 'solid_geometry': []
} }
@ -583,12 +593,22 @@ class NonCopperClear(FlatCAMTool, Gerber):
tool_uid_item = QtWidgets.QTableWidgetItem(str(int(tooluid_key))) tool_uid_item = QtWidgets.QTableWidgetItem(str(int(tooluid_key)))
operation_type = QtWidgets.QComboBox()
operation_type.addItem('iso')
operation_type.setStyleSheet('background-color: rgb(255,255,255)')
operation_type.addItem('clear')
operation_type.setStyleSheet('background-color: rgb(255,255,255)')
op_idx = operation_type.findText(tooluid_value['operation'])
operation_type.setCurrentIndex(op_idx)
self.tools_table.setItem(row_no, 1, dia) # Diameter self.tools_table.setItem(row_no, 1, dia) # Diameter
self.tools_table.setCellWidget(row_no, 2, tool_type_item) self.tools_table.setCellWidget(row_no, 2, tool_type_item)
# ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ## # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
self.tools_table.setItem(row_no, 3, tool_uid_item) # Tool unique ID self.tools_table.setItem(row_no, 3, tool_uid_item) # Tool unique ID
self.tools_table.setCellWidget(row_no, 4, operation_type)
# make the diameter column editable # make the diameter column editable
for row in range(tool_id): for row in range(tool_id):
self.tools_table.item(row, 1).setFlags( self.tools_table.item(row, 1).setFlags(
@ -728,6 +748,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
'offset_value': 0.0, 'offset_value': 0.0,
'type': 'Iso', 'type': 'Iso',
'tool_type': 'V', 'tool_type': 'V',
'operation': 'clear',
'data': dict(self.default_data), 'data': dict(self.default_data),
'solid_geometry': [] 'solid_geometry': []
} }
@ -863,8 +884,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.app.inform.emit(_("[ERROR_NOTCL] Object not found: %s") % self.ncc_obj) self.app.inform.emit(_("[ERROR_NOTCL] Object not found: %s") % self.ncc_obj)
return return
# use the selected tools in the tool table; get diameters # use the selected tools in the tool table; get diameters for non-copper clear
tooldia_list = list() iso_dia_list = list()
# use the selected tools in the tool table; get diameters for non-copper clear
ncc_dia_list = list()
if self.tools_table.selectedItems(): if self.tools_table.selectedItems():
for x in self.tools_table.selectedItems(): for x in self.tools_table.selectedItems():
try: try:
@ -877,7 +900,11 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.app.inform.emit(_("[ERROR_NOTCL] Wrong Tool Dia value format entered, " self.app.inform.emit(_("[ERROR_NOTCL] Wrong Tool Dia value format entered, "
"use a number.")) "use a number."))
continue continue
tooldia_list.append(tooldia)
if self.tools_table.cellWidget(x.row(), 4).currentText() == 'iso':
iso_dia_list.append(tooldia)
else:
ncc_dia_list.append(tooldia)
else: else:
self.app.inform.emit(_("[ERROR_NOTCL] No selected tools in Tool Table.")) self.app.inform.emit(_("[ERROR_NOTCL] No selected tools in Tool Table."))
return return
@ -895,7 +922,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
return "Could not retrieve object: %s" % self.obj_name return "Could not retrieve object: %s" % self.obj_name
self.clear_copper(ncc_obj=self.ncc_obj, self.clear_copper(ncc_obj=self.ncc_obj,
tooldia=tooldia_list, ncctooldia=ncc_dia_list,
isotooldia=iso_dia_list,
has_offset=has_offset, has_offset=has_offset,
outname=o_name, outname=o_name,
overlap=overlap, overlap=overlap,
@ -951,7 +979,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.sel_rect = cascaded_union(self.sel_rect) self.sel_rect = cascaded_union(self.sel_rect)
self.clear_copper(ncc_obj=self.ncc_obj, self.clear_copper(ncc_obj=self.ncc_obj,
sel_obj=self.bound_obj, sel_obj=self.bound_obj,
tooldia=tooldia_list, ncctooldia=ncc_dia_list,
isotooldia=iso_dia_list,
has_offset=has_offset, has_offset=has_offset,
outname=o_name, outname=o_name,
overlap=overlap, overlap=overlap,
@ -977,7 +1006,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.sel_rect = cascaded_union(self.sel_rect) self.sel_rect = cascaded_union(self.sel_rect)
self.clear_copper(ncc_obj=self.ncc_obj, self.clear_copper(ncc_obj=self.ncc_obj,
sel_obj=self.bound_obj, sel_obj=self.bound_obj,
tooldia=tooldia_list, ncctooldia=ncc_dia_list,
isotooldia=iso_dia_list,
has_offset=has_offset, has_offset=has_offset,
outname=o_name, outname=o_name,
overlap=overlap, overlap=overlap,
@ -1026,7 +1056,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.clear_copper(ncc_obj=self.ncc_obj, self.clear_copper(ncc_obj=self.ncc_obj,
sel_obj=self.bound_obj, sel_obj=self.bound_obj,
tooldia=tooldia_list, ncctooldia=ncc_dia_list,
isotooldia=iso_dia_list,
has_offset=has_offset, has_offset=has_offset,
outname=o_name, outname=o_name,
overlap=overlap, overlap=overlap,
@ -1036,7 +1067,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
def clear_copper(self, ncc_obj, def clear_copper(self, ncc_obj,
sel_obj=None, sel_obj=None,
tooldia=None, ncctooldia=None,
isotooldia=None,
margin=None, margin=None,
has_offset=None, has_offset=None,
offset=None, offset=None,
@ -1053,7 +1085,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
Clear the excess copper from the entire object. Clear the excess copper from the entire object.
:param ncc_obj: ncc cleared object :param ncc_obj: ncc cleared object
:param tooldia: a tuple or single element made out of diameters of the tools to be used :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 overlap: value by which the paths will overlap
:param order: if the tools are ordered and how :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 :param select_method: if to do ncc on the whole object, on an defined area or on an area defined by
@ -1124,17 +1157,18 @@ class NonCopperClear(FlatCAMTool, Gerber):
# # Read the tooldia parameter and create a sorted list out them - they may be more than one diameter ## # # Read the tooldia parameter and create a sorted list out them - they may be more than one diameter ##
# ###################################################################################################### # ######################################################################################################
sorted_tools = [] sorted_tools = []
if tooldia is not None: if ncctooldia is not None:
try: try:
sorted_tools = [float(eval(dia)) for dia in tooldia.split(",") if dia != ''] sorted_tools = [float(eval(dia)) for dia in ncctooldia.split(",") if dia != '']
except AttributeError: except AttributeError:
if not isinstance(tooldia, list): if not isinstance(ncctooldia, list):
sorted_tools = [float(tooldia)] sorted_tools = [float(ncctooldia)]
else: else:
sorted_tools = tooldia sorted_tools = ncctooldia
else: else:
for row in range(self.tools_table.rowCount()): for row in range(self.tools_table.rowCount()):
sorted_tools.append(float(self.tools_table.item(row, 1).text())) if self.tools_table.cellWidget(row, 1).currentText() == 'clear':
sorted_tools.append(float(self.tools_table.item(row, 1).text()))
# ############################################################################################################## # ##############################################################################################################
# Prepare non-copper polygons. Create the bounding box area from which the copper features will be subtracted ## # Prepare non-copper polygons. Create the bounding box area from which the copper features will be subtracted ##
@ -1200,37 +1234,6 @@ class NonCopperClear(FlatCAMTool, Gerber):
log.debug("NonCopperClear.clear_copper() --> %s" % str(e)) log.debug("NonCopperClear.clear_copper() --> %s" % str(e))
return 'fail' return 'fail'
# ###################################################################################################
# Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry ##
# ###################################################################################################
if isinstance(ncc_obj, FlatCAMGerber):
if has_offset is True:
self.app.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
offseted_geo = ncc_obj.solid_geometry.buffer(distance=ncc_offset)
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=ncc_obj.solid_geometry, boundary=bounding_box)
elif isinstance(ncc_obj, FlatCAMGeometry):
sol_geo = cascaded_union(ncc_obj.solid_geometry)
if has_offset is True:
self.app.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
offseted_geo = sol_geo.buffer(distance=ncc_offset)
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 empty.is_empty:
self.app.inform.emit(_("[ERROR_NOTCL] Could not get the extent of the area to be non copper cleared."))
return
if type(empty) is Polygon:
empty = MultiPolygon([empty])
# ######################################################################################################## # ########################################################################################################
# set the name for the future Geometry object # 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 # I do it here because it is also stored inside the gen_clear_area() and gen_clear_area_rest() methods
@ -1267,8 +1270,110 @@ class NonCopperClear(FlatCAMTool, Gerber):
current_uid = int(1) current_uid = int(1)
tool = eval(self.app.defaults["tools_ncctools"])[0] tool = eval(self.app.defaults["tools_ncctools"])[0]
# ###################################################################################################
# Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry ##
# ###################################################################################################
if isinstance(ncc_obj, FlatCAMGerber) and not isotooldia:
sol_geo = ncc_obj.solid_geometry
if has_offset is True:
app_obj.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
sol_geo = sol_geo.buffer(distance=ncc_offset)
app_obj.inform.emit(_("[success] Buffering finished ..."))
empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
elif isinstance(ncc_obj, FlatCAMGerber) 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 = 'cl'
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] Isolation geometry could not be generated."))
else:
try:
for geo_elem in isolated_geo:
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)
for k, v in tools_storage.items():
if float('%.4f' % v['tooldia']) == float('%.4f' % 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] Buffering ..."))
sol_geo = sol_geo.buffer(distance=ncc_offset)
app_obj.inform.emit(_("[success] Buffering finished ..."))
empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
elif isinstance(ncc_obj, FlatCAMGeometry):
sol_geo = cascaded_union(ncc_obj.solid_geometry)
if has_offset is True:
app_obj.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
sol_geo = sol_geo.buffer(distance=ncc_offset)
app_obj.inform.emit(_("[success] Buffering finished ..."))
empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
else:
app_obj.inform.emit(_('[ERROR_NOTCL] The selected object is not suitable for copper clearing.'))
return
if empty.is_empty:
app_obj.inform.emit(_("[ERROR_NOTCL] Could not get the extent of the area to be non copper cleared."))
return 'fail'
if type(empty) is Polygon:
empty = MultiPolygon([empty])
for tool in sorted_tools: for tool in sorted_tools:
self.app.inform.emit(_('[success] Non-Copper Clearing with ToolDia = %s started.') % str(tool)) app_obj.inform.emit(_('[success] Non-Copper Clearing with ToolDia = %s started.') % str(tool))
cleared_geo[:] = [] cleared_geo[:] = []
# Get remaining tools offset # Get remaining tools offset
@ -1352,9 +1457,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
if geo_obj.tools[tooluid]['solid_geometry']: if geo_obj.tools[tooluid]['solid_geometry']:
has_solid_geo += 1 has_solid_geo += 1
if has_solid_geo == 0: if has_solid_geo == 0:
self.app.inform.emit(_("[ERROR] There is no Painting Geometry in the file.\n" app_obj.inform.emit(_("[ERROR] There is no Painting Geometry in the file.\n"
"Usually it means that the tool diameter is too big for the painted geometry.\n" "Usually it means that the tool diameter is too big for the painted geometry.\n"
"Change the painting parameters and try again.")) "Change the painting parameters and try again."))
return return
# Experimental... # Experimental...
@ -1381,11 +1486,71 @@ class NonCopperClear(FlatCAMTool, Gerber):
# repurposed flag for final object, geo_obj. True if it has any solid_geometry, False if not. # repurposed flag for final object, geo_obj. True if it has any solid_geometry, False if not.
app_obj.poly_not_cleared = True app_obj.poly_not_cleared = True
# ###################################################################################################
# Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry ##
# ###################################################################################################
if isinstance(ncc_obj, FlatCAMGerber) and not isotooldia:
sol_geo = ncc_obj.solid_geometry
if has_offset is True:
app_obj.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
sol_geo = sol_geo.buffer(distance=ncc_offset)
app_obj.inform.emit(_("[success] Buffering finished ..."))
empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
elif isinstance(ncc_obj, FlatCAMGerber) 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 = 'cl'
for tool_iso in isotooldia:
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] Isolation geometry could not be generated."))
else:
for k, v in tools_storage.items():
if float('%.4f' % v['tooldia']) == float('%.4f' % 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(isolated_geo)
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] Buffering ..."))
sol_geo = sol_geo.buffer(distance=ncc_offset)
app_obj.inform.emit(_("[success] Buffering finished ..."))
empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
elif isinstance(ncc_obj, FlatCAMGeometry):
sol_geo = cascaded_union(ncc_obj.solid_geometry)
if has_offset is True:
app_obj.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
sol_geo = sol_geo.buffer(distance=ncc_offset)
app_obj.inform.emit(_("[success] Buffering finished ..."))
empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
else:
app_obj.inform.emit(_('[ERROR_NOTCL] The selected object is not suitable for copper clearing.'))
return
if empty.is_empty:
app_obj.inform.emit(_("[ERROR_NOTCL] Could not get the extent of the area to be non copper cleared."))
return 'fail'
if type(empty) is Polygon:
empty = MultiPolygon([empty])
area = empty.buffer(0) area = empty.buffer(0)
# Generate area for each tool # Generate area for each tool
while sorted_tools: while sorted_tools:
tool = sorted_tools.pop(0) tool = sorted_tools.pop(0)
self.app.inform.emit(_('[success] Non-Copper Rest Clearing with ToolDia = %s started.') % str(tool)) app_obj.inform.emit(_('[success] Non-Copper Rest Clearing with ToolDia = %s started.') % str(tool))
tool_used = tool - 1e-12 tool_used = tool - 1e-12
cleared_geo[:] = [] cleared_geo[:] = []
@ -1879,3 +2044,44 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.mouse_is_dragging = False self.mouse_is_dragging = False
self.sel_rect = [] self.sel_rect = []
@staticmethod
def poly2rings(poly):
return [poly.exterior] + [interior for interior in poly.interiors]
def generate_envelope(self, offset, invert, envelope_iso_type=2, follow=None):
# isolation_geometry produces an envelope that is going on the left of the geometry
# (the copper features). To leave the least amount of burrs on the features
# the tool needs to travel on the right side of the features (this is called conventional milling)
# the first pass is the one cutting all of the features, so it needs to be reversed
# the other passes overlap preceding ones and cut the left over copper. It is better for them
# to cut on the right side of the left over copper i.e on the left side of the features.
try:
geom = self.isolation_geometry(offset, iso_type=envelope_iso_type, follow=follow)
except Exception as e:
log.debug('NonCopperClear.generate_envelope() --> %s' % str(e))
return 'fail'
if invert:
try:
try:
pl = []
for p in geom:
if p is not None:
if isinstance(p, Polygon):
pl.append(Polygon(p.exterior.coords[::-1], p.interiors))
elif isinstance(p, LinearRing):
pl.append(Polygon(p.coords[::-1]))
geom = MultiPolygon(pl)
except TypeError:
if isinstance(geom, Polygon) and geom is not None:
geom = Polygon(geom.exterior.coords[::-1], geom.interiors)
elif isinstance(geom, LinearRing) and geom is not None:
geom = Polygon(geom.coords[::-1])
else:
log.debug("NonCopperClear.generate_envelope() Error --> Unexpected Geometry %s" %
type(geom))
except Exception as e:
log.debug("NonCopperClear.generate_envelope() Error --> %s" % str(e))
return 'fail'
return geom