From c025d6ad79e2b426bcee2a13e66825ceff1d3e7d Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Mon, 25 Nov 2019 22:22:36 +0200 Subject: [PATCH] - remade the Paint Tool - single polygon painting; now it can single paint a list of polygons that are clicked onto (right click will start the actual painting) --- FlatCAMObj.py | 6 +- README.md | 1 + flatcamTools/ToolPaint.py | 332 ++++++++++++++++++++++---------------- 3 files changed, 193 insertions(+), 146 deletions(-) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index fe75d9ce..3835dd0c 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -623,7 +623,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): # Mouse events self.mr = None - # dict to store the polygons selected for isolation; key is the shape added to be ploted and value is the poly + # dict to store the polygons selected for isolation; key is the shape added to be plotted and value is the poly self.poly_dict = dict() # store the status of grid snapping @@ -1045,7 +1045,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): if iso_scope == 'all': self.isolate(iso_type=iso_type) else: - # disengage the grid snapping since it will be hard to find the drills on grid + # disengage the grid snapping since it may be hard to click on polygons with grid snapping on if self.app.ui.grid_snap_btn.isChecked(): self.grid_status_memory = True self.app.ui.grid_snap_btn.trigger() @@ -1059,7 +1059,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): else: self.app.plotcanvas.graph_event_disconnect(self.app.mr) - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on polygon to isolate it.")) + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to isolate it.")) def on_mouse_click_release(self, event): if self.app.is_legacy is False: diff --git a/README.md b/README.md index f6658b54..b54755f2 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ CAD program, and create G-Code for Isolation routing. - clicking to add a polygon when doing Single type isolation will add a blue shape marking the selected polygon, second click will remove that shape - fixed bugs in Paint Tool when painting single polygon - in Gerber isolation added the option to selectively isolate only certain polygons - made it to work for Legacy(2D) graphic mode +- remade the Paint Tool - single polygon painting; now it can single paint a list of polygons that are clicked onto (right click will start the actual painting) 23.11.2019 diff --git a/flatcamTools/ToolPaint.py b/flatcamTools/ToolPaint.py index 463cdeb9..2c92c3bc 100644 --- a/flatcamTools/ToolPaint.py +++ b/flatcamTools/ToolPaint.py @@ -395,6 +395,12 @@ class ToolPaint(FlatCAMTool, Gerber): self.sel_rect = [] + # store here if the grid snapping is active + self.grid_status_memory = False + + # dict to store the polygons selected for painting; key is the shape added to be plotted and value is the poly + self.poly_dict = dict() + # store here the default data for Geometry Data self.default_data = {} self.default_data.update({ @@ -1017,36 +1023,16 @@ class ToolPaint(FlatCAMTool, Gerber): contour=self.contour) elif self.select_method == "single": - self.app.inform.emit('[WARNING_NOTCL] %s' % - _("Click inside the desired polygon.")) + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to paint it.")) - # use the first tool in the tool table; get the diameter - # tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text())) + # disengage the grid snapping since it may be hard to click on polygons with grid snapping on + if self.app.ui.grid_snap_btn.isChecked(): + self.grid_status_memory = True + self.app.ui.grid_snap_btn.trigger() + else: + self.grid_status_memory = False - # To be called after clicking on the plot. - def doit(event): - # do paint single only for left mouse clicks - if event.button == 1: - self.app.inform.emit(_("Painting polygon...")) - if self.app.is_legacy is False: - self.app.plotcanvas.graph_event_disconnect('mouse_press', doit) - else: - self.app.plotcanvas.graph_event_disconnect(self.mp) - - pos = self.app.plotcanvas.translate_coords(event.pos) - if self.app.grid_status() == True: - pos = self.app.geo_editor.snap(pos[0], pos[1]) - - self.paint_poly(self.paint_obj, - inside_pt=[pos[0], pos[1]], - tooldia=self.tooldia_list, - overlap=self.overlap, - connect=self.connect, - contour=self.contour) - self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', - self.app.on_mouse_click_over_plot) - self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release', - self.app.on_mouse_click_release_over_plot) + self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_single_poly_mouse_release) if self.app.is_legacy is False: self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot) @@ -1055,11 +1041,8 @@ class ToolPaint(FlatCAMTool, Gerber): self.app.plotcanvas.graph_event_disconnect(self.app.mr) self.app.plotcanvas.graph_event_disconnect(self.app.mp) - self.mp = self.app.plotcanvas.graph_event_connect('mouse_press', doit) - elif self.select_method == "area": - self.app.inform.emit('[WARNING_NOTCL] %s' % - _("Click the start point of the paint area.")) + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the paint area.")) if self.app.is_legacy is False: self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot) @@ -1072,7 +1055,6 @@ class ToolPaint(FlatCAMTool, Gerber): self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release) self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move) - elif self.select_method == 'ref': self.bound_obj_name = self.box_combo.currentText() # Get source object. @@ -1092,6 +1074,91 @@ class ToolPaint(FlatCAMTool, Gerber): connect=self.connect, contour=self.contour) + # To be called after clicking on the plot. + def on_single_poly_mouse_release(self, event): + if self.app.is_legacy is False: + event_pos = event.pos + right_button = 2 + event_is_dragging = self.app.event_is_dragging + else: + event_pos = (event.xdata, event.ydata) + right_button = 3 + event_is_dragging = self.app.ui.popMenu.mouse_is_panning + + try: + x = float(event_pos[0]) + y = float(event_pos[1]) + except TypeError: + return + + event_pos = (x, y) + curr_pos = self.app.plotcanvas.translate_coords(event_pos) + + # do paint single only for left mouse clicks + if event.button == 1: + clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]), geoset=self.paint_obj.solid_geometry) + + if clicked_poly: + if clicked_poly not in self.poly_dict.values(): + shape_id = self.app.tool_shapes.add(tolerance=self.paint_obj.drawing_tolerance, + layer=0, + shape=clicked_poly, + color=self.app.defaults['global_sel_draw_color'] + 'AF', + face_color=self.app.defaults['global_sel_draw_color'] + 'AF', + visible=True) + self.poly_dict[shape_id] = clicked_poly + self.app.inform.emit( + '%s: %d. %s' % (_("Added polygon"), + int(len(self.poly_dict)), + _("Click to add next polygon or right click to start painting.")) + ) + else: + try: + for k, v in list(self.poly_dict.items()): + if v == clicked_poly: + self.app.tool_shapes.remove(k) + self.poly_dict.pop(k) + break + except TypeError: + return + self.app.inform.emit( + '%s. %s' % (_("Removed polygon"), + _("Click to add/remove next polygon or right click to start painting.")) + ) + + self.app.tool_shapes.redraw() + else: + self.app.inform.emit(_("No polygon detected under click position.")) + + elif event.button == right_button and event_is_dragging is False: + # restore the Grid snapping if it was active before + if self.grid_status_memory is True: + self.app.ui.grid_snap_btn.trigger() + + if self.app.is_legacy is False: + self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_single_poly_mouse_release) + else: + self.app.plotcanvas.graph_event_disconnect(self.mr) + + self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', + self.app.on_mouse_click_over_plot) + self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release', + self.app.on_mouse_click_release_over_plot) + + self.app.tool_shapes.clear(update=True) + + if self.poly_dict: + poly_list = deepcopy(list(self.poly_dict.values())) + self.paint_poly(self.paint_obj, + poly_list=poly_list, + tooldia=self.tooldia_list, + overlap=self.overlap, + connect=self.connect, + contour=self.contour) + self.poly_dict.clear() + else: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting.")) + # To be called after clicking on the plot. def on_mouse_release(self, event): if self.app.is_legacy is False: @@ -1229,6 +1296,7 @@ class ToolPaint(FlatCAMTool, Gerber): def paint_poly(self, obj, inside_pt=None, + poly_list=None, tooldia=None, overlap=None, order=None, @@ -1260,19 +1328,15 @@ class ToolPaint(FlatCAMTool, Gerber): :return: None """ - # Which polygon. - # poly = find_polygon(self.solid_geometry, inside_pt) if isinstance(obj, FlatCAMGerber): if self.app.defaults["gerber_buffering"] == 'no': self.app.inform.emit('%s %s %s' % (_("Paint Tool."), _("Normal painting polygon task started."), _("Buffering geometry..."))) else: - self.app.inform.emit('%s %s' % - (_("Paint Tool."), _("Normal painting polygon task started."))) + self.app.inform.emit('%s %s' % (_("Paint Tool."), _("Normal painting polygon task started."))) else: - self.app.inform.emit('%s %s' % - (_("Paint Tool."), _("Normal painting polygon task started."))) + self.app.inform.emit('%s %s' % (_("Paint Tool."), _("Normal painting polygon task started."))) if isinstance(obj, FlatCAMGerber): if self.app.defaults["tools_paint_plotting"] == 'progressive': @@ -1281,37 +1345,29 @@ class ToolPaint(FlatCAMTool, Gerber): else: obj.solid_geometry = obj.solid_geometry.buffer(0) - poly = self.find_polygon(point=inside_pt, geoset=obj.solid_geometry) - - paint_method = method if method is not None else self.paintmethod_combo.get_value() - - if margin is not None: - paint_margin = margin - else: - paint_margin = float(self.paintmargin_entry.get_value()) - - # determine if to use the progressive plotting - if self.app.defaults["tools_paint_plotting"] == 'progressive': - prog_plot = True - else: - prog_plot = False + polygon_list = None + if inside_pt and poly_list is None: + polygon_list = [self.find_polygon(point=inside_pt, geoset=obj.solid_geometry)] + elif inside_pt is None and poly_list: + polygon_list = poly_list # No polygon? - if poly is None: + if polygon_list is None: self.app.log.warning('No polygon found.') self.app.inform.emit('[WARNING] %s' % _('No polygon found.')) return - proc = self.app.proc_container.new(_("Painting polygon...")) - self.app.inform.emit('%s %s: %s' % - (_("Paint Tool."), _("Painting polygon at location"), str(inside_pt))) + paint_method = method if method is not None else self.paintmethod_combo.get_value() + paint_margin = float(self.paintmargin_entry.get_value()) if margin is None else margin + # determine if to use the progressive plotting + prog_plot = True if self.app.defaults["tools_paint_plotting"] == 'progressive' else False 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() + tools_storage = self.paint_tools if tools_storage is None else tools_storage sorted_tools = [] if tooldia is not None: @@ -1326,24 +1382,17 @@ class ToolPaint(FlatCAMTool, Gerber): 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 + # sort the tools if we have an order selected in the UI + if order == 'fwd': + sorted_tools.sort(reverse=False) + elif order == 'rev': + sorted_tools.sort(reverse=True) + + proc = self.app.proc_container.new(_("Painting polygon...")) # 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(app_obj, App) - - tool_dia = None - if order == 'fwd': - sorted_tools.sort(reverse=False) - elif order == 'rev': - sorted_tools.sort(reverse=True) - else: - pass + geo_obj.solid_geometry = list() def paint_p(polyg, tooldiameter): cpoly = None @@ -1390,8 +1439,72 @@ class ToolPaint(FlatCAMTool, Gerber): _('Geometry could not be painted completely')) return None + current_uid = int(1) + tool_dia = None + 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('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool_dia)): + current_uid = int(k) + break + try: - a, b, c, d = poly.bounds + poly_buf = [pol.buffer(-paint_margin) for pol in polygon_list] + cp = list() + try: + for pp in poly_buf: + cp.append(paint_p(pp, tooldiameter=tool_dia)) + except TypeError: + cp = paint_p(poly_buf, tooldiameter=tool_dia) + + total_geometry = list() + if cp: + try: + for x in cp: + total_geometry += list(x.get_objects()) + except TypeError: + total_geometry = list(cp.get_objects()) + except FlatCAMApp.GracefulException: + return "fail" + except Exception as e: + log.debug("Could not Paint the polygons. %s" % str(e)) + app_obj.inform.emit('[ERROR] %s\n%s' % + (_("Could not do Paint. Try a different combination of parameters. " + "Or a different strategy of paint"), + str(e) + ) + ) + return "fail" + + # 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 + + # clean the progressive plotted shapes if it was used + if self.app.defaults["tools_paint_plotting"] == 'progressive': + self.temp_shapes.clear(update=True) + + # delete tools with empty geometry + # look for keys in the tools_storage dict that have 'solid_geometry' values empty + for uid in list(tools_storage.keys()): + # if the solid_geometry (type=list) is empty + if not tools_storage[uid]['solid_geometry']: + tools_storage.pop(uid, None) + + geo_obj.options["cnctooldia"] = str(tool_dia) + + # this will 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) + + geo_obj.solid_geometry = cascaded_union(tools_storage[current_uid]['solid_geometry']) + + try: + a, b, c, d = geo_obj.solid_geometry.bounds geo_obj.options['xmin'] = a geo_obj.options['ymin'] = b geo_obj.options['xmax'] = c @@ -1400,80 +1513,12 @@ class ToolPaint(FlatCAMTool, Gerber): 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 tools_storage.items(): - if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, 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, tooldiameter=tool_dia)) - else: - cp = paint_p(poly_buf, tooldiameter=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 FlatCAMApp.GracefulException: - return "fail" - except Exception as e: - log.debug("Could not Paint the polygons. %s" % str(e)) - app_obj.inform.emit('[ERROR] %s\n%s' % - (_("Could not do Paint. Try a different combination of parameters. " - "Or a different strategy of paint"), - str(e) - ) - ) - return "fail" - - # 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[:] = [] - - # clean the progressive plotted shapes if it was used - if self.app.defaults["tools_paint_plotting"] == 'progressive': - self.temp_shapes.clear(update=True) - - # delete tools with empty geometry - 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] %s' % _("There is no Painting Geometry in the file.\n" @@ -1481,6 +1526,7 @@ class ToolPaint(FlatCAMTool, Gerber): "Change the painting parameters and try again.")) return + total_geometry[:] = [] self.app.inform.emit('[success] %s' % _("Paint Single Done.")) # Experimental...