Browse Source

- 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

Marius Stanciu 6 years ago
parent
commit
87d1558977
5 changed files with 617 additions and 203 deletions
  1. 3 0
      README.md
  2. 407 170
      flatcamTools/ToolPaint.py
  3. 2 2
      flatcamTools/ToolProperties.py
  4. 2 3
      tclCommands/TclCommand.py
  5. 203 28
      tclCommands/TclCommandPaint.py

+ 3 - 0
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
 

+ 407 - 170
flatcamTools/ToolPaint.py

@@ -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_area(obj=self.paint_obj,
-                                 sel_obj=sel_rect,
-                                 outname=o_name,
-                                 overlap=overlap,
-                                 connect=connect,
-                                 contour=contour)
+            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)
 
-    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))
-                return
+            total_geometry = []
+            current_uid = int(1)
 
-            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.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)
 
-            geo_obj.options["cnctooldia"] = str(tooldia)
+                    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 = False
+            geo_obj.multigeo = True
             geo_obj.multitool = True
+            geo_obj.tools.clear()
+            geo_obj.tools = dict(tools_storage)
 
-            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
+            # 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
 
-            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)
-            tool_dia = None
+            # assert isinstance(geo_obj, FlatCAMGeometry), \
+            #     "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
 
-            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()
+            tool_dia = None
             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):

+ 2 - 2
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

+ 2 - 3
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)
 

+ 203 - 28
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
 
-class TclCommandPaint(TclCommandSignaled):
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+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)
-        if obj is None:
-            self.raise_tcl_error("Object not found: %s" % 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"],
 
-        if not isinstance(obj, Geometry):
-            self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
+            "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()
 
-        if 'all' in args and args['all']:
-            obj.paint_poly_all(tooldia, overlap, outname)
+        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:
+            return "Object not found: %s" % name
+
+        # 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."