- modified the Paint Tool. Now the Single Polygon and Area/Reference Object painting works with multiple tools too. The tools have to be selected in the Tool Table.

- remade the TclCommand Paint to work in the new configuration of the the app (the painting functions are now in their own tool, Paint Tool)
- fixed a bug in the Properties Tool
This commit is contained in:
Marius Stanciu 2019-08-24 19:55:03 +03:00 committed by Marius
parent 82b39d64d2
commit 87d1558977
5 changed files with 621 additions and 207 deletions

View File

@ -15,6 +15,9 @@ CAD program, and create G-Code for Isolation routing.
- added ability to turn on/of the grid snapping and to jump to a location while in CutOut Tool manual gap adding action
- made PlotCanvas class inherit from VisPy Canvas instead of creating an instance of it (work of JP)
- fixed selection by dragging a selection shape in Geometry Editor
- modified the Paint Tool. Now the Single Polygon and Area/Reference Object painting works with multiple tools too. The tools have to be selected in the Tool Table.
- remade the TclCommand Paint to work in the new configuration of the the app (the painting functions are now in their own tool, Paint Tool)
- fixed a bug in the Properties Tool
23.08.2019

View File

@ -232,10 +232,10 @@ class ToolPaint(FlatCAMTool, Gerber):
# Method
methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
methodlabel.setToolTip(
_("Algorithm for non-copper clearing:<BR>"
"<B>Standard</B>: Fixed step inwards.<BR>"
"<B>Seed-based</B>: Outwards from seed.<BR>"
"<B>Line-based</B>: Parallel lines.")
_("Algorithm for painting:\n"
"- Standard: Fixed step inwards.\n"
"- Seed-based: Outwards from seed.\n"
"- Line-based: Parallel lines.")
)
grid3.addWidget(methodlabel, 3, 0)
self.paintmethod_combo = RadioSet([
@ -473,14 +473,14 @@ class ToolPaint(FlatCAMTool, Gerber):
self.rest_cb.set_value(False)
self.rest_cb.setDisabled(True)
# delete all tools except first row / tool for single polygon painting
list_to_del = list(range(1, self.tools_table.rowCount()))
if list_to_del:
self.on_tool_delete(rows_to_delete=list_to_del)
# disable addTool and delTool
self.addtool_entry.setDisabled(True)
self.addtool_btn.setDisabled(True)
self.deltool_btn.setDisabled(True)
self.tools_table.setContextMenuPolicy(Qt.NoContextMenu)
# list_to_del = list(range(1, self.tools_table.rowCount()))
# if list_to_del:
# self.on_tool_delete(rows_to_delete=list_to_del)
# # disable addTool and delTool
# self.addtool_entry.setDisabled(True)
# self.addtool_btn.setDisabled(True)
# self.deltool_btn.setDisabled(True)
# self.tools_table.setContextMenuPolicy(Qt.NoContextMenu)
if self.selectmethod_combo.get_value() == 'area':
# disable rest-machining for single polygon painting
self.rest_cb.set_value(False)
@ -941,8 +941,28 @@ class ToolPaint(FlatCAMTool, Gerber):
o_name = '%s_multitool_paint' % self.obj_name
# use the selected tools in the tool table; get diameters
tooldia_list = list()
if self.tools_table.selectedItems():
for x in self.tools_table.selectedItems():
try:
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:
tooldia = float(self.tools_table.item(x.row(), 1).text().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL] Wrong Tool Dia value format entered, "
"use a number."))
continue
tooldia_list.append(tooldia)
else:
self.app.inform.emit(_("[ERROR_NOTCL] No selected tools in Tool Table."))
return
if select_method == "all":
self.paint_poly_all(self.paint_obj,
tooldia=tooldia_list,
outname=o_name,
overlap=overlap,
connect=connect,
@ -952,7 +972,7 @@ class ToolPaint(FlatCAMTool, Gerber):
self.app.inform.emit(_("[WARNING_NOTCL] Click inside the desired polygon."))
# use the first tool in the tool table; get the diameter
tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
# tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
# To be called after clicking on the plot.
def doit(event):
@ -967,12 +987,14 @@ class ToolPaint(FlatCAMTool, Gerber):
self.paint_poly(self.paint_obj,
inside_pt=[pos[0], pos[1]],
tooldia=tooldia,
tooldia=tooldia_list,
overlap=overlap,
connect=connect,
contour=contour)
self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_connect('mouse_press', doit)
@ -980,7 +1002,7 @@ class ToolPaint(FlatCAMTool, Gerber):
self.app.inform.emit(_("[WARNING_NOTCL] Click the start point of the paint area."))
# use the first tool in the tool table; get the diameter
tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
# tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
# To be called after clicking on the plot.
def on_mouse_release(event):
@ -1024,6 +1046,7 @@ class ToolPaint(FlatCAMTool, Gerber):
self.sel_rect = cascaded_union(self.sel_rect)
self.paint_poly_area(obj=self.paint_obj,
tooldia=tooldia_list,
sel_obj= self.sel_rect,
outname=o_name,
overlap=overlap,
@ -1047,6 +1070,7 @@ class ToolPaint(FlatCAMTool, Gerber):
self.sel_rect = cascaded_union(self.sel_rect)
self.paint_poly_area(obj=self.paint_obj,
tooldia=tooldia_list,
sel_obj=self.sel_rect,
outname=o_name,
overlap=overlap,
@ -1093,32 +1117,27 @@ class ToolPaint(FlatCAMTool, Gerber):
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
return "Could not retrieve object: %s" % self.obj_name
geo = self.bound_obj.solid_geometry
try:
if isinstance(geo, MultiPolygon):
env_obj = geo.convex_hull
elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \
(isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon):
env_obj = cascaded_union(self.bound_obj.solid_geometry)
else:
env_obj = cascaded_union(self.bound_obj.solid_geometry)
env_obj = env_obj.convex_hull
sel_rect = env_obj.buffer(distance=0.0000001, join_style=base.JOIN_STYLE.mitre)
except Exception as e:
log.debug("ToolPaint.on_paint_button_click() --> %s" % str(e))
self.app.inform.emit(_("[ERROR_NOTCL] No object available."))
return
self.paint_poly_ref(obj=self.paint_obj,
sel_obj=self.bound_obj,
tooldia=tooldia_list,
overlap=overlap,
outname=o_name,
connect=connect,
contour=contour)
self.paint_poly_area(obj=self.paint_obj,
sel_obj=sel_rect,
outname=o_name,
overlap=overlap,
connect=connect,
contour=contour)
def paint_poly(self, obj, inside_pt, tooldia, overlap, outname=None, connect=True, contour=True):
def paint_poly(self, obj,
inside_pt=None,
tooldia=None,
overlap=None,
order=None,
margin=None,
method=None,
outname=None,
connect=None,
contour=None,
tools_storage=None):
"""
Paints a polygon selected by clicking on its interior.
Paints a polygon selected by clicking on its interior or by having a point coordinates given
Note:
* The margin is taken directly from the form.
@ -1126,27 +1145,35 @@ class ToolPaint(FlatCAMTool, Gerber):
:param inside_pt: [x, y]
:param tooldia: Diameter of the painting tool
:param overlap: Overlap of the tool between passes.
:param order: if the tools are ordered and how
:param margin: a border around painting area
:param outname: Name of the resulting Geometry Object.
:param connect: Connect lines to avoid tool lifts.
:param contour: Paint around the edges.
: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.
Usage of the different one is related to when this function is called from a TcL command.
:return: None
"""
# Which polygon.
# poly = find_polygon(self.solid_geometry, inside_pt)
poly = self.find_polygon(point=inside_pt, geoset=obj.solid_geometry)
paint_method = self.paintmethod_combo.get_value()
paint_method = method if method is None else self.paintmethod_combo.get_value()
try:
paint_margin = float(self.paintmargin_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
if margin is not None:
paint_margin = margin
else:
try:
paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
paint_margin = float(self.paintmargin_entry.get_value())
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
# try to convert comma to decimal point. if it's still not working error message and return
try:
paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
# No polygon?
if poly is None:
@ -1156,41 +1183,72 @@ class ToolPaint(FlatCAMTool, Gerber):
proc = self.app.proc_container.new(_("Painting polygon."))
name = outname if outname else self.obj_name + "_paint"
name = outname if outname is not None else self.obj_name + "_paint"
over = overlap if overlap is not None else float(self.app.defaults["tools_paintoverlap"])
conn = connect if connect is not None else self.app.defaults["tools_pathconnect"]
cont = contour if contour is not None else self.app.defaults["tools_paintcontour"]
order = order if order is not None else self.order_radio.get_value()
sorted_tools = []
if tooldia is not None:
try:
sorted_tools = [float(eval(dia)) for dia in tooldia.split(",") if dia != '']
except AttributeError:
if not isinstance(tooldia, list):
sorted_tools = [float(tooldia)]
else:
sorted_tools = tooldia
else:
for row in range(self.tools_table.rowCount()):
sorted_tools.append(float(self.tools_table.item(row, 1).text()))
if tools_storage is not None:
tools_storage = tools_storage
else:
tools_storage = self.paint_tools
# Initializes the new geometry object
def gen_paintarea(geo_obj, app_obj):
assert isinstance(geo_obj, FlatCAMGeometry), \
"Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
# assert isinstance(geo_obj, FlatCAMGeometry), \
# "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
# assert isinstance(app_obj, App)
def paint_p(polyg):
tool_dia = None
if order == 'fwd':
sorted_tools.sort(reverse=False)
elif order == 'rev':
sorted_tools.sort(reverse=True)
else:
pass
def paint_p(polyg, tooldia):
if paint_method == "seed":
# Type(cp) == FlatCAMRTreeStorage | None
cpoly = self.clear_polygon2(polyg,
tooldia=tooldia,
steps_per_circle=self.app.defaults["geometry_circle_steps"],
overlap=overlap,
contour=contour,
connect=connect)
overlap=over,
contour=cont,
connect=conn)
elif paint_method == "lines":
# Type(cp) == FlatCAMRTreeStorage | None
cpoly = self.clear_polygon3(polyg,
tooldia=tooldia,
steps_per_circle=self.app.defaults["geometry_circle_steps"],
overlap=overlap,
contour=contour,
connect=connect)
overlap=over,
contour=cont,
connect=conn)
else:
# Type(cp) == FlatCAMRTreeStorage | None
cpoly = self.clear_polygon(polyg,
tooldia=tooldia,
steps_per_circle=self.app.defaults["geometry_circle_steps"],
overlap=overlap,
contour=contour,
connect=connect)
overlap=over,
contour=cont,
connect=conn)
if cpoly is not None:
geo_obj.solid_geometry += list(cpoly.get_objects())
@ -1199,8 +1257,6 @@ class ToolPaint(FlatCAMTool, Gerber):
self.app.inform.emit(_('[ERROR_NOTCL] Geometry could not be painted completely'))
return None
geo_obj.solid_geometry = []
try:
a, b, c, d = poly.bounds
geo_obj.options['xmin'] = a
@ -1211,39 +1267,78 @@ class ToolPaint(FlatCAMTool, Gerber):
log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(e))
return
try:
poly_buf = poly.buffer(-paint_margin)
if isinstance(poly_buf, MultiPolygon):
cp = []
for pp in poly_buf:
cp.append(paint_p(pp))
else:
cp = paint_p(poly_buf)
except Exception as e:
log.debug("Could not Paint the polygons. %s" % str(e))
self.app.inform.emit(
_("[ERROR] Could not do Paint. Try a different combination of parameters. "
"Or a different strategy of paint\n%s") % str(e))
total_geometry = []
current_uid = int(1)
geo_obj.solid_geometry = []
for tool_dia in sorted_tools:
# 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('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
current_uid = int(k)
break
try:
poly_buf = poly.buffer(-paint_margin)
if isinstance(poly_buf, MultiPolygon):
cp = []
for pp in poly_buf:
cp.append(paint_p(pp, tooldia=tool_dia))
else:
cp = paint_p(poly_buf, tooldia=tool_dia)
if cp is not None:
if isinstance(cp, list):
for x in cp:
total_geometry += list(x.get_objects())
else:
total_geometry = list(cp.get_objects())
except Exception as e:
log.debug("Could not Paint the polygons. %s" % str(e))
self.app.inform.emit(
_("[ERROR] Could not do Paint. Try a different combination of parameters. "
"Or a different strategy of paint\n%s") % str(e))
return
# 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(total_geometry)
tools_storage[current_uid]['data']['name'] = name
total_geometry[:] = []
# delete tools with empty geometry
keys_to_delete = []
# look for keys in the tools_storage dict that have 'solid_geometry' values empty
for uid in tools_storage:
# if the solid_geometry (type=list) is empty
if not tools_storage[uid]['solid_geometry']:
keys_to_delete.append(uid)
# actual delete of keys from the tools_storage dict
for k in keys_to_delete:
tools_storage.pop(k, None)
geo_obj.options["cnctooldia"] = str(tool_dia)
# this turn on the FlatCAMCNCJob plot for multiple tools
geo_obj.multigeo = True
geo_obj.multitool = True
geo_obj.tools.clear()
geo_obj.tools = dict(tools_storage)
# test if at least one tool has solid_geometry. If no tool has solid_geometry we raise an Exception
has_solid_geo = 0
for tooluid in geo_obj.tools:
if geo_obj.tools[tooluid]['solid_geometry']:
has_solid_geo += 1
if has_solid_geo == 0:
self.app.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"
"Change the painting parameters and try again."))
return
if cp is not None:
if isinstance(cp, list):
for x in cp:
geo_obj.solid_geometry += list(x.get_objects())
else:
geo_obj.solid_geometry = list(cp.get_objects())
geo_obj.options["cnctooldia"] = str(tooldia)
# this turn on the FlatCAMCNCJob plot for multiple tools
geo_obj.multigeo = False
geo_obj.multitool = True
current_uid = int(self.tools_table.item(0, 3).text())
for k, v in self.paint_tools.items():
if k == current_uid:
v['data']['name'] = name
geo_obj.tools = dict(self.paint_tools)
self.app.inform.emit(_("[success] Paint Single Done."))
# Experimental...
# print("Indexing...", end=' ')
@ -1278,36 +1373,73 @@ class ToolPaint(FlatCAMTool, Gerber):
# Background
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
def paint_poly_all(self, obj, overlap, outname=None, connect=True, contour=True):
def paint_poly_all(self, obj,
tooldia=None,
overlap=None,
order=None,
margin=None,
method=None,
outname=None,
connect=None,
contour=None,
tools_storage=None):
"""
Paints all polygons in this object.
:param obj: painted object
:param overlap:
:param outname:
:param tooldia: a tuple or single element made out of diameters of the tools to be used
:param overlap: value by which the paths will overlap
:param order: if the tools are ordered and how
:param margin: a border around painting area
:param outname: name of the resulting object
:param connect: Connect lines to avoid tool lifts.
:param contour: Paint around the edges.
:param method: choice out of 'seed', 'normal', 'lines'
:param 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.
:return:
"""
paint_method = self.paintmethod_combo.get_value()
paint_method = method if method is None else self.paintmethod_combo.get_value()
try:
paint_margin = float(self.paintmargin_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
if margin is not None:
paint_margin = margin
else:
try:
paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
paint_margin = float(self.paintmargin_entry.get_value())
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
# try to convert comma to decimal point. if it's still not working error message and return
try:
paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
proc = self.app.proc_container.new(_("Painting polygon..."))
name = outname if outname else self.obj_name + "_paint"
over = overlap
conn = connect
cont = contour
name = outname if outname is not None else self.obj_name + "_paint"
over = overlap if overlap is not None else float(self.app.defaults["tools_paintoverlap"])
conn = connect if connect is not None else self.app.defaults["tools_pathconnect"]
cont = contour if contour is not None else self.app.defaults["tools_paintcontour"]
order = order if order is not None else self.order_radio.get_value()
sorted_tools = []
if tooldia is not None:
try:
sorted_tools = [float(eval(dia)) for dia in tooldia.split(",") if dia != '']
except AttributeError:
if not isinstance(tooldia, list):
sorted_tools = [float(tooldia)]
else:
sorted_tools = tooldia
else:
for row in range(self.tools_table.rowCount()):
sorted_tools.append(float(self.tools_table.item(row, 1).text()))
if tools_storage is not None:
tools_storage = tools_storage
else:
tools_storage = self.paint_tools
# This is a recursive generator of individual Polygons.
# Note: Double check correct implementation. Might exit
# early if it finds something that is not a Polygon?
@ -1355,15 +1487,10 @@ class ToolPaint(FlatCAMTool, Gerber):
# Initializes the new geometry object
def gen_paintarea(geo_obj, app_obj):
assert isinstance(geo_obj, FlatCAMGeometry), \
"Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
# assert isinstance(geo_obj, FlatCAMGeometry), \
# "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
tool_dia = None
sorted_tools = []
for row in range(self.tools_table.rowCount()):
sorted_tools.append(float(self.tools_table.item(row, 1).text()))
order = self.order_radio.get_value()
if order == 'fwd':
sorted_tools.sort(reverse=False)
elif order == 'rev':
@ -1383,10 +1510,12 @@ class ToolPaint(FlatCAMTool, Gerber):
total_geometry = []
current_uid = int(1)
geo_obj.solid_geometry = []
for tool_dia in sorted_tools:
# find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
for k, v in self.paint_tools.items():
for k, v in tools_storage.items():
if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
current_uid = int(k)
break
@ -1434,19 +1563,31 @@ class ToolPaint(FlatCAMTool, Gerber):
"Or a different Method of paint\n%s") % str(e))
return
# add the solid_geometry to the current too in self.paint_tools dictionary and then reset the
# temporary list that stored that solid_geometry
self.paint_tools[current_uid]['solid_geometry'] = deepcopy(total_geometry)
# 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(total_geometry)
self.paint_tools[current_uid]['data']['name'] = name
tools_storage[current_uid]['data']['name'] = name
total_geometry[:] = []
# delete tools with empty geometry
keys_to_delete = []
# look for keys in the tools_storage dict that have 'solid_geometry' values empty
for uid in tools_storage:
# if the solid_geometry (type=list) is empty
if not tools_storage[uid]['solid_geometry']:
keys_to_delete.append(uid)
# actual delete of keys from the tools_storage dict
for k in keys_to_delete:
tools_storage.pop(k, None)
geo_obj.options["cnctooldia"] = str(tool_dia)
# this turn on the FlatCAMCNCJob plot for multiple tools
geo_obj.multigeo = True
geo_obj.multitool = True
geo_obj.tools.clear()
geo_obj.tools = dict(self.paint_tools)
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
@ -1467,13 +1608,10 @@ class ToolPaint(FlatCAMTool, Gerber):
# Initializes the new geometry object
def gen_paintarea_rest_machining(geo_obj, app_obj):
assert isinstance(geo_obj, FlatCAMGeometry), \
"Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
# assert isinstance(geo_obj, FlatCAMGeometry), \
# "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
tool_dia = None
sorted_tools = []
for row in range(self.tools_table.rowCount()):
sorted_tools.append(float(self.tools_table.item(row, 1).text()))
sorted_tools.sort(reverse=True)
cleared_geo = []
@ -1526,16 +1664,16 @@ class ToolPaint(FlatCAMTool, Gerber):
return
# find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
for k, v in self.paint_tools.items():
for k, v in tools_storage.items():
if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
current_uid = int(k)
break
# add the solid_geometry to the current too in self.paint_tools dictionary and then reset the
# temporary list that stored that solid_geometry
self.paint_tools[current_uid]['solid_geometry'] = deepcopy(cleared_geo)
# add the solid_geometry to the current too in self.paint_tools (or tools_storage) dictionary and
# then reset the temporary list that stored that solid_geometry
tools_storage[current_uid]['solid_geometry'] = deepcopy(cleared_geo)
self.paint_tools[current_uid]['data']['name'] = name
tools_storage[current_uid]['data']['name'] = name
cleared_geo[:] = []
geo_obj.options["cnctooldia"] = str(tool_dia)
@ -1543,7 +1681,7 @@ class ToolPaint(FlatCAMTool, Gerber):
geo_obj.multigeo = True
geo_obj.multitool = True
geo_obj.tools.clear()
geo_obj.tools = dict(self.paint_tools)
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
@ -1584,36 +1722,74 @@ class ToolPaint(FlatCAMTool, Gerber):
# Background
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
def paint_poly_area(self, obj, sel_obj, overlap, outname=None, connect=True, contour=True):
def paint_poly_area(self, obj, sel_obj,
tooldia=None,
overlap=None,
order=None,
margin=None,
method=None,
outname=None,
connect=None,
contour=None,
tools_storage=None):
"""
Paints all polygons in this object that are within the sel_obj object
:param obj: painted object
:param sel_obj: paint only what is inside this object bounds
:param overlap:
:param outname:
:param tooldia: a tuple or single element made out of diameters of the tools to be used
:param overlap: value by which the paths will overlap
:param order: if the tools are ordered and how
:param margin: a border around painting area
:param outname: name of the resulting object
:param connect: Connect lines to avoid tool lifts.
:param contour: Paint around the edges.
:param method: choice out of 'seed', 'normal', 'lines'
:param 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.
:return:
"""
paint_method = self.paintmethod_combo.get_value()
paint_method = method if method is None else self.paintmethod_combo.get_value()
try:
paint_margin = float(self.paintmargin_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
if margin is not None:
paint_margin = margin
else:
try:
paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
paint_margin = float(self.paintmargin_entry.get_value())
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
# try to convert comma to decimal point. if it's still not working error message and return
try:
paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
proc = self.app.proc_container.new(_("Painting polygon..."))
name = outname if outname else self.obj_name + "_paint"
over = overlap
conn = connect
cont = contour
name = outname if outname is not None else self.obj_name + "_paint"
over = overlap if overlap is not None else float(self.app.defaults["tools_paintoverlap"])
conn = connect if connect is not None else self.app.defaults["tools_pathconnect"]
cont = contour if contour is not None else self.app.defaults["tools_paintcontour"]
order = order if order is not None else self.order_radio.get_value()
sorted_tools = []
if tooldia is not None:
try:
sorted_tools = [float(eval(dia)) for dia in tooldia.split(",") if dia != '']
except AttributeError:
if not isinstance(tooldia, list):
sorted_tools = [float(tooldia)]
else:
sorted_tools = tooldia
else:
for row in range(self.tools_table.rowCount()):
sorted_tools.append(float(self.tools_table.item(row, 1).text()))
if tools_storage is not None:
tools_storage = tools_storage
else:
tools_storage = self.paint_tools
def recurse(geometry, reset=True):
"""
@ -1648,15 +1824,9 @@ class ToolPaint(FlatCAMTool, Gerber):
# Initializes the new geometry object
def gen_paintarea(geo_obj, app_obj):
assert isinstance(geo_obj, FlatCAMGeometry), \
"Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
# assert isinstance(geo_obj, FlatCAMGeometry), \
# "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
tool_dia = None
sorted_tools = []
for row in range(self.tools_table.rowCount()):
sorted_tools.append(float(self.tools_table.item(row, 1).text()))
order = self.order_radio.get_value()
if order == 'fwd':
sorted_tools.sort(reverse=False)
elif order == 'rev':
@ -1664,6 +1834,7 @@ class ToolPaint(FlatCAMTool, Gerber):
else:
pass
# this is were heavy lifting is done and creating the geometry to be painted
geo_to_paint = []
if not isinstance(obj.solid_geometry, list):
target_geo = [obj.solid_geometry]
@ -1686,10 +1857,12 @@ class ToolPaint(FlatCAMTool, Gerber):
total_geometry = []
current_uid = int(1)
geo_obj.solid_geometry = []
for tool_dia in sorted_tools:
# find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
for k, v in self.paint_tools.items():
for k, v in tools_storage.items():
if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
current_uid = int(k)
break
@ -1737,19 +1910,31 @@ class ToolPaint(FlatCAMTool, Gerber):
"Or a different Method of paint\n%s") % str(e))
return
# add the solid_geometry to the current too in self.paint_tools dictionary and then reset the
# temporary list that stored that solid_geometry
self.paint_tools[current_uid]['solid_geometry'] = deepcopy(total_geometry)
# 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(total_geometry)
self.paint_tools[current_uid]['data']['name'] = name
tools_storage[current_uid]['data']['name'] = name
total_geometry[:] = []
# delete tools with empty geometry
keys_to_delete = []
# look for keys in the tools_storage dict that have 'solid_geometry' values empty
for uid in tools_storage:
# if the solid_geometry (type=list) is empty
if not tools_storage[uid]['solid_geometry']:
keys_to_delete.append(uid)
# actual delete of keys from the tools_storage dict
for k in keys_to_delete:
tools_storage.pop(k, None)
geo_obj.options["cnctooldia"] = str(tool_dia)
# this turn on the FlatCAMCNCJob plot for multiple tools
geo_obj.multigeo = True
geo_obj.multitool = True
geo_obj.tools.clear()
geo_obj.tools = dict(self.paint_tools)
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
@ -1766,7 +1951,7 @@ class ToolPaint(FlatCAMTool, Gerber):
# print("Indexing...", end=' ')
# geo_obj.make_index()
self.app.inform.emit(_("[success] Paint All Done."))
self.app.inform.emit(_("[success] Paint Area Done."))
# Initializes the new geometry object
def gen_paintarea_rest_machining(geo_obj, app_obj):
@ -1774,9 +1959,6 @@ class ToolPaint(FlatCAMTool, Gerber):
"Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
tool_dia = None
sorted_tools = []
for row in range(self.tools_table.rowCount()):
sorted_tools.append(float(self.tools_table.item(row, 1).text()))
sorted_tools.sort(reverse=True)
cleared_geo = []
@ -1829,16 +2011,16 @@ class ToolPaint(FlatCAMTool, Gerber):
return
# find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
for k, v in self.paint_tools.items():
for k, v in tools_storage.items():
if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
current_uid = int(k)
break
# add the solid_geometry to the current too in self.paint_tools dictionary and then reset the
# temporary list that stored that solid_geometry
self.paint_tools[current_uid]['solid_geometry'] = deepcopy(cleared_geo)
# add the solid_geometry to the current too in self.paint_tools (or tools_storage) dictionary and
# then reset the temporary list that stored that solid_geometry
tools_storage[current_uid]['solid_geometry'] = deepcopy(cleared_geo)
self.paint_tools[current_uid]['data']['name'] = name
tools_storage[current_uid]['data']['name'] = name
cleared_geo[:] = []
geo_obj.options["cnctooldia"] = str(tool_dia)
@ -1887,6 +2069,61 @@ class ToolPaint(FlatCAMTool, Gerber):
# Background
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
def paint_poly_ref(self, obj, sel_obj,
tooldia=None,
overlap=None,
order=None,
margin=None,
method=None,
outname=None,
connect=None,
contour=None,
tools_storage=None):
"""
Paints all polygons in this object that are within the sel_obj object
:param obj: painted object
:param sel_obj: paint only what is inside this object bounds
:param tooldia: a tuple or single element made out of diameters of the tools to be used
:param overlap: value by which the paths will overlap
:param order: if the tools are ordered and how
:param margin: a border around painting area
:param outname: name of the resulting object
:param connect: Connect lines to avoid tool lifts.
:param contour: Paint around the edges.
:param method: choice out of 'seed', 'normal', 'lines'
:param 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.
:return:
"""
geo = sel_obj.solid_geometry
try:
if isinstance(geo, MultiPolygon):
env_obj = geo.convex_hull
elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \
(isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon):
env_obj = cascaded_union(self.bound_obj.solid_geometry)
else:
env_obj = cascaded_union(self.bound_obj.solid_geometry)
env_obj = env_obj.convex_hull
sel_rect = env_obj.buffer(distance=0.0000001, join_style=base.JOIN_STYLE.mitre)
except Exception as e:
log.debug("ToolPaint.on_paint_button_click() --> %s" % str(e))
self.app.inform.emit(_("[ERROR_NOTCL] No object available."))
return
self.paint_poly_area(obj=obj,
sel_obj=sel_rect,
tooldia=tooldia,
overlap=overlap,
order=order,
margin=margin,
method=method,
outname=outname,
connect=connect,
contour=contour,
tools_storage=tools_storage)
@staticmethod
def paint_bounds(geometry):
def bounds_rec(o):

View File

@ -175,10 +175,10 @@ class Properties(FlatCAMTool):
env_obj = geo.convex_hull
elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \
(isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon):
env_obj = cascaded_union(self.bound_obj.solid_geometry)
env_obj = cascaded_union(obj.solid_geometry)
env_obj = env_obj.convex_hull
else:
env_obj = cascaded_union(self.bound_obj.solid_geometry)
env_obj = cascaded_union(obj.solid_geometry)
env_obj = env_obj.convex_hull
area_chull = env_obj.area

View File

@ -202,7 +202,6 @@ class TclCommand(object):
"""
arguments, options = self.parse_arguments(args)
named_args = {}
unnamed_args = []
@ -274,7 +273,7 @@ class TclCommand(object):
:return: None, output text or exception
"""
#self.worker_task.emit({'fcn': self.exec_command_test, 'params': [text, False]})
# self.worker_task.emit({'fcn': self.exec_command_test, 'params': [text, False]})
try:
self.log.debug("TCL command '%s' executed." % str(self.__class__))
@ -283,7 +282,7 @@ class TclCommand(object):
return self.execute(args, unnamed_args)
except Exception as unknown:
error_info = sys.exc_info()
self.log.error("TCL command '%s' failed." % str(self))
self.log.error("TCL command '%s' failed. Error text: %s" % (str(self), str(unknown)))
self.app.display_tcl_error(unknown, error_info)
self.raise_tcl_unknown_error(unknown)

View File

@ -1,8 +1,16 @@
from ObjectCollection import *
from tclCommands.TclCommand import TclCommandSignaled
from tclCommands.TclCommand import TclCommand
import gettext
import FlatCAMTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class TclCommandPaint(TclCommandSignaled):
class TclCommandPaint(TclCommand):
"""
Paint the interior of polygons
"""
@ -13,32 +21,53 @@ class TclCommandPaint(TclCommandSignaled):
# dictionary of types from Tcl command, needs to be ordered
arg_names = collections.OrderedDict([
('name', str),
('tooldia', float),
('overlap', float)
])
# dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
option_types = collections.OrderedDict([
('outname', str),
('tooldia', str),
('overlap', float),
('order', str),
('margin', float),
('method', str),
('connect', bool),
('contour', bool),
('all', bool),
('single', bool),
('ref', bool),
('box', str),
('x', float),
('y', float)
('y', float),
('outname', str),
])
# array of mandatory options for current Tcl command: required = {'name','outname'}
required = ['name', 'tooldia', 'overlap']
required = ['name']
# structured help for current command, args needs to be ordered
help = {
'main': "Paint polygons",
'args': collections.OrderedDict([
('name', 'Name of the source Geometry object.'),
('tooldia', 'Diameter of the tool to be used.'),
('overlap', 'Fraction of the tool diameter to overlap cuts.'),
('outname', 'Name of the resulting Geometry object.'),
('all', 'Paint all polygons in the object.'),
('x', 'X value of coordinate for the selection of a single polygon.'),
('y', 'Y value of coordinate for the selection of a single polygon.')
('name', 'Name of the source Geometry object. String.'),
('tooldia', 'Diameter of the tool to be used. Can be a comma separated list of diameters. No space is '
'allowed between tool diameters. E.g: correct: 0.5,1 / incorrect: 0.5, 1'),
('overlap', 'Fraction of the tool diameter to overlap cuts. Float number.'),
('order', 'Can have the values: "no", "fwd" and "rev". String.'
'It is useful when there are multiple tools in tooldia parameter.'
'"no" -> the order used is the one provided.'
'"fwd" -> tools are ordered from smallest to biggest.'
'"rev" -> tools are ordered from biggest to smallest.'),
('method', 'Algorithm for painting. Can be: "standard", "seed" or "lines".'),
('connect', 'Draw lines to minimize tool lifts. True or False'),
('contour', 'Cut around the perimeter of the painting. True or False'),
('all', 'Paint all polygons in the object. True or False'),
('single', 'Paint a single polygon specified by "x" and "y" parameters. True or False'),
('ref', 'Paint all polygons within a specified object with the name in "box" parameter. True or False'),
('box', 'name of the object to be used as paint reference when selecting "ref"" True. String.'),
('x', 'X value of coordinate for the selection of a single polygon. Float number.'),
('y', 'Y value of coordinate for the selection of a single polygon. Float number.'),
('outname', 'Name of the resulting Geometry object. String.'),
]),
'examples': []
}
@ -54,31 +83,177 @@ class TclCommandPaint(TclCommandSignaled):
"""
name = args['name']
tooldia = args['tooldia']
overlap = args['overlap']
if 'tooldia' in args:
tooldia = str(args['tooldia'])
else:
tooldia = float(self.app.defaults["tools_paintoverlap"])
if 'overlap' in args:
overlap = float(args['overlap'])
else:
overlap = float(self.app.defaults["tools_paintoverlap"])
if 'order' in args:
order = args['order']
else:
order = str(self.app.defaults["tools_paintorder"])
if 'margin' in args:
margin = float(args['margin'])
else:
margin = float(self.app.defaults["tools_paintmargin"])
if 'method' in args:
method = args['method']
else:
method = str(self.app.defaults["tools_paintmethod"])
if 'connect' in args:
connect = eval(str(args['connect']).capitalize())
else:
connect = eval(str(self.app.defaults["tools_pathconnect"]))
if 'contour' in args:
contour = eval(str(args['contour']).capitalize())
else:
contour = eval(str(self.app.defaults["tools_paintcontour"]))
if 'outname' in args:
outname = args['outname']
else:
outname = name + "_paint"
obj = self.app.collection.get_by_name(name)
# Get source object.
try:
obj = self.app.collection.get_by_name(str(name))
except Exception as e:
log.debug("TclCommandPaint.execute() --> %s" % str(e))
self.raise_tcl_error("%s: %s" % (_("Could not retrieve object"), name))
return "Could not retrieve object: %s" % name
try:
tools = [float(eval(dia)) for dia in tooldia.split(",") if dia != '']
except AttributeError:
tools = [float(tooldia)]
# store here the default data for Geometry Data
default_data = {}
default_data.update({
"name": '_paint',
"plot": self.app.defaults["geometry_plot"],
"cutz": self.app.defaults["geometry_cutz"],
"vtipdia": 0.1,
"vtipangle": 30,
"travelz": self.app.defaults["geometry_travelz"],
"feedrate": self.app.defaults["geometry_feedrate"],
"feedrate_z": self.app.defaults["geometry_feedrate_z"],
"feedrate_rapid": self.app.defaults["geometry_feedrate_rapid"],
"dwell": self.app.defaults["geometry_dwell"],
"dwelltime": self.app.defaults["geometry_dwelltime"],
"multidepth": self.app.defaults["geometry_multidepth"],
"ppname_g": self.app.defaults["geometry_ppname_g"],
"depthperpass": self.app.defaults["geometry_depthperpass"],
"extracut": self.app.defaults["geometry_extracut"],
"toolchange": self.app.defaults["geometry_toolchange"],
"toolchangez": self.app.defaults["geometry_toolchangez"],
"endz": self.app.defaults["geometry_endz"],
"spindlespeed": self.app.defaults["geometry_spindlespeed"],
"toolchangexy": self.app.defaults["geometry_toolchangexy"],
"startz": self.app.defaults["geometry_startz"],
"tooldia": self.app.defaults["tools_painttooldia"],
"paintmargin": self.app.defaults["tools_paintmargin"],
"paintmethod": self.app.defaults["tools_paintmethod"],
"selectmethod": self.app.defaults["tools_selectmethod"],
"pathconnect": self.app.defaults["tools_pathconnect"],
"paintcontour": self.app.defaults["tools_paintcontour"],
"paintoverlap": self.app.defaults["tools_paintoverlap"]
})
paint_tools = dict()
tooluid = 0
for tool in tools:
tooluid += 1
paint_tools.update({
int(tooluid): {
'tooldia': float('%.4f' % tool),
'offset': 'Path',
'offset_value': 0.0,
'type': 'Iso',
'tool_type': 'C1',
'data': dict(default_data),
'solid_geometry': []
}
})
if obj is None:
self.raise_tcl_error("Object not found: %s" % name)
return "Object not found: %s" % name
if not isinstance(obj, Geometry):
self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
if 'all' in args and args['all']:
obj.paint_poly_all(tooldia, overlap, outname)
# Paint all polygons in the painted object
if 'all' in args and args['all'] is True:
self.app.paint_tool.paint_poly_all(obj=obj,
tooldia=tooldia,
overlap=overlap,
order=order,
margin=margin,
method=method,
outname=outname,
connect=connect,
contour=contour,
tools_storage=paint_tools)
return
if 'x' not in args or 'y' not in args:
self.raise_tcl_error('Expected -all 1 or -x <value> and -y <value>.')
# Paint single polygon in the painted object
elif 'single' in args and args['single'] is True:
if 'x' not in args or 'y' not in args:
self.raise_tcl_error('%s' % _("Expected -x <value> and -y <value>."))
else:
x = args['x']
y = args['y']
x = args['x']
y = args['y']
self.app.paint_tool.paint_poly(obj=obj,
inside_pt=[x, y],
tooldia=tooldia,
overlap=overlap,
order=order,
margin=margin,
method=method,
outname=outname,
connect=connect,
contour=contour,
tools_storage=paint_tools)
return
obj.paint_poly_single_click([x, y], tooldia, overlap, outname)
# Paint all polygons found within the box object from the the painted object
elif 'ref' in args and args['ref'] is True:
if 'box' not in args:
self.raise_tcl_error('%s' % _("Expected -box <value>."))
else:
box_name = args['box']
# Get box source object.
try:
box_obj = self.app.collection.get_by_name(str(box_name))
except Exception as e:
log.debug("TclCommandPaint.execute() --> %s" % str(e))
self.raise_tcl_error("%s: %s" % (_("Could not retrieve box object"), name))
return "Could not retrieve object: %s" % name
self.app.paint_tool.paint_poly_ref(obj=obj,
sel_obj=box_obj,
tooldia=tooldia,
overlap=overlap,
order=order,
margin=margin,
method=method,
outname=outname,
connect=connect,
contour=contour,
tools_storage=paint_tools)
return
else:
self.raise_tcl_error("%s:" % _("There was none of the following args: 'ref', 'single', 'all'.\n"
"Paint failed."))
return "There was none of the following args: 'ref', 'single', 'all'.\n" \
"Paint failed."