Преглед изворни кода

- finished added 'Area' type of Paint in Paint Tool

Marius Stanciu пре 6 година
родитељ
комит
40dc73ce93
3 измењених фајлова са 384 додато и 26 уклоњено
  1. 11 6
      FlatCAMApp.py
  2. 1 0
      README.md
  3. 372 20
      flatcamTools/ToolPaint.py

+ 11 - 6
FlatCAMApp.py

@@ -5694,13 +5694,12 @@ class App(QtCore.QObject):
         self.plotcanvas.vispy_canvas.view.camera.pan_button_setting = self.defaults['global_pan_button']
 
         self.pos_canvas = self.plotcanvas.vispy_canvas.translate_coords(event.pos)
+        self.pos = (self.pos_canvas[0], self.pos_canvas[1])
+        self.app_cursor.enabled = False
 
-        if self.grid_status() == True:
+        if self.grid_status():
             self.pos = self.geo_editor.snap(self.pos_canvas[0], self.pos_canvas[1])
             self.app_cursor.enabled = True
-        else:
-            self.pos = (self.pos_canvas[0], self.pos_canvas[1])
-            self.app_cursor.enabled = False
 
         try:
             modifiers = QtWidgets.QApplication.keyboardModifiers()
@@ -5753,7 +5752,7 @@ class App(QtCore.QObject):
         if self.rel_point1 is not None:
             try:  # May fail in case mouse not within axes
                 pos_canvas = self.plotcanvas.vispy_canvas.translate_coords(event.pos)
-                if self.grid_status():
+                if self.grid_status() == True:
                     pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                     self.app_cursor.enabled = True
                     # Update cursor
@@ -6157,6 +6156,12 @@ class App(QtCore.QObject):
             face_color = kwargs['face_color']
         else:
             face_color = self.defaults['global_sel_fill']
+
+        if 'face_alpha' in kwargs:
+            face_alpha = kwargs['face_alpha']
+        else:
+            face_alpha = 0.3
+
         x0, y0 = old_coords
         x1, y1 = coords
         pt1 = (x0, y0)
@@ -6166,7 +6171,7 @@ class App(QtCore.QObject):
         sel_rect = Polygon([pt1, pt2, pt3, pt4])
 
         color_t = Color(face_color)
-        color_t.alpha = 0.3
+        color_t.alpha = face_alpha
         self.move_tool.sel_shapes.add(sel_rect, color=color, face_color=color_t, update=True,
                                       layer=0, tolerance=None)
 

+ 1 - 0
README.md

@@ -12,6 +12,7 @@ CAD program, and create G-Code for Isolation routing.
 9.09.2018
 
 - added Exception handing for the case when the user is trying to save & overwrite a file already opened in another file
+- finished added 'Area' type of Paint in Paint Tool
 
 7.09.2019
 

+ 372 - 20
flatcamTools/ToolPaint.py

@@ -269,6 +269,9 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.units = ''
         self.paint_tools = {}
         self.tooluid = 0
+        self.first_click = False
+        self.cursor_pos = None
+
         # store here the default data for Geometry Data
         self.default_data = {}
         self.default_data.update({
@@ -828,29 +831,61 @@ class ToolPaint(FlatCAMTool, Gerber):
             def on_mouse_press(event):
                 # do paint single only for left mouse clicks
                 if event.button == 1:
-                    self.app.inform.emit(_("Painting selected area..."))
-                    self.app.plotcanvas.vis_disconnect('mouse_press', doit)
-
-                    pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
-                    if self.app.grid_status():
-                        pos = self.app.geo_editor.snap(pos[0], pos[1])
+                    if not self.first_click:
+                        self.first_click = True
+                        self.app.inform.emit(_("[WARNING_NOTCL] Click the end point of the paint area."))
 
-                    self.paint_poly(self.paint_obj,
-                                    inside_pt=[pos[0], pos[1]],
-                                    tooldia=tooldia,
-                                    overlap=overlap,
-                                    connect=connect,
-                                    contour=contour)
-
-            # to be called after second click on plot
-            def on_mouse_click_release(event):
-                self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-                self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-                self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+                        self.cursor_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                        if self.app.grid_status():
+                            self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1])
+                    else:
+                        self.app.inform.emit(_("Done."))
+                        self.first_click = False
+                        self.app.delete_selection_shape()
+
+                        curr_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                        if self.app.grid_status():
+                            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+
+                        x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
+                        x1, y1 = curr_pos[0], curr_pos[1]
+                        pt1 = (x0, y0)
+                        pt2 = (x1, y0)
+                        pt3 = (x1, y1)
+                        pt4 = (x0, y1)
+                        sel_rect = Polygon([pt1, pt2, pt3, pt4])
+
+                        self.paint_poly_area(obj=self.paint_obj,
+                                             sel_obj= sel_rect,
+                                             outname=o_name,
+                                             overlap=overlap,
+                                             connect=connect,
+                                             contour=contour)
+
+                        self.app.plotcanvas.vis_disconnect('mouse_press', on_mouse_press)
+                        self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
+
+                        self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
+                        self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+                        self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
 
             # called on mouse move
             def on_mouse_move(event):
-                pass
+                curr_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                self.app.app_cursor.enabled = False
+
+                if self.app.grid_status():
+                    self.app.app_cursor.enabled = True
+                    # Update cursor
+                    curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+                    self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
+                                                 symbol='++', edge_color='black', size=20)
+
+                if self.first_click:
+                    self.app.delete_selection_shape()
+                    self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
+                                                         coords=(curr_pos[0], curr_pos[1]),
+                                                         face_alpha=0.0)
 
             self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
             self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
@@ -858,7 +893,6 @@ class ToolPaint(FlatCAMTool, Gerber):
 
             self.app.plotcanvas.vis_connect('mouse_press', on_mouse_press)
             self.app.plotcanvas.vis_connect('mouse_move', on_mouse_move)
-            self.app.plotcanvas.vis_connect('mouse_release', on_mouse_click_release)
 
     def paint_poly(self, obj, inside_pt, tooldia, overlap, outname=None, connect=True, contour=True):
         """
@@ -1321,5 +1355,323 @@ 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):
+        """
+        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 connect: Connect lines to avoid tool lifts.
+        :param contour: Paint around the edges.
+        :return:
+        """
+        paint_method = 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
+            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
+
+        def recurse(geometry, reset=True):
+            """
+            Creates a list of non-iterable linear geometry objects.
+            Results are placed in self.flat_geometry
+
+            :param geometry: Shapely type or list or list of list of such.
+            :param reset: Clears the contents of self.flat_geometry.
+            """
+
+            if geometry is None:
+                return
+
+            if reset:
+                self.flat_geometry = []
+
+            # ## If iterable, expand recursively.
+            try:
+                for geo in geometry:
+                    if geo is not None:
+                        recurse(geometry=geo, reset=False)
+
+            # ## Not iterable, do the actual indexing and add.
+            except TypeError:
+                if isinstance(geometry, LinearRing):
+                    g = Polygon(geometry)
+                    self.flat_geometry.append(g)
+                else:
+                    self.flat_geometry.append(geometry)
+
+            return self.flat_geometry
+
+        # 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
+
+            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)
+
+            geo_to_paint = []
+            for poly in obj.solid_geometry:
+                new_pol = poly.intersection(sel_obj)
+                geo_to_paint.append(new_pol)
+
+            try:
+                a, b, c, d = self.paint_bounds(geo_to_paint)
+                geo_obj.options['xmin'] = a
+                geo_obj.options['ymin'] = b
+                geo_obj.options['xmax'] = c
+                geo_obj.options['ymax'] = d
+            except Exception as e:
+                log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(e))
+                return
+
+            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():
+                    if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
+                        current_uid = int(k)
+                        break
+
+                for geo in recurse(geo_to_paint):
+                    try:
+                        # Polygons are the only really paintable geometries, lines in theory have no area to be painted
+                        if not isinstance(geo, Polygon):
+                            continue
+                        poly_buf = geo.buffer(-paint_margin)
+
+                        if paint_method == "seed":
+                            # Type(cp) == FlatCAMRTreeStorage | None
+                            cp = self.clear_polygon2(poly_buf,
+                                                     tooldia=tool_dia,
+                                                     steps_per_circle=self.app.defaults["geometry_circle_steps"],
+                                                     overlap=over,
+                                                     contour=cont,
+                                                     connect=conn)
+
+                        elif paint_method == "lines":
+                            # Type(cp) == FlatCAMRTreeStorage | None
+                            cp = self.clear_polygon3(poly_buf,
+                                                     tooldia=tool_dia,
+                                                     steps_per_circle=self.app.defaults["geometry_circle_steps"],
+                                                     overlap=over,
+                                                     contour=cont,
+                                                     connect=conn)
+
+                        else:
+                            # Type(cp) == FlatCAMRTreeStorage | None
+                            cp = self.clear_polygon(poly_buf,
+                                                    tooldia=tool_dia,
+                                                    steps_per_circle=self.app.defaults["geometry_circle_steps"],
+                                                    overlap=over,
+                                                    contour=cont,
+                                                    connect=conn)
+
+                        if cp is not None:
+                            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 All. Try a different combination of parameters. "
+                              "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)
+
+                self.paint_tools[current_uid]['data']['name'] = name
+                total_geometry[:] = []
+
+            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)
+
+            # 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
+
+            # Experimental...
+            # print("Indexing...", end=' ')
+            # geo_obj.make_index()
+
+            self.app.inform.emit(_("[success] Paint All Done."))
+
+        # 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)
+
+            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 = []
+            current_uid = int(1)
+            geo_obj.solid_geometry = []
+
+            try:
+                a, b, c, d = obj.bounds()
+                geo_obj.options['xmin'] = a
+                geo_obj.options['ymin'] = b
+                geo_obj.options['xmax'] = c
+                geo_obj.options['ymax'] = d
+            except Exception as e:
+                log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(e))
+                return
+
+            for tool_dia in sorted_tools:
+                for geo in recurse(obj.solid_geometry):
+                    try:
+                        geo = Polygon(geo) if not isinstance(geo, Polygon) else geo
+                        poly_buf = geo.buffer(-paint_margin)
+                        cp = None
+
+                        if paint_method == "standard":
+                            # Type(cp) == FlatCAMRTreeStorage | None
+                            cp = self.clear_polygon(poly_buf, tooldia=tool_dia,
+                                                    steps_per_circle=self.app.defaults["geometry_circle_steps"],
+                                                    overlap=over, contour=cont, connect=conn)
+
+                        elif paint_method == "seed":
+                            # Type(cp) == FlatCAMRTreeStorage | None
+                            cp = self.clear_polygon2(poly_buf, tooldia=tool_dia,
+                                                     steps_per_circle=self.app.defaults["geometry_circle_steps"],
+                                                     overlap=over, contour=cont, connect=conn)
+
+                        elif paint_method == "lines":
+                            # Type(cp) == FlatCAMRTreeStorage | None
+                            cp = self.clear_polygon3(poly_buf, tooldia=tool_dia,
+                                                     steps_per_circle=self.app.defaults["geometry_circle_steps"],
+                                                     overlap=over, contour=cont, connect=conn)
+
+                        if cp is not None:
+                            cleared_geo += 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 All. Try a different combination of parameters. "
+                              "Or a different Method of paint\n%s") % str(e))
+                        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():
+                    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)
+
+                self.paint_tools[current_uid]['data']['name'] = name
+                cleared_geo[:] = []
+
+            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)
+
+            # 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_NOTCL] 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
+
+            # Experimental...
+            # print("Indexing...", end=' ')
+            # geo_obj.make_index()
+
+            self.app.inform.emit(_("[success] Paint All with Rest-Machining done."))
+
+        def job_thread(app_obj):
+            try:
+                if self.rest_cb.isChecked():
+                    app_obj.new_object("geometry", name, gen_paintarea_rest_machining)
+                else:
+                    app_obj.new_object("geometry", name, gen_paintarea)
+            except Exception as e:
+                proc.done()
+                traceback.print_stack()
+                return
+            proc.done()
+            # focus on Selected Tab
+            self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
+
+        self.app.inform.emit(_("Polygon Paint started ..."))
+
+        # Promise object with the new name
+        self.app.collection.promise(name)
+
+        # Background
+        self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+
+    @staticmethod
+    def paint_bounds(geometry):
+        def bounds_rec(o):
+            if type(o) is list:
+                minx = Inf
+                miny = Inf
+                maxx = -Inf
+                maxy = -Inf
+
+                for k in o:
+                    try:
+                        minx_, miny_, maxx_, maxy_ = bounds_rec(k)
+                    except Exception as e:
+                        log.debug("ToolPaint.bounds() --> %s" % str(e))
+                        return
+
+                    minx = min(minx, minx_)
+                    miny = min(miny, miny_)
+                    maxx = max(maxx, maxx_)
+                    maxy = max(maxy, maxy_)
+                return minx, miny, maxx, maxy
+            else:
+                # it's a Shapely object, return it's bounds
+                return o.bounds
+
+        return bounds_rec(geometry)
+
     def reset_fields(self):
         self.object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))