diff --git a/README.md b/README.md
index 1bb62a18..eac5dcc6 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/flatcamTools/ToolPaint.py b/flatcamTools/ToolPaint.py
index 7d45ed51..937be49a 100644
--- a/flatcamTools/ToolPaint.py
+++ b/flatcamTools/ToolPaint.py
@@ -232,10 +232,10 @@ class ToolPaint(FlatCAMTool, Gerber):
# Method
methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
methodlabel.setToolTip(
- _("Algorithm for non-copper clearing:
"
- "Standard: Fixed step inwards.
"
- "Seed-based: Outwards from seed.
"
- "Line-based: 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):
diff --git a/flatcamTools/ToolProperties.py b/flatcamTools/ToolProperties.py
index 40ca330f..d1517cc6 100644
--- a/flatcamTools/ToolProperties.py
+++ b/flatcamTools/ToolProperties.py
@@ -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
diff --git a/tclCommands/TclCommand.py b/tclCommands/TclCommand.py
index 5e69a2c4..b50b380c 100644
--- a/tclCommands/TclCommand.py
+++ b/tclCommands/TclCommand.py
@@ -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)
diff --git a/tclCommands/TclCommandPaint.py b/tclCommands/TclCommandPaint.py
index 8a118290..8ea70516 100644
--- a/tclCommands/TclCommandPaint.py
+++ b/tclCommands/TclCommandPaint.py
@@ -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 and -y .')
+ # 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 and -y ."))
+ 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 ."))
+ 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."