- in Paint Tool and NCC Tool updated the way the selected tools were processed and made sure that the Tools Table rows are counted only once in the processing

- modified the UI in Paint Tool such that in case of using rest machining the offset will apply for all tools
- Paint Tool - made the rest machining function for the paint single polygon method
This commit is contained in:
Marius Stanciu
2020-06-15 03:34:34 +03:00
committed by Marius
parent 5dcc31ef8b
commit 9968fd14f2
5 changed files with 469 additions and 175 deletions

View File

@@ -7,6 +7,12 @@ CHANGELOG for FlatCAM beta
================================================= =================================================
15.06.2020
- in Paint Tool and NCC Tool updated the way the selected tools were processed and made sure that the Tools Table rows are counted only once in the processing
- modified the UI in Paint Tool such that in case of using rest machining the offset will apply for all tools
- Paint Tool - made the rest machining function for the paint single polygon method
14.06.2020 14.06.2020
- made sure that clicking the icons in the status bar works only for the left mouse click - made sure that clicking the icons in the status bar works only for the left mouse click

View File

@@ -1260,7 +1260,26 @@ class ToolIsolation(AppTool, Gerber):
combine = self.ui.combine_passes_cb.get_value() combine = self.ui.combine_passes_cb.get_value()
tools_storage = self.iso_tools tools_storage = self.iso_tools
# update the Common Parameters valuse in the self.iso_tools # TODO currently the tool use all the tools in the tool table regardless of selections. Correct this
sorted_tools = []
table_items = self.ui.tools_table.selectedItems()
sel_rows = {t.row() for t in table_items}
for row in sel_rows:
try:
tdia = float(self.ui.tools_table.item(row, 1).text())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
tdia = float(self.ui.tools_table.item(row, 1).text().replace(',', '.'))
except ValueError:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
continue
sorted_tools.append(tdia)
if not sorted_tools:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
return 'fail'
# update the Common Parameters values in the self.iso_tools
for tool_iso in self.iso_tools: for tool_iso in self.iso_tools:
for key in self.iso_tools[tool_iso]: for key in self.iso_tools[tool_iso]:
if key == 'data': if key == 'data':

View File

@@ -1152,14 +1152,15 @@ class NonCopperClear(AppTool, Gerber):
self.ncc_dia_list = [] self.ncc_dia_list = []
table_items = self.ui.tools_table.selectedItems() table_items = self.ui.tools_table.selectedItems()
if table_items: sel_rows = {t.row() for t in table_items}
for x in table_items: if len(sel_rows) > 0:
for row in sel_rows:
try: try:
self.tooldia = float(self.ui.tools_table.item(x.row(), 1).text()) self.tooldia = float(self.ui.tools_table.item(row, 1).text())
except ValueError: except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return # try to convert comma to decimal point. if it's still not working error message and return
try: try:
self.tooldia = float(self.ui.tools_table.item(x.row(), 1).text().replace(',', '.')) self.tooldia = float(self.ui.tools_table.item(row, 1).text().replace(',', '.'))
except ValueError: except ValueError:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number.")) self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
continue continue
@@ -2303,19 +2304,8 @@ class NonCopperClear(AppTool, Gerber):
# check if there is a geometry at all in the cleared geometry # check if there is a geometry at all in the cleared geometry
if cleared_geo: if cleared_geo:
# find the tool uid associated with the current tool_dia so we know tools_storage[tool_uid]["solid_geometry"] = deepcopy(cleared_geo)
# where to add the tool solid_geometry tools_storage[tool_uid]["data"]["name"] = name + '_' + str(tool)
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 + '_' + str(tool)
break
geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) geo_obj.tools[current_uid] = dict(tools_storage[current_uid])
else: else:
log.debug("There are no geometries in the cleared polygon.") log.debug("There are no geometries in the cleared polygon.")

View File

@@ -180,8 +180,6 @@ class ToolPaint(AppTool, Gerber):
self.ui.generate_paint_button.clicked.connect(self.on_paint_button_click) self.ui.generate_paint_button.clicked.connect(self.on_paint_button_click)
self.ui.selectmethod_combo.currentIndexChanged.connect(self.on_selection) self.ui.selectmethod_combo.currentIndexChanged.connect(self.on_selection)
self.ui.order_radio.activated_custom[str].connect(self.on_order_changed)
self.ui.rest_cb.stateChanged.connect(self.on_rest_machining_check)
self.ui.reference_type_combo.currentIndexChanged.connect(self.on_reference_combo_changed) self.ui.reference_type_combo.currentIndexChanged.connect(self.on_reference_combo_changed)
self.ui.type_obj_radio.activated_custom.connect(self.on_type_obj_changed) self.ui.type_obj_radio.activated_custom.connect(self.on_type_obj_changed)
@@ -477,9 +475,6 @@ class ToolPaint(AppTool, Gerber):
self.ui.area_shape_label.show() self.ui.area_shape_label.show()
self.ui.area_shape_radio.show() self.ui.area_shape_radio.show()
else: else:
self.ui.rest_cb.setDisabled(False)
self.ui.rest_cb.set_value(self.app.defaults["tools_paintrest"])
self.ui.addtool_entry.setDisabled(False) self.ui.addtool_entry.setDisabled(False)
self.ui.addtool_btn.setDisabled(False) self.ui.addtool_btn.setDisabled(False)
self.ui.deltool_btn.setDisabled(False) self.ui.deltool_btn.setDisabled(False)
@@ -497,10 +492,20 @@ class ToolPaint(AppTool, Gerber):
self.ui.order_radio.set_value('rev') self.ui.order_radio.set_value('rev')
self.ui.order_label.setDisabled(True) self.ui.order_label.setDisabled(True)
self.ui.order_radio.setDisabled(True) self.ui.order_radio.setDisabled(True)
self.ui.offset_label.hide()
self.ui.offset_entry.hide()
self.ui.rest_offset_label.show()
self.ui.rest_offset_entry.show()
else: else:
self.ui.order_label.setDisabled(False) self.ui.order_label.setDisabled(False)
self.ui.order_radio.setDisabled(False) self.ui.order_radio.setDisabled(False)
self.ui.offset_label.show()
self.ui.offset_entry.show()
self.ui.rest_offset_label.hide()
self.ui.rest_offset_entry.hide()
def set_tool_ui(self): def set_tool_ui(self):
self.ui.tools_frame.show() self.ui.tools_frame.show()
self.reset_fields() self.reset_fields()
@@ -621,6 +626,8 @@ class ToolPaint(AppTool, Gerber):
for dia in diameters: for dia in diameters:
self.on_tool_add(dia, muted=True) self.on_tool_add(dia, muted=True)
self.on_rest_machining_check(state=self.app.defaults["tools_paintrest"])
# if the Paint Method is "Single" disable the tool table context menu # if the Paint Method is "Single" disable the tool table context menu
if self.default_data["tools_selectmethod"] == "single": if self.default_data["tools_selectmethod"] == "single":
self.ui.tools_table.setContextMenuPolicy(Qt.NoContextMenu) self.ui.tools_table.setContextMenuPolicy(Qt.NoContextMenu)
@@ -992,14 +999,15 @@ class ToolPaint(AppTool, Gerber):
# use the selected tools in the tool table; get diameters # use the selected tools in the tool table; get diameters
self.tooldia_list = [] self.tooldia_list = []
table_items = self.ui.tools_table.selectedItems() table_items = self.ui.tools_table.selectedItems()
if table_items: sel_rows = {t.row() for t in table_items}
for x in table_items: if len(sel_rows) > 0:
for row in sel_rows:
try: try:
self.tooldia = float(self.ui.tools_table.item(x.row(), 1).text()) self.tooldia = float(self.ui.tools_table.item(row, 1).text())
except ValueError: except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return # try to convert comma to decimal point. if it's still not working error message and return
try: try:
self.tooldia = float(self.ui.tools_table.item(x.row(), 1).text().replace(',', '.')) self.tooldia = float(self.ui.tools_table.item(row, 1).text().replace(',', '.'))
except ValueError: except ValueError:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number.")) self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
continue continue
@@ -1217,7 +1225,7 @@ class ToolPaint(AppTool, Gerber):
return "" return ""
elif event.button == right_button and self.mouse_is_dragging is False: elif event.button == right_button and self.mouse_is_dragging is False:
shape_type = self.area_shape_radio.get_value() shape_type = self.ui.area_shape_radio.get_value()
if shape_type == "square": if shape_type == "square":
self.first_click = False self.first_click = False
@@ -1674,17 +1682,19 @@ class ToolPaint(AppTool, Gerber):
Note: Note:
* The margin is taken directly from the form. * The margin is taken directly from the form.
:param run_threaded: :param run_threaded:
:param plot: :param plot:
:param poly_list: :param poly_list:
:param obj: painted object :param obj: painted object
:param inside_pt: [x, y] :param inside_pt: [x, y]
:param tooldia: Diameter of the painting tool :param tooldia: Diameter of the painting tool
:param order: if the tools are ordered and how :param order: if the tools are ordered and how
:param outname: Name of the resulting Geometry Object. :param outname: Name of the resulting Geometry Object.
:param method: choice out of _("Seed"), 'normal', 'lines' :param method: choice out of _("Seed"), 'normal', 'lines'
:param tools_storage: whether to use the current tools_storage self.paints_tools or a different one. :param tools_storage: whether to use the current tools_storage self.paints_tools or a different one.
Usage of the different one is related to when this function is called from a TcL command. Usage of the different one is related to when this function is called
from a TcL command.
:return: None :return: None
""" """
@@ -1737,137 +1747,149 @@ class ToolPaint(AppTool, Gerber):
else: else:
sorted_tools = tooldia sorted_tools = tooldia
else: else:
for row in range(self.ui.tools_table.rowCount()): table_items = self.ui.tools_table.selectedItems()
sorted_tools.append(float(self.ui.tools_table.item(row, 1).text())) sel_rows = {t.row() for t in table_items}
for row in sel_rows:
# sort the tools if we have an order selected in the UI
if order == 'fwd':
sorted_tools.sort(reverse=False)
elif order == 'rev':
sorted_tools.sort(reverse=True)
proc = self.app.proc_container.new(_("Painting polygon..."))
tool_dia = None
current_uid = None
final_solid_geometry = []
old_disp_number = 0
for tool_dia in sorted_tools:
log.debug("Starting geometry processing for tool: %s" % str(tool_dia))
msg = '[success] %s %s%s %s' % (_('Painting with tool diameter = '),
str(tool_dia),
self.units.lower(),
_('started'))
self.app.inform.emit(msg)
self.app.proc_container.update_view_text(' %d%%' % 0)
# find the tooluid associated with the current tool_dia so we know what tool to use
for k, v in tools_storage.items():
if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool_dia)):
current_uid = int(k)
if not current_uid:
return "fail"
# determine the tool parameters to use
over = float(tools_storage[current_uid]['data']['tools_paintoverlap']) / 100.0
conn = tools_storage[current_uid]['data']['tools_pathconnect']
cont = tools_storage[current_uid]['data']['tools_paintcontour']
paint_offset = float(tools_storage[current_uid]['data']['tools_paintoffset'])
poly_buf = []
for pol in polygon_list:
buffered_pol = pol.buffer(-paint_offset)
if buffered_pol and not buffered_pol.is_empty:
poly_buf.append(buffered_pol)
if not poly_buf:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Margin parameter too big. Tool is not used"))
continue
# variables to display the percentage of work done
geo_len = len(poly_buf)
log.warning("Total number of polygons to be cleared. %s" % str(geo_len))
pol_nr = 0
# -----------------------------
# effective polygon clearing job
# -----------------------------
try:
cp = []
try: try:
for pp in poly_buf: self.tooldia = float(self.ui.tools_table.item(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.ui.tools_table.item(row, 1).text().replace(',', '.'))
except ValueError:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
continue
sorted_tools.append(self.tooldia)
if not sorted_tools:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
return 'fail'
def job_normal_clear(geo_obj, app_obj):
tool_dia = None
current_uid = None
final_solid_geometry = []
old_disp_number = 0
# sort the tools if we have an order selected in the UI
if order == 'fwd':
sorted_tools.sort(reverse=False)
else:
sorted_tools.sort(reverse=True)
for tool_dia in sorted_tools:
log.debug("Starting geometry processing for tool: %s" % str(tool_dia))
msg = '[success] %s %s%s %s' % (_('Painting with tool diameter = '),
str(tool_dia),
self.units.lower(),
_('started'))
self.app.inform.emit(msg)
self.app.proc_container.update_view_text(' %d%%' % 0)
# find the tooluid associated with the current tool_dia so we know what tool to use
for k, v in tools_storage.items():
if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool_dia)):
current_uid = int(k)
if not current_uid:
return "fail"
# determine the tool parameters to use
over = float(tools_storage[current_uid]['data']['tools_paintoverlap']) / 100.0
conn = tools_storage[current_uid]['data']['tools_pathconnect']
cont = tools_storage[current_uid]['data']['tools_paintcontour']
paint_offset = float(tools_storage[current_uid]['data']['tools_paintoffset'])
poly_buf = []
for pol in polygon_list:
buffered_pol = pol.buffer(-paint_offset)
if buffered_pol and not buffered_pol.is_empty:
poly_buf.append(buffered_pol)
if not poly_buf:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Margin parameter too big. Tool is not used"))
continue
# variables to display the percentage of work done
geo_len = len(poly_buf)
log.warning("Total number of polygons to be cleared. %s" % str(geo_len))
pol_nr = 0
# -----------------------------
# effective polygon clearing job
# -----------------------------
try:
cp = []
try:
for pp in poly_buf:
# 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
geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
cont=cont, paint_method=paint_method, obj=obj,
prog_plot=prog_plot)
if geo_res:
cp.append(geo_res)
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
except TypeError:
# provide the app with a way to process the GUI events when in a blocking loop # provide the app with a way to process the GUI events when in a blocking loop
QtWidgets.QApplication.processEvents() QtWidgets.QApplication.processEvents()
if self.app.abort_flag: if self.app.abort_flag:
# graceful abort requested by the user # graceful abort requested by the user
raise grace raise grace
geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
cont=cont, paint_method=paint_method, obj=obj, cont=cont, paint_method=paint_method, obj=obj,
prog_plot=prog_plot) prog_plot=prog_plot)
if geo_res: if geo_res:
cp.append(geo_res) cp.append(geo_res)
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: total_geometry = []
self.app.proc_container.update_view_text(' %d%%' % disp_number) if cp:
old_disp_number = disp_number for x in cp:
except TypeError: total_geometry += list(x.get_objects())
# provide the app with a way to process the GUI events when in a blocking loop final_solid_geometry += total_geometry
QtWidgets.QApplication.processEvents() except grace:
if self.app.abort_flag: return "fail"
# graceful abort requested by the user except Exception as e:
raise grace log.debug("Could not Paint the polygons. %s" % str(e))
self.app.inform.emit(
'[ERROR] %s\n%s' %
(_("Could not do Paint. Try a different combination of parameters. "
"Or a different strategy of paint"), str(e)
)
)
continue
geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn, # add the solid_geometry to the current too in self.paint_tools (tools_storage)
cont=cont, paint_method=paint_method, obj=obj, # dictionary and then reset the temporary list that stored that solid_geometry
prog_plot=prog_plot) tools_storage[current_uid]['solid_geometry'] = deepcopy(total_geometry)
if geo_res: tools_storage[current_uid]['data']['name'] = name
cp.append(geo_res)
total_geometry = [] # clean the progressive plotted shapes if it was used
if cp: if self.app.defaults["tools_paint_plotting"] == 'progressive':
for x in cp: self.temp_shapes.clear(update=True)
total_geometry += list(x.get_objects())
final_solid_geometry += total_geometry
except grace:
return "fail"
except Exception as e:
log.debug("Could not Paint the polygons. %s" % str(e))
self.app.inform.emit(
'[ERROR] %s\n%s' %
(_("Could not do Paint. Try a different combination of parameters. "
"Or a different strategy of paint"), str(e)
)
)
continue
# add the solid_geometry to the current too in self.paint_tools (tools_storage) # delete tools with empty geometry
# dictionary and then reset the temporary list that stored that solid_geometry # look for keys in the tools_storage dict that have 'solid_geometry' values empty
tools_storage[current_uid]['solid_geometry'] = deepcopy(total_geometry) for uid in list(tools_storage.keys()):
tools_storage[current_uid]['data']['name'] = name # if the solid_geometry (type=list) is empty
if not tools_storage[uid]['solid_geometry']:
tools_storage.pop(uid, None)
# clean the progressive plotted shapes if it was used if not tools_storage:
if self.app.defaults["tools_paint_plotting"] == 'progressive': return 'fail'
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 in list(tools_storage.keys()):
# if the solid_geometry (type=list) is empty
if not tools_storage[uid]['solid_geometry']:
tools_storage.pop(uid, None)
if not tools_storage:
return 'fail'
def job_init(geo_obj, app_obj):
geo_obj.options["cnctooldia"] = str(tool_dia) geo_obj.options["cnctooldia"] = str(tool_dia)
# this will turn on the FlatCAMCNCJob plot for multiple tools # this will turn on the FlatCAMCNCJob plot for multiple tools
geo_obj.multigeo = True geo_obj.multigeo = True
@@ -1908,9 +1930,239 @@ class ToolPaint(AppTool, Gerber):
# print("Indexing...", end=' ') # print("Indexing...", end=' ')
# geo_obj.make_index() # geo_obj.make_index()
def job_rest_clear(geo_obj, app_obj):
current_uid = None
final_solid_geometry = []
old_disp_number = 0
# sort the tools reversed for the rest machining
sorted_tools.sort(reverse=True)
paint_offset = self.ui.rest_offset_entry.get_value()
poly_buf = []
for pol in polygon_list:
buffered_pol = pol.buffer(-paint_offset)
if buffered_pol and not buffered_pol.is_empty:
try:
for x in buffered_pol:
poly_buf.append(x)
except TypeError:
poly_buf.append(buffered_pol)
poly_buf = cascaded_union(poly_buf)
if not poly_buf:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Margin parameter too big. Tool is not used"))
return 'fail'
# variables to display the percentage of work done
geo_len = len(poly_buf)
log.warning("Total number of polygons to be cleared. %s" % str(geo_len))
for tool_dia in sorted_tools:
log.debug("Starting geometry processing for tool: %s" % str(tool_dia))
msg = '[success] %s %s%s %s' % (_('Painting with tool diameter = '),
str(tool_dia),
self.units.lower(),
_('started'))
self.app.inform.emit(msg)
self.app.proc_container.update_view_text(' %d%%' % 0)
# find the tooluid associated with the current tool_dia so we know what tool to use
for k, v in tools_storage.items():
if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool_dia)):
current_uid = int(k)
if not current_uid:
return "fail"
# store here the cleared geometry
cleared_geo = []
# determine the tool parameters to use
over = float(tools_storage[current_uid]['data']['tools_paintoverlap']) / 100.0
conn = tools_storage[current_uid]['data']['tools_pathconnect']
cont = tools_storage[current_uid]['data']['tools_paintcontour']
pol_nr = 0
# store here the parts of polygons that could not be cleared; actually those are parts of polygons
rest_list = []
# -----------------------------
# effective polygon clearing job
# -----------------------------
try:
cleared_geo = []
try:
for pp in poly_buf:
# 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
# speedup the clearing by not trying to clear polygons that is clear they can't be
# cleared with the current tool. this tremendously reduce the clearing time
check_dist = -tool_dia / 2.0
check_buff = pp.buffer(check_dist)
if not check_buff or check_buff.is_empty:
continue
geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
cont=cont, paint_method=paint_method, obj=obj,
prog_plot=prog_plot)
geo_elems = list(geo_res.get_objects())
# See if the polygon was completely cleared
pp_cleared = cascaded_union(geo_elems).buffer(tool_dia / 2.0)
rest = pp.difference(pp_cleared)
if rest and not rest.is_empty:
try:
for r in rest:
rest_list.append(r)
except TypeError:
rest_list.append(rest)
if geo_res:
cleared_geo += geo_elems
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
except TypeError:
# 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
# speedup the clearing by not trying to clear polygons that is clear they can't be
# cleared with the current tool. this tremendously reduce the clearing time
check_dist = -tool_dia / 2.0
check_buff = poly_buf.buffer(check_dist)
if not check_buff or check_buff.is_empty:
continue
geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
cont=cont, paint_method=paint_method, obj=obj,
prog_plot=prog_plot)
geo_elems = list(geo_res.get_objects())
# See if the polygon was completely cleared
pp_cleared = cascaded_union(geo_elems).buffer(tool_dia / 2.0)
rest = poly_buf.difference(pp_cleared)
if rest and not rest.is_empty:
try:
for r in rest:
rest_list.append(r)
except TypeError:
rest_list.append(rest)
if geo_res:
cleared_geo += geo_elems
except grace:
return "fail"
except Exception as e:
log.debug("Could not Paint the polygons. %s" % str(e))
msg = '[ERROR] %s\n%s' % (_("Could not do Paint. Try a different combination of parameters. "
"Or a different strategy of paint"), str(e))
self.app.inform.emit(msg)
continue
if cleared_geo:
final_solid_geometry += cleared_geo
# add the solid_geometry to the current too in self.paint_tools (tools_storage)
# dictionary and then reset the temporary list that stored that solid_geometry
tools_storage[current_uid]['solid_geometry'] = deepcopy(cleared_geo)
tools_storage[current_uid]['data']['name'] = name
geo_obj.tools[current_uid] = dict(tools_storage[current_uid])
else:
log.debug("There are no geometries in the cleared polygon.")
# Area to clear next
log.debug("Generating rest geometry for the next tool.")
buffered_cleared = cascaded_union(cleared_geo).buffer(tool_dia / 2.0)
poly_buf = poly_buf.difference(buffered_cleared)
tmp = []
try:
for p in poly_buf:
tmp.append(p)
except TypeError:
tmp.append(poly_buf)
tmp += rest_list
poly_buf = MultiPolygon(tmp)
if not poly_buf or poly_buf.is_empty:
log.debug("Rest geometry empty. Breaking.")
break
geo_obj.multigeo = True
geo_obj.options["cnctooldia"] = '0.0'
# clean the progressive plotted shapes if it was used
if self.app.defaults["tools_paint_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 in list(tools_storage.keys()):
# if the solid_geometry (type=list) is empty
if not tools_storage[uid]['solid_geometry']:
tools_storage.pop(uid, None)
if not tools_storage:
return 'fail'
geo_obj.multitool = True
if geo_obj.tools:
# test if at least one tool has solid_geometry. If no tool has solid_geometry we raise an Exception
has_solid_geo = 0
for tooluid in geo_obj.tools:
if geo_obj.tools[tooluid]['solid_geometry']:
has_solid_geo += 1
if has_solid_geo == 0:
app_obj.inform.emit('[ERROR] %s' %
_("There is no Painting 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"
geo_obj.solid_geometry = cascaded_union(final_solid_geometry)
else:
return 'fail'
try:
if isinstance(geo_obj.solid_geometry, list):
a, b, c, d = MultiPolygon(geo_obj.solid_geometry).bounds
else:
a, b, c, d = geo_obj.solid_geometry.bounds
geo_obj.options['xmin'] = a
geo_obj.options['ymin'] = b
geo_obj.options['xmax'] = c
geo_obj.options['ymax'] = d
except Exception as ee:
log.debug("ToolPaint.paint_poly.job_init() bounds error --> %s" % str(ee))
return
# Experimental...
# print("Indexing...", end=' ')
# geo_obj.make_index()
def job_thread(app_obj): def job_thread(app_obj):
try: try:
ret = app_obj.app_obj.new_object("geometry", name, job_init, plot=plot) if self.ui.rest_cb.get_value():
ret = app_obj.app_obj.new_object("geometry", name, job_rest_clear, plot=plot)
else:
ret = app_obj.app_obj.new_object("geometry", name, job_normal_clear, plot=plot)
except grace: except grace:
proc.done() proc.done()
return return
@@ -1935,6 +2187,8 @@ class ToolPaint(AppTool, Gerber):
# Promise object with the new name # Promise object with the new name
self.app.collection.promise(name) self.app.collection.promise(name)
proc = self.app.proc_container.new(_("Painting polygon..."))
if run_threaded: if run_threaded:
# Background # Background
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
@@ -2534,7 +2788,7 @@ class ToolPaint(AppTool, Gerber):
self.app.inform.emit('[WARNING] %s' % _('No polygon found.')) self.app.inform.emit('[WARNING] %s' % _('No polygon found.'))
return return
paint_method = method if method is not None else self.paintmethod_combo.get_value() paint_method = method if method is not None else self.ui.paintmethod_combo.get_value()
# determine if to use the progressive plotting # determine if to use the progressive plotting
prog_plot = True if self.app.defaults["tools_paint_plotting"] == 'progressive' else False prog_plot = True if self.app.defaults["tools_paint_plotting"] == 'progressive' else False
@@ -3006,6 +3260,7 @@ class ToolPaint(AppTool, Gerber):
self.ui.tools_table.clicked.connect(self.on_row_selection_change) self.ui.tools_table.clicked.connect(self.on_row_selection_change)
self.ui.tools_table.horizontalHeader().sectionClicked.connect(self.on_toggle_all_rows) self.ui.tools_table.horizontalHeader().sectionClicked.connect(self.on_toggle_all_rows)
# Table widgets
for row in range(self.ui.tools_table.rowCount()): for row in range(self.ui.tools_table.rowCount()):
try: try:
self.ui.tools_table.cellWidget(row, 2).currentIndexChanged.connect(self.on_tooltable_cellwidget_change) self.ui.tools_table.cellWidget(row, 2).currentIndexChanged.connect(self.on_tooltable_cellwidget_change)
@@ -3017,8 +3272,7 @@ class ToolPaint(AppTool, Gerber):
except AttributeError: except AttributeError:
pass pass
self.ui.tool_type_radio.activated_custom.connect(self.on_tool_type) # Parameters FORM UI
# first disconnect # first disconnect
for opt in self.form_fields: for opt in self.form_fields:
current_widget = self.form_fields[opt] current_widget = self.form_fields[opt]
@@ -3050,6 +3304,7 @@ class ToolPaint(AppTool, Gerber):
elif isinstance(current_widget, FCComboBox): elif isinstance(current_widget, FCComboBox):
current_widget.currentIndexChanged.connect(self.form_to_storage) current_widget.currentIndexChanged.connect(self.form_to_storage)
self.ui.tool_type_radio.activated_custom.connect(self.on_tool_type)
self.ui.rest_cb.stateChanged.connect(self.on_rest_machining_check) self.ui.rest_cb.stateChanged.connect(self.on_rest_machining_check)
self.ui.order_radio.activated_custom[str].connect(self.on_order_changed) self.ui.order_radio.activated_custom[str].connect(self.on_order_changed)
@@ -3060,17 +3315,17 @@ class ToolPaint(AppTool, Gerber):
except (TypeError, AttributeError): except (TypeError, AttributeError):
pass pass
# rows selected
try: try:
self.ui.tools_table.horizontalHeader().sectionClicked.disconnect(self.on_row_selection_change) self.ui.tools_table.clicked.disconnect()
except (TypeError, AttributeError): except (TypeError, AttributeError):
pass pass
try:
try: self.ui.tools_table.horizontalHeader().sectionClicked.disconnect()
# if connected, disconnect the signal from the slot on item_changed as it creates issues
self.ui.tool_type_radio.activated_custom.disconnect()
except (TypeError, AttributeError): except (TypeError, AttributeError):
pass pass
# Table widgets
for row in range(self.ui.tools_table.rowCount()): for row in range(self.ui.tools_table.rowCount()):
for col in [2, 4]: for col in [2, 4]:
try: try:
@@ -3078,6 +3333,7 @@ class ToolPaint(AppTool, Gerber):
except (TypeError, AttributeError): except (TypeError, AttributeError):
pass pass
# Parameters FORM UI
for opt in self.form_fields: for opt in self.form_fields:
current_widget = self.form_fields[opt] current_widget = self.form_fields[opt]
if isinstance(current_widget, FCCheckBox): if isinstance(current_widget, FCCheckBox):
@@ -3101,13 +3357,22 @@ class ToolPaint(AppTool, Gerber):
except (TypeError, ValueError): except (TypeError, ValueError):
pass pass
# rows selected
try: try:
self.ui.tools_table.clicked.disconnect() # if connected, disconnect the signal from the slot on item_changed as it creates issues
self.ui.tool_type_radio.activated_custom.disconnect()
except (TypeError, AttributeError): except (TypeError, AttributeError):
pass pass
try: try:
self.ui.tools_table.horizontalHeader().sectionClicked.disconnect() # if connected, disconnect the signal from the slot on item_changed as it creates issues
self.ui.rest_cb.stateChanged.disconnect()
except (TypeError, AttributeError):
pass
try:
# if connected, disconnect the signal from the slot on item_changed as it creates issues
self.ui.order_radio.activated_custom[str].disconnect()
except (TypeError, AttributeError): except (TypeError, AttributeError):
pass pass
@@ -3557,9 +3822,9 @@ class PaintUI:
grid4.addWidget(ovlabel, 1, 0) grid4.addWidget(ovlabel, 1, 0)
grid4.addWidget(self.paintoverlap_entry, 1, 1) grid4.addWidget(self.paintoverlap_entry, 1, 1)
# Margin # Offset
marginlabel = QtWidgets.QLabel('%s:' % _('Offset')) self.offset_label = QtWidgets.QLabel('%s:' % _('Offset'))
marginlabel.setToolTip( self.offset_label.setToolTip(
_("Distance by which to avoid\n" _("Distance by which to avoid\n"
"the edges of the polygon to\n" "the edges of the polygon to\n"
"be painted.") "be painted.")
@@ -3569,7 +3834,7 @@ class PaintUI:
self.offset_entry.set_range(-9999.9999, 9999.9999) self.offset_entry.set_range(-9999.9999, 9999.9999)
self.offset_entry.setObjectName('p_offset') self.offset_entry.setObjectName('p_offset')
grid4.addWidget(marginlabel, 2, 0) grid4.addWidget(self.offset_label, 2, 0)
grid4.addWidget(self.offset_entry, 2, 1) grid4.addWidget(self.offset_entry, 2, 1)
# Method # Method
@@ -3663,6 +3928,20 @@ class PaintUI:
) )
grid4.addWidget(self.rest_cb, 16, 0, 1, 2) grid4.addWidget(self.rest_cb, 16, 0, 1, 2)
# Rest Offset
self.rest_offset_label = QtWidgets.QLabel('%s:' % _('Offset'))
self.rest_offset_label.setToolTip(
_("Distance by which to avoid\n"
"the edges of the polygon to\n"
"be painted.")
)
self.rest_offset_entry = FCDoubleSpinner(callback=self.confirmation_message)
self.rest_offset_entry.set_precision(self.decimals)
self.rest_offset_entry.set_range(-9999.9999, 9999.9999)
grid4.addWidget(self.rest_offset_label, 17, 0)
grid4.addWidget(self.rest_offset_entry, 17, 1)
# Polygon selection # Polygon selection
selectlabel = QtWidgets.QLabel('%s:' % _('Selection')) selectlabel = QtWidgets.QLabel('%s:' % _('Selection'))
selectlabel.setToolTip( selectlabel.setToolTip(
@@ -3765,11 +4044,11 @@ class PaintUI:
_("Will reset the tool parameters.") _("Will reset the tool parameters.")
) )
self.reset_button.setStyleSheet(""" self.reset_button.setStyleSheet("""
QPushButton QPushButton
{ {
font-weight: bold; font-weight: bold;
} }
""") """)
self.tools_box.addWidget(self.reset_button) self.tools_box.addWidget(self.reset_button)
# #################################### FINSIHED GUI ########################### # #################################### FINSIHED GUI ###########################

View File

@@ -415,7 +415,7 @@ class FlatCAMDefaults:
"tools_iso_isotype": "full", "tools_iso_isotype": "full",
"tools_iso_rest": False, "tools_iso_rest": False,
"tools_iso_combine_passes": False, "tools_iso_combine_passes": True,
"tools_iso_isoexcept": False, "tools_iso_isoexcept": False,
"tools_iso_selection": _("All"), "tools_iso_selection": _("All"),
"tools_iso_poly_ints": False, "tools_iso_poly_ints": False,