From 935d556c93f957024e71060cc52eb8ae99d5c337 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 24 Apr 2020 21:08:27 +0300 Subject: [PATCH] - updated Paint Tool for the new Tool DB - updated the Tcl commands CopperClear and Paint --- CHANGELOG.md | 2 + flatcamTools/ToolPaint.py | 1336 ++++++++------------------ tclCommands/TclCommandCopperClear.py | 70 +- tclCommands/TclCommandPaint.py | 49 +- 4 files changed, 498 insertions(+), 959 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b0b4b10..7ba9929d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ CHANGELOG for FlatCAM beta - updated the Readme file with the steps for installation for MacOS - updated the requirements.txt file - updated some of the icons in the dark_resources folder (some added, some modified) +- updated Paint Tool for the new Tool DB +- updated the Tcl commands CopperClear and Paint 23.04.2020 diff --git a/flatcamTools/ToolPaint.py b/flatcamTools/ToolPaint.py index 4ad2890f..1bec59b1 100644 --- a/flatcamTools/ToolPaint.py +++ b/flatcamTools/ToolPaint.py @@ -1556,11 +1556,11 @@ class ToolPaint(FlatCAMTool, Gerber): def on_mouse_release(self, event): if self.app.is_legacy is False: event_pos = event.pos - event_is_dragging = event.is_dragging + # event_is_dragging = event.is_dragging right_button = 2 else: event_pos = (event.xdata, event.ydata) - event_is_dragging = self.app.plotcanvas.is_dragging + # event_is_dragging = self.app.plotcanvas.is_dragging right_button = 3 try: @@ -1682,11 +1682,11 @@ class ToolPaint(FlatCAMTool, Gerber): if self.app.is_legacy is False: event_pos = event.pos event_is_dragging = event.is_dragging - right_button = 2 + # right_button = 2 else: event_pos = (event.xdata, event.ydata) event_is_dragging = self.app.plotcanvas.is_dragging - right_button = 3 + # right_button = 3 try: x = float(event_pos[0]) @@ -1734,8 +1734,8 @@ class ToolPaint(FlatCAMTool, Gerber): self.draw_moving_selection_shape_poly(points=self.points, data=(curr_pos[0], curr_pos[1])) def on_key_press(self, event): - modifiers = QtWidgets.QApplication.keyboardModifiers() - matplotlib_key_flag = False + # modifiers = QtWidgets.QApplication.keyboardModifiers() + # matplotlib_key_flag = False # events out of the self.app.collection view (it's about Project Tab) are of type int if type(event) is int: @@ -1744,7 +1744,7 @@ class ToolPaint(FlatCAMTool, Gerber): elif type(event) == QtGui.QKeyEvent: key = event.key() elif isinstance(event, mpl_key_event): # MatPlotLib key events are trickier to interpret than the rest - matplotlib_key_flag = True + # matplotlib_key_flag = True key = event.key key = QtGui.QKeySequence(key) @@ -1754,13 +1754,17 @@ class ToolPaint(FlatCAMTool, Gerber): if '+' in key_string: mod, __, key_text = key_string.rpartition('+') if mod.lower() == 'ctrl': - modifiers = QtCore.Qt.ControlModifier + # modifiers = QtCore.Qt.ControlModifier + pass elif mod.lower() == 'alt': - modifiers = QtCore.Qt.AltModifier + # modifiers = QtCore.Qt.AltModifier + pass elif mod.lower() == 'shift': - modifiers = QtCore.Qt.ShiftModifier + # modifiers = QtCore.Qt.ShiftModifier + pass else: - modifiers = QtCore.Qt.NoModifier + # modifiers = QtCore.Qt.NoModifier + pass key = QtGui.QKeySequence(key_text) # events from Vispy are of type KeyEvent @@ -2091,7 +2095,6 @@ class ToolPaint(FlatCAMTool, Gerber): else: self.app.inform.emit('%s %s' % (_("Paint Tool."), _("Normal painting polygon task started."))) - 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) or (inside_pt and poly_list): @@ -2137,6 +2140,7 @@ class ToolPaint(FlatCAMTool, Gerber): tool_dia = None current_uid = None final_solid_geometry = [] + old_disp_number = 0 for tool_dia in sorted_tools: log.debug("Starting geometry processing for tool: %s" % str(tool_dia)) @@ -2184,12 +2188,30 @@ class ToolPaint(FlatCAMTool, Gerber): cp = [] try: for pp in poly_buf: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn, cont=cont, paint_method=paint_method, obj=obj, prog_plot=prog_plot) if geo_res: cp.append(geo_res) + pol_nr += 1 + disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) + # log.debug("Polygons cleared: %d" % pol_nr) + + if old_disp_number < disp_number <= 100: + self.app.proc_container.update_view_text(' %d%%' % disp_number) + old_disp_number = disp_number except TypeError: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn, cont=cont, paint_method=paint_method, obj=obj, prog_plot=prog_plot) @@ -2229,12 +2251,11 @@ class ToolPaint(FlatCAMTool, Gerber): if not tools_storage[uid]['solid_geometry']: tools_storage.pop(uid, None) + if not tools_storage: + return 'fail' + def job_init(geo_obj, app_obj): - if not tools_storage: - return 'fail' - 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 @@ -2254,7 +2275,7 @@ class ToolPaint(FlatCAMTool, Gerber): geo_obj.options['xmax'] = c geo_obj.options['ymax'] = d except Exception as ee: - log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(ee)) + log.debug("ToolPaint.paint_poly.job_init() bounds error --> %s" % str(ee)) return # test if at least one tool has solid_geometry. If no tool has solid_geometry we raise an Exception @@ -2273,16 +2294,6 @@ class ToolPaint(FlatCAMTool, Gerber): # Experimental... # print("Indexing...", end=' ') # geo_obj.make_index() - # if errors == 0: - # print("[success] Paint single polygon Done") - # self.app.inform.emit("[success] Paint single polygon Done") - # else: - # print("[WARNING] Paint single polygon done with errors") - # self.app.inform.emit("[WARNING] Paint single polygon done with errors. " - # "%d area(s) could not be painted.\n" - # "Use different paint parameters or edit the paint geometry and correct" - # "the issue." - # % errors) def job_thread(app_obj): try: @@ -2292,7 +2303,8 @@ class ToolPaint(FlatCAMTool, Gerber): return except Exception as er: proc.done() - app_obj.inform.emit('[ERROR_NOTCL] %s --> %s' % ('PaintTool.paint_poly()', str(er))) + app_obj.inform.emit('[ERROR] %s --> %s' % ('PaintTool.paint_poly()', str(er))) + traceback.print_stack() return proc.done() @@ -2321,46 +2333,17 @@ class ToolPaint(FlatCAMTool, Gerber): """ Paints all polygons in this object. + :param obj: painted object + :param tooldia: a tuple or single element made out of diameters of the tools to be used + :param order: if the tools are ordered and how + :param outname: name of the resulting object + :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. :param run_threaded: :param plot: - :param obj: painted object - :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 = method if method is not None else self.paintmethod_combo.get_value() - - # determine if to use the progressive plotting - if self.app.defaults["tools_paint_plotting"] == 'progressive': - prog_plot = True - else: - prog_plot = False - - proc = self.app.proc_container.new(_("Painting polygons...")) - name = outname if outname is not None else self.obj_name + "_paint" - 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: - 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())) # This is a recursive generator of individual Polygons. # Note: Double check correct implementation. Might exit @@ -2410,23 +2393,57 @@ class ToolPaint(FlatCAMTool, Gerber): return self.flat_geometry + if obj.kind == 'gerber': + # I don't do anything here, like buffering when the Gerber is loaded without buffering????!!!! + if self.app.defaults["gerber_buffering"] == 'no': + self.app.inform.emit('%s %s %s' % (_("Paint Tool."), _("Paint all polygons task started."), + _("Buffering geometry..."))) + else: + self.app.inform.emit('%s %s' % (_("Paint Tool."), _("Paint all polygons task started."))) + + if self.app.defaults["tools_paint_plotting"] == 'progressive': + if isinstance(obj.solid_geometry, list): + obj.solid_geometry = MultiPolygon(obj.solid_geometry).buffer(0) + else: + obj.solid_geometry = obj.solid_geometry.buffer(0) + else: + self.app.inform.emit('%s %s' % (_("Paint Tool."), _("Paint all polygons task started."))) + + painted_area = recurse(obj.solid_geometry) + + # No polygon? + if not painted_area: + self.app.log.warning('No polygon found.') + self.app.inform.emit('[WARNING] %s' % _('No polygon found.')) + return + + paint_method = method if method is not None else self.paintmethod_combo.get_value() + # 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" + 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: + 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())) + + proc = self.app.proc_container.new(_("Painting polygons...")) + # 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) - log.debug("Paint Tool. Normal painting all task started.") - if obj.kind == 'gerber': - if app_obj.defaults["gerber_buffering"] == 'no': - app_obj.inform.emit('%s %s' % - (_("Paint Tool. Normal painting all task started."), - _("Buffering geometry..."))) - else: - app_obj.inform.emit(_("Paint Tool. Normal painting all task started.")) - else: - app_obj.inform.emit(_("Paint Tool. Normal painting all task started.")) - tool_dia = None if order == 'fwd': sorted_tools.sort(reverse=False) elif order == 'rev': @@ -2434,38 +2451,16 @@ class ToolPaint(FlatCAMTool, Gerber): else: pass - if obj.kind == 'gerber': - if self.app.defaults["tools_paint_plotting"] == 'progressive': - if isinstance(obj.solid_geometry, list): - obj.solid_geometry = MultiPolygon(obj.solid_geometry).buffer(0) - else: - obj.solid_geometry = obj.solid_geometry.buffer(0) - - 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 - - total_geometry = [] + tool_dia = None current_uid = int(1) old_disp_number = 0 - geo_obj.solid_geometry = [] final_solid_geometry = [] - painted_area = recurse(obj.solid_geometry) - for tool_dia in sorted_tools: log.debug("Starting geometry processing for tool: %s" % str(tool_dia)) app_obj.inform.emit( - '[success] %s %s%s %s' % (_('Painting with tool diameter = '), - str(tool_dia), - self.units.lower(), + '[success] %s %s%s %s' % (_('Painting with tool diameter = '), str(tool_dia), self.units.lower(), _('started')) ) app_obj.proc_container.update_view_text(' %d%%' % 0) @@ -2486,6 +2481,7 @@ class ToolPaint(FlatCAMTool, Gerber): paint_margin = float(tools_storage[current_uid]['data']['tools_paintmargin']) poly_buf = [] for pol in painted_area: + pol = Polygon(pol) if not isinstance(pol, Polygon) else pol buffered_pol = pol.buffer(-paint_margin) if buffered_pol and not buffered_pol.is_empty: poly_buf.append(buffered_pol) @@ -2508,10 +2504,8 @@ class ToolPaint(FlatCAMTool, Gerber): try: cp = [] - geo_res = None try: for pp in poly_buf: - # provide the app with a way to process the GUI events when in a blocking loop QtWidgets.QApplication.processEvents() if self.app.abort_flag: @@ -2526,6 +2520,16 @@ class ToolPaint(FlatCAMTool, Gerber): poly_processed.append(True) else: poly_processed.append(False) + + pol_nr += 1 + disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) + # log.debug("Polygons cleared: %d" % pol_nr) + + if old_disp_number < disp_number <= 100: + app_obj.proc_container.update_view_text(' %d%%' % disp_number) + old_disp_number = disp_number + # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number)) + except TypeError: # provide the app with a way to process the GUI events when in a blocking loop QtWidgets.QApplication.processEvents() @@ -2548,15 +2552,6 @@ class ToolPaint(FlatCAMTool, Gerber): total_geometry += list(x.get_objects()) final_solid_geometry += total_geometry - pol_nr += 1 - disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) - # log.debug("Polygons cleared: %d" % pol_nr) - - if old_disp_number < disp_number <= 100: - app_obj.proc_container.update_view_text(' %d%%' % disp_number) - old_disp_number = disp_number - # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number)) - except Exception as err: log.debug("Could not Paint the polygons. %s" % str(err)) self.app.inform.emit( @@ -2604,11 +2599,27 @@ class ToolPaint(FlatCAMTool, Gerber): geo_obj.solid_geometry = cascaded_union(final_solid_geometry) + try: + # a, b, c, d = obj.bounds() + if isinstance(geo_obj.solid_geometry, list): + a, b, c, d = MultiPolygon(geo_obj.solid_geometry).bounds + else: + a, b, c, d = geo_obj.solid_geometry.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 + # 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" @@ -2624,56 +2635,22 @@ 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) - log.debug("Paint Tool. Rest machining painting all task started.") - if obj.kind == 'gerber': - if app_obj.defaults["gerber_buffering"] == 'no': - app_obj.inform.emit('%s %s %s' % - (_("Paint Tool."), _("Rest machining painting all task started."), - _("Buffering geometry..."))) - else: - app_obj.inform.emit('%s %s' % - (_("Paint Tool."), _("Rest machining painting all task started."))) - else: - app_obj.inform.emit('%s %s' % - (_("Paint Tool."), _("Rest machining painting all task started."))) - tool_dia = None + # when using rest machining use always the reverse order; from bigger tool to smaller one sorted_tools.sort(reverse=True) - if obj.kind == 'gerber': - if self.app.defaults["tools_paint_plotting"] == 'progressive': - if isinstance(obj.solid_geometry, list): - obj.solid_geometry = MultiPolygon(obj.solid_geometry).buffer(0) - else: - obj.solid_geometry = obj.solid_geometry.buffer(0) - - 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 - + tool_dia = None cleared_geo = [] current_uid = int(1) - geo_obj.solid_geometry = [] - final_solid_geometry = [] old_disp_number = 0 - painted_area = recurse(obj.solid_geometry) + final_solid_geometry = [] for tool_dia in sorted_tools: log.debug("Starting geometry processing for tool: %s" % str(tool_dia)) app_obj.inform.emit( - '[success] %s %s%s %s' % (_('Painting with tool diameter = '), - str(tool_dia), - self.units.lower(), + '[success] %s %s%s %s' % (_('Painting with tool diameter = '), str(tool_dia), self.units.lower(), _('started')) ) app_obj.proc_container.update_view_text(' %d%%' % 0) @@ -2710,212 +2687,59 @@ class ToolPaint(FlatCAMTool, Gerber): pol_nr = 0 - for geo in poly_buf: + # ----------------------------- + # effective polygon clearing job + # ----------------------------- + try: + cp = [] try: - cp = None + for pp in poly_buf: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn, + cont=cont, paint_method=paint_method, obj=obj, + prog_plot=prog_plot) + if geo_res: + cp.append(geo_res) + pol_nr += 1 + disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) + # log.debug("Polygons cleared: %d" % pol_nr) - if paint_method == _("Standard"): - # Type(cp) == FlatCAMRTreeStorage | None - cp = self.clear_polygon(geo, tooldia=tool_dia, - steps_per_circle=self.app.defaults["geometry_circle_steps"], - overlap=over, contour=cont, connect=conn, - prog_plot=prog_plot) - elif paint_method == _("Seed"): - # Type(cp) == FlatCAMRTreeStorage | None - cp = self.clear_polygon2(geo, tooldia=tool_dia, - steps_per_circle=self.app.defaults["geometry_circle_steps"], - overlap=over, contour=cont, connect=conn, - prog_plot=prog_plot) - elif paint_method == _("Lines"): - # Type(cp) == FlatCAMRTreeStorage | None - cp = self.clear_polygon3(geo, tooldia=tool_dia, - steps_per_circle=self.app.defaults["geometry_circle_steps"], - overlap=over, contour=cont, connect=conn, - prog_plot=prog_plot) - elif paint_method == _("Laser_lines"): - # line = None - # aperture_size = None + if old_disp_number < disp_number <= 100: + self.app.proc_container.update_view_text(' %d%%' % disp_number) + old_disp_number = disp_number + except TypeError: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException - # the key is the aperture type and the val is a list of geo elements - flash_el_dict = {} - # the key is the aperture size, the val is a list of geo elements - traces_el_dict = {} - - # find the flashes and the lines that are in the selected polygon and store - # them separately - for apid, apval in obj.apertures.items(): - for geo_el in apval['geometry']: - if apval["size"] == 0.0: - if apval["size"] in traces_el_dict: - traces_el_dict[apval["size"]].append(geo_el) - else: - traces_el_dict[apval["size"]] = [geo_el] - - if 'follow' in geo_el and geo_el['follow'].within(geo): - if isinstance(geo_el['follow'], Point): - if apval["type"] == 'C': - if 'C' in flash_el_dict: - flash_el_dict['C'].append(geo_el) - else: - flash_el_dict['C'] = [geo_el] - elif apval["type"] == 'O': - if 'O' in flash_el_dict: - flash_el_dict['O'].append(geo_el) - else: - flash_el_dict['O'] = [geo_el] - elif apval["type"] == 'R': - if 'R' in flash_el_dict: - flash_el_dict['R'].append(geo_el) - else: - flash_el_dict['R'] = [geo_el] - else: - aperture_size = apval['size'] - - if aperture_size in traces_el_dict: - traces_el_dict[aperture_size].append(geo_el) - else: - traces_el_dict[aperture_size] = [geo_el] - - cp = FlatCAMRTreeStorage() - pads_lines_list = [] - - # process the flashes found in the selected polygon with the 'lines' method - # for rectangular flashes and with _("Seed") for oblong and circular flashes - # and pads (flahes) need the contour therefore I override the GUI settings - # with always True - for ap_type in flash_el_dict: - for elem in flash_el_dict[ap_type]: - if 'solid' in elem: - if ap_type == 'C': - f_o = self.clear_polygon2(elem['solid'], - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=True, - connect=conn, - prog_plot=prog_plot) - pads_lines_list += [p for p in f_o.get_objects() if p] - - elif ap_type == 'O': - f_o = self.clear_polygon2(elem['solid'], - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=True, - connect=conn, - prog_plot=prog_plot) - pads_lines_list += [p for p in f_o.get_objects() if p] - - elif ap_type == 'R': - f_o = self.clear_polygon3(elem['solid'], - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=True, - connect=conn, - prog_plot=prog_plot) - - pads_lines_list += [p for p in f_o.get_objects() if p] - - # add the lines from pads to the storage - try: - for lin in pads_lines_list: - if lin: - cp.insert(lin) - except TypeError: - cp.insert(pads_lines_list) - - copper_lines_list = [] - # process the traces found in the selected polygon using the 'laser_lines' - # method, method which will follow the 'follow' line therefore use the longer - # path possible for the laser, therefore the acceleration will play - # a smaller factor - for aperture_size in traces_el_dict: - for elem in traces_el_dict[aperture_size]: - line = elem['follow'] - if line: - t_o = self.fill_with_lines(line, aperture_size, - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) - - copper_lines_list += [p for p in t_o.get_objects() if p] - - # add the lines from copper features to storage but first try to make as few - # lines as possible - # by trying to fuse them - lines_union = linemerge(unary_union(copper_lines_list)) - try: - for lin in lines_union: - if lin: - cp.insert(lin) - except TypeError: - cp.insert(lines_union) - elif paint_method == _("Combo"): - self.app.inform.emit(_("Painting polygons with method: lines.")) - cp = self.clear_polygon3(geo, - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) - - if cp and cp.objects: - pass - else: - self.app.inform.emit(_("Failed. Painting polygons with method: seed.")) - cp = self.clear_polygon2(geo, - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) - if cp and cp.objects: - pass - else: - self.app.inform.emit(_("Failed. Painting polygons with method: standard.")) - cp = self.clear_polygon(geo, - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, + geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn, + cont=cont, paint_method=paint_method, obj=obj, prog_plot=prog_plot) + if geo_res: + cp.append(geo_res) - if cp is not None: - cleared_geo += list(cp.get_objects()) - except FlatCAMApp.GracefulException: - return "fail" - except Exception as e: - log.debug("Could not Paint the polygons. %s" % str(e)) - self.app.inform.emit('[ERROR] %s\n%s' % - (_("Could not do Paint All. Try a different combination of parameters. " - "Or a different Method of paint"), - str(e))) - return "fail" + if cp: + for x in cp: + cleared_geo += list(x.get_objects()) + final_solid_geometry += cleared_geo + except FlatCAMApp.GracefulException: + return "fail" + except Exception as e: + log.debug("Could not Paint the polygons. %s" % str(e)) + self.app.inform.emit( + '[ERROR] %s\n%s' % + (_("Could not do Paint. Try a different combination of parameters. " + "Or a different strategy of paint"), str(e) + ) + ) + continue - pol_nr += 1 - disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) - # log.debug("Polygons cleared: %d" % pol_nr) - - if old_disp_number < disp_number <= 100: - app_obj.proc_container.update_view_text(' %d%%' % disp_number) - old_disp_number = disp_number - # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number)) - - final_solid_geometry += 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) @@ -2945,6 +2769,21 @@ class ToolPaint(FlatCAMTool, Gerber): geo_obj.solid_geometry = cascaded_union(final_solid_geometry) + try: + # a, b, c, d = obj.bounds() + if isinstance(geo_obj.solid_geometry, list): + a, b, c, d = MultiPolygon(geo_obj.solid_geometry).bounds + else: + a, b, c, d = geo_obj.solid_geometry.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_rest_machining() bounds error --> %s" % str(e)) + return + # 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: @@ -2966,19 +2805,27 @@ class ToolPaint(FlatCAMTool, Gerber): def job_thread(app_obj): try: if self.rest_cb.isChecked(): - app_obj.new_object("geometry", name, gen_paintarea_rest_machining, plot=plot) + ret = app_obj.new_object("geometry", name, gen_paintarea_rest_machining, plot=plot) else: - app_obj.new_object("geometry", name, gen_paintarea, plot=plot) + ret = app_obj.new_object("geometry", name, gen_paintarea, plot=plot) except FlatCAMApp.GracefulException: proc.done() return - except Exception: + except Exception as err: proc.done() + app_obj.inform.emit('[ERROR] %s --> %s' % ('PaintTool.paint_poly_all()', str(err))) traceback.print_stack() return proc.done() + + if ret == 'fail': + self.app.inform.emit('[ERROR] %s' % _("Paint All failed.")) + return + # focus on Selected Tab - self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab) + # self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab) + + self.app.inform.emit('[success] %s' % _("Paint Poly All Done.")) self.app.inform.emit(_("Polygon Paint started ...")) @@ -2991,52 +2838,23 @@ class ToolPaint(FlatCAMTool, Gerber): else: job_thread(app_obj=self.app) - def paint_poly_area(self, obj, sel_obj, tooldia=None, order=None, method=None, - outname=None, tools_storage=None, plot=True, run_threaded=True): + def paint_poly_area(self, obj, sel_obj, tooldia=None, order=None, method=None, outname=None, + tools_storage=None, plot=True, run_threaded=True): """ Paints all polygons in this object that are within the sel_obj object - :param run_threaded: - :param plot: :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. + :param run_threaded: + :param plot: :return: """ - paint_method = method if method is not None else self.paintmethod_combo.get_value() - - # determine if to use the progressive plotting - if self.app.defaults["tools_paint_plotting"] == 'progressive': - prog_plot = True - else: - prog_plot = False - - proc = self.app.proc_container.new(_("Painting polygons...")) - name = outname if outname is not None else self.obj_name + "_paint" - 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: - 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())) def recurse(geometry, reset=True): """ @@ -3072,23 +2890,58 @@ class ToolPaint(FlatCAMTool, Gerber): return self.flat_geometry + # this is were heavy lifting is done and creating the geometry to be painted + target_geo = MultiPolygon(obj.solid_geometry) + if obj.kind == 'gerber': + # I don't do anything here, like buffering when the Gerber is loaded without buffering????!!!! + if self.app.defaults["gerber_buffering"] == 'no': + self.app.inform.emit('%s %s %s' % (_("Paint Tool."), _("Painting area task started."), + _("Buffering geometry..."))) + else: + self.app.inform.emit('%s %s' % (_("Paint Tool."), _("Painting area task started."))) + + if obj.kind == 'gerber': + if self.app.defaults["tools_paint_plotting"] == 'progressive': + target_geo = target_geo.buffer(0) + else: + self.app.inform.emit('%s %s' % (_("Paint Tool."), _("Painting area task started."))) + + geo_to_paint = target_geo.intersection(sel_obj) + painted_area = recurse(geo_to_paint) + + # No polygon? + if not painted_area: + self.app.log.warning('No polygon found.') + self.app.inform.emit('[WARNING] %s' % _('No polygon found.')) + return + + paint_method = method if method is not None else self.paintmethod_combo.get_value() + # 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" + 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: + 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())) + + proc = self.app.proc_container.new(_("Painting polygons...")) + # 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) - log.debug("Paint Tool. Normal painting area task started.") - if obj.kind == 'gerber': - if app_obj.defaults["gerber_buffering"] == 'no': - app_obj.inform.emit('%s %s' % - (_("Paint Tool. Normal painting area task started."), - _("Buffering geometry..."))) - else: - app_obj.inform.emit(_("Paint Tool. Normal painting area task started.")) - else: - app_obj.inform.emit(_("Paint Tool. Normal painting area task started.")) - tool_dia = None if order == 'fwd': sorted_tools.sort(reverse=False) elif order == 'rev': @@ -3096,42 +2949,16 @@ class ToolPaint(FlatCAMTool, Gerber): else: pass - # this is were heavy lifting is done and creating the geometry to be painted - target_geo = MultiPolygon(obj.solid_geometry) - - if obj.kind == 'gerber': - if self.app.defaults["tools_paint_plotting"] == 'progressive': - if isinstance(target_geo, list): - target_geo = MultiPolygon(target_geo).buffer(0) - else: - target_geo = target_geo.buffer(0) - - geo_to_paint = target_geo.intersection(sel_obj) - painted_area = recurse(geo_to_paint) - - 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 = [] + tool_dia = None current_uid = int(1) - - geo_obj.solid_geometry = [] - final_solid_geometry = [] old_disp_number = 0 + final_solid_geometry = [] + for tool_dia in sorted_tools: log.debug("Starting geometry processing for tool: %s" % str(tool_dia)) app_obj.inform.emit( - '[success] %s %s%s %s' % (_('Painting with tool diameter = '), - str(tool_dia), - self.units.lower(), + '[success] %s %s%s %s' % (_('Painting with tool diameter = '), str(tool_dia), self.units.lower(), _('started')) ) app_obj.proc_container.update_view_text(' %d%%' % 0) @@ -3141,7 +2968,6 @@ class ToolPaint(FlatCAMTool, Gerber): if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool_dia)): current_uid = int(k) break - if not current_uid: return "fail" @@ -3153,6 +2979,7 @@ class ToolPaint(FlatCAMTool, Gerber): paint_margin = float(tools_storage[current_uid]['data']['tools_paintmargin']) poly_buf = [] for pol in painted_area: + pol = Polygon(pol) if not isinstance(pol, Polygon) else pol buffered_pol = pol.buffer(-paint_margin) if buffered_pol and not buffered_pol.is_empty: poly_buf.append(buffered_pol) @@ -3168,226 +2995,73 @@ class ToolPaint(FlatCAMTool, Gerber): pol_nr = 0 - for geo in poly_buf: + # ----------------------------- + # effective polygon clearing job + # ----------------------------- + poly_processed = [] + total_geometry = [] + + try: try: - # Polygons are the only really paintable geometries, lines in theory have no area to be painted - if not isinstance(geo, Polygon): - continue + for pp in poly_buf: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException - cp = None - if paint_method == _("Seed"): - # Type(cp) == FlatCAMRTreeStorage | None - cp = self.clear_polygon2(geo, - tooldia=tool_dia, - steps_per_circle=self.app.defaults["geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) - - elif paint_method == _("Lines"): - # Type(cp) == FlatCAMRTreeStorage | None - cp = self.clear_polygon3(geo, - tooldia=tool_dia, - steps_per_circle=self.app.defaults["geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) - - elif paint_method == _("Standard"): - # Type(cp) == FlatCAMRTreeStorage | None - cp = self.clear_polygon(geo, - tooldia=tool_dia, - steps_per_circle=self.app.defaults["geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) - elif paint_method == _("Laser_lines"): - # line = None - # aperture_size = None - - # the key is the aperture type and the val is a list of geo elements - flash_el_dict = {} - # the key is the aperture size, the val is a list of geo elements - traces_el_dict = {} - - # find the flashes and the lines that are in the selected polygon and store - # them separately - for apid, apval in obj.apertures.items(): - for geo_el in apval['geometry']: - if apval["size"] == 0.0: - if apval["size"] in traces_el_dict: - traces_el_dict[apval["size"]].append(geo_el) - else: - traces_el_dict[apval["size"]] = [geo_el] - - if 'follow' in geo_el and geo_el['follow'].within(geo): - if isinstance(geo_el['follow'], Point): - if apval["type"] == 'C': - if 'C' in flash_el_dict: - flash_el_dict['C'].append(geo_el) - else: - flash_el_dict['C'] = [geo_el] - elif apval["type"] == 'O': - if 'O' in flash_el_dict: - flash_el_dict['O'].append(geo_el) - else: - flash_el_dict['O'] = [geo_el] - elif apval["type"] == 'R': - if 'R' in flash_el_dict: - flash_el_dict['R'].append(geo_el) - else: - flash_el_dict['R'] = [geo_el] - else: - aperture_size = apval['size'] - - if aperture_size in traces_el_dict: - traces_el_dict[aperture_size].append(geo_el) - else: - traces_el_dict[aperture_size] = [geo_el] - - cp = FlatCAMRTreeStorage() - pads_lines_list = [] - - # process the flashes found in the selected polygon with the 'lines' method - # for rectangular flashes and with _("Seed") for oblong and circular flashes - # and pads (flahes) need the contour therefore I override the GUI settings - # with always True - for ap_type in flash_el_dict: - for elem in flash_el_dict[ap_type]: - if 'solid' in elem: - if ap_type == 'C': - f_o = self.clear_polygon2(elem['solid'], - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=True, - connect=conn, - prog_plot=prog_plot) - pads_lines_list += [p for p in f_o.get_objects() if p] - - elif ap_type == 'O': - f_o = self.clear_polygon2(elem['solid'], - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=True, - connect=conn, - prog_plot=prog_plot) - pads_lines_list += [p for p in f_o.get_objects() if p] - - elif ap_type == 'R': - f_o = self.clear_polygon3(elem['solid'], - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=True, - connect=conn, - prog_plot=prog_plot) - - pads_lines_list += [p for p in f_o.get_objects() if p] - - # add the lines from pads to the storage - try: - for lin in pads_lines_list: - if lin: - cp.insert(lin) - except TypeError: - cp.insert(pads_lines_list) - - copper_lines_list = [] - # process the traces found in the selected polygon using the 'laser_lines' - # method, method which will follow the 'follow' line therefore use the longer - # path possible for the laser, therefore the acceleration will play - # a smaller factor - for aperture_size in traces_el_dict: - for elem in traces_el_dict[aperture_size]: - line = elem['follow'] - if line: - t_o = self.fill_with_lines(line, aperture_size, - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) - - copper_lines_list += [p for p in t_o.get_objects() if p] - - # add the lines from copper features to storage but first try to make as few - # lines as possible - # by trying to fuse them - lines_union = linemerge(unary_union(copper_lines_list)) - try: - for lin in lines_union: - if lin: - cp.insert(lin) - except TypeError: - cp.insert(lines_union) - elif paint_method == _("Combo"): - self.app.inform.emit(_("Painting polygons with method: lines.")) - cp = self.clear_polygon3(geo, - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) - - if cp and cp.objects: - pass + geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn, + cont=cont, paint_method=paint_method, obj=obj, + prog_plot=prog_plot) + if geo_res and geo_res.objects: + total_geometry += list(geo_res.get_objects()) + poly_processed.append(True) else: - self.app.inform.emit(_("Failed. Painting polygons with method: seed.")) - cp = self.clear_polygon2(geo, - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) - if cp and cp.objects: - pass - else: - self.app.inform.emit(_("Failed. Painting polygons with method: standard.")) - cp = self.clear_polygon(geo, - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, + poly_processed.append(False) + + pol_nr += 1 + disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) + # log.debug("Polygons cleared: %d" % pol_nr) + + if old_disp_number < disp_number <= 100: + app_obj.proc_container.update_view_text(' %d%%' % disp_number) + old_disp_number = disp_number + # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number)) + + except TypeError: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn, + cont=cont, paint_method=paint_method, obj=obj, prog_plot=prog_plot) - if cp and cp.objects: - total_geometry += list(cp.get_objects()) - final_solid_geometry += total_geometry - except FlatCAMApp.GracefulException: - return "fail" - except Exception as err: - log.debug("Could not Paint the polygons. %s" % str(err)) - self.app.inform.emit( - '[ERROR] %s\n%s' % - (_("Could not do Paint. Try a different combination of parameters. " - "Or a different strategy of paint"), str(err) - ) - ) - continue + if geo_res and geo_res.objects: + total_geometry += list(geo_res.get_objects()) + poly_processed.append(True) + else: + poly_processed.append(False) - pol_nr += 1 - disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) - # log.debug("Polygons cleared: %d" % pol_nr) + except Exception as err: + log.debug("Could not Paint the polygons. %s" % str(err)) + self.app.inform.emit( + '[ERROR] %s\n%s' % + (_("Could not do Paint. Try a different combination of parameters. " + "Or a different strategy of paint"), str(err) + ) + ) + continue - if old_disp_number < disp_number <= 100: - app_obj.proc_container.update_view_text(' %d%%' % disp_number) - old_disp_number = disp_number - # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number)) + p_cleared = poly_processed.count(True) + p_not_cleared = poly_processed.count(False) + + if p_not_cleared: + app_obj.poly_not_cleared = True + + if p_cleared == 0: + continue # 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 @@ -3418,6 +3092,16 @@ class ToolPaint(FlatCAMTool, Gerber): geo_obj.solid_geometry = cascaded_union(final_solid_geometry) + 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 + # 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: @@ -3428,7 +3112,7 @@ class ToolPaint(FlatCAMTool, Gerber): _("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 + return "fail" # Experimental... # print("Indexing...", end=' ') @@ -3438,60 +3122,22 @@ 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) - log.debug("Paint Tool. Rest machining painting area task started.") - if obj.kind == 'gerber': - if app_obj.defaults["gerber_buffering"] == 'no': - app_obj.inform.emit('%s %s %s' % - (_("Paint Tool."), _("Rest machining painting area task started."), - _("Buffering geometry..."))) - else: - app_obj.inform.emit('%s %s' % - (_("Paint Tool."), _("Rest machining painting area task started."))) - else: - app_obj.inform.emit('%s %s' % - (_("Paint Tool."), _("Rest machining painting area task started."))) - tool_dia = None sorted_tools.sort(reverse=True) cleared_geo = [] + + tool_dia = None current_uid = int(1) - geo_obj.solid_geometry = [] - final_solid_geometry = [] old_disp_number = 0 - # this is were heavy lifting is done and creating the geometry to be painted - target_geo = obj.solid_geometry - - if obj.kind == 'gerber': - if self.app.defaults["tools_paint_plotting"] == 'progressive': - if isinstance(target_geo, list): - target_geo = MultiPolygon(target_geo).buffer(0) - else: - target_geo = target_geo.buffer(0) - - geo_to_paint = target_geo.intersection(sel_obj) - painted_area = recurse(geo_to_paint) - - 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 + final_solid_geometry = [] for tool_dia in sorted_tools: log.debug("Starting geometry processing for tool: %s" % str(tool_dia)) app_obj.inform.emit( - '[success] %s %s%s %s' % (_('Painting with tool diameter = '), - str(tool_dia), - self.units.lower(), + '[success] %s %s%s %s' % (_('Painting with tool diameter = '), str(tool_dia), self.units.lower(), _('started')) ) app_obj.proc_container.update_view_text(' %d%%' % 0) @@ -3501,12 +3147,9 @@ class ToolPaint(FlatCAMTool, Gerber): if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool_dia)): current_uid = int(k) break - if not current_uid: return "fail" - painted_area = recurse(obj.solid_geometry) - # determine the tool parameters to use over = float(tools_storage[current_uid]['data']['tools_paintoverlap']) / 100.0 conn = tools_storage[current_uid]['data']['tools_pathconnect'] @@ -3531,207 +3174,72 @@ class ToolPaint(FlatCAMTool, Gerber): pol_nr = 0 - for geo in poly_buf: + # ----------------------------- + # effective polygon clearing job + # ----------------------------- + poly_processed = [] + + try: try: - cp = None + for pp in poly_buf: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException - if paint_method == _("Standard"): - # Type(cp) == FlatCAMRTreeStorage | None - cp = self.clear_polygon(geo, tooldia=tool_dia, - steps_per_circle=self.app.defaults["geometry_circle_steps"], - overlap=over, contour=cont, connect=conn, - prog_plot=prog_plot) - elif paint_method == _("Seed"): - # Type(cp) == FlatCAMRTreeStorage | None - cp = self.clear_polygon2(geo, tooldia=tool_dia, - steps_per_circle=self.app.defaults["geometry_circle_steps"], - overlap=over, contour=cont, connect=conn, - prog_plot=prog_plot) - elif paint_method == _("Lines"): - # Type(cp) == FlatCAMRTreeStorage | None - cp = self.clear_polygon3(geo, tooldia=tool_dia, - steps_per_circle=self.app.defaults["geometry_circle_steps"], - overlap=over, contour=cont, connect=conn, - prog_plot=prog_plot) - elif paint_method == _("Laser_lines"): - # line = None - # aperture_size = None - - # the key is the aperture type and the val is a list of geo elements - flash_el_dict = {} - # the key is the aperture size, the val is a list of geo elements - copper_el_dict = {} - - # find the flashes and the lines that are in the selected polygon and store - # them separately - for apid, apval in obj.apertures.items(): - for geo_el in apval['geometry']: - if apval["size"] == 0.0: - if apval["size"] in copper_el_dict: - copper_el_dict[apval["size"]].append(geo_el) - else: - copper_el_dict[apval["size"]] = [geo_el] - - if 'follow' in geo_el and geo_el['follow'].within(geo): - if isinstance(geo_el['follow'], Point): - if apval["type"] == 'C': - if 'C' in flash_el_dict: - flash_el_dict['C'].append(geo_el) - else: - flash_el_dict['C'] = [geo_el] - elif apval["type"] == 'O': - if 'O' in flash_el_dict: - flash_el_dict['O'].append(geo_el) - else: - flash_el_dict['O'] = [geo_el] - elif apval["type"] == 'R': - if 'R' in flash_el_dict: - flash_el_dict['R'].append(geo_el) - else: - flash_el_dict['R'] = [geo_el] - else: - aperture_size = apval['size'] - - if aperture_size in copper_el_dict: - copper_el_dict[aperture_size].append(geo_el) - else: - copper_el_dict[aperture_size] = [geo_el] - - cp = FlatCAMRTreeStorage() - pads_lines_list = [] - - # process the flashes found in the selected polygon with the 'lines' method - # for rectangular flashes and with _("Seed") for oblong and circular flashes - # and pads (flahes) need the contour therefore I override the GUI settings - # with always True - for ap_type in flash_el_dict: - for elem in flash_el_dict[ap_type]: - if 'solid' in elem: - if ap_type == 'C': - f_o = self.clear_polygon2(elem['solid'], - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=True, - connect=conn, - prog_plot=prog_plot) - pads_lines_list += [p for p in f_o.get_objects() if p] - - elif ap_type == 'O': - f_o = self.clear_polygon2(elem['solid'], - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=True, - connect=conn, - prog_plot=prog_plot) - pads_lines_list += [p for p in f_o.get_objects() if p] - - elif ap_type == 'R': - f_o = self.clear_polygon3(elem['solid'], - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=True, - connect=conn, - prog_plot=prog_plot) - - pads_lines_list += [p for p in f_o.get_objects() if p] - - # add the lines from pads to the storage - try: - for lin in pads_lines_list: - if lin: - cp.insert(lin) - except TypeError: - cp.insert(pads_lines_list) - - copper_lines_list = [] - # process the traces found in the selected polygon using the 'laser_lines' - # method, method which will follow the 'follow' line therefore use the longer - # path possible for the laser, therefore the acceleration will play - # a smaller factor - for aperture_size in copper_el_dict: - for elem in copper_el_dict[aperture_size]: - line = elem['follow'] - if line: - t_o = self.fill_with_lines(line, aperture_size, - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) - - copper_lines_list += [p for p in t_o.get_objects() if p] - - # add the lines from copper features to storage but first try to make as few - # lines as possible - # by trying to fuse them - lines_union = linemerge(unary_union(copper_lines_list)) - try: - for lin in lines_union: - if lin: - cp.insert(lin) - except TypeError: - cp.insert(lines_union) - elif paint_method == _("Combo"): - self.app.inform.emit(_("Painting polygons with method: lines.")) - cp = self.clear_polygon3(geo, - tooldia=tool_dia, - steps_per_circle=self.app.defaults["geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) - - if cp and cp.objects: - pass + geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn, + cont=cont, paint_method=paint_method, obj=obj, + prog_plot=prog_plot) + if geo_res and geo_res.objects: + cleared_geo += list(geo_res.get_objects()) + poly_processed.append(True) else: - self.app.inform.emit(_("Failed. Painting polygons with method: seed.")) - cp = self.clear_polygon2(geo, - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, - prog_plot=prog_plot) - if cp and cp.objects: - pass - else: - self.app.inform.emit(_("Failed. Painting polygons with method: standard.")) - cp = self.clear_polygon(geo, - tooldia=tool_dia, - steps_per_circle=self.app.defaults[ - "geometry_circle_steps"], - overlap=over, - contour=cont, - connect=conn, + poly_processed.append(False) + + pol_nr += 1 + disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) + # log.debug("Polygons cleared: %d" % pol_nr) + + if old_disp_number < disp_number <= 100: + app_obj.proc_container.update_view_text(' %d%%' % disp_number) + old_disp_number = disp_number + # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number)) + + except TypeError: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn, + cont=cont, paint_method=paint_method, obj=obj, prog_plot=prog_plot) - if cp and cp.objects: - cleared_geo += list(cp.get_objects()) - except FlatCAMApp.GracefulException: - return "fail" - except Exception as e: - log.debug("Could not Paint the polygons. %s" % str(e)) - self.app.inform.emit('[ERROR] %s\n%s' % - (_("Could not do Paint All. Try a different combination of parameters. " - "Or a different Method of paint"), str(e))) - return + if geo_res and geo_res.objects: + cleared_geo += list(geo_res.get_objects()) + poly_processed.append(True) + else: + poly_processed.append(False) - pol_nr += 1 - disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) - # log.debug("Polygons cleared: %d" % pol_nr) + except Exception as err: + log.debug("Could not Paint the polygons. %s" % str(err)) + self.app.inform.emit( + '[ERROR] %s\n%s' % + (_("Could not do Paint. Try a different combination of parameters. " + "Or a different strategy of paint"), str(err) + ) + ) + continue - if old_disp_number < disp_number <= 100: - app_obj.proc_container.update_view_text(' %d%%' % disp_number) - old_disp_number = disp_number - # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number)) + p_cleared = poly_processed.count(True) + p_not_cleared = poly_processed.count(False) + + if p_not_cleared: + app_obj.poly_not_cleared = True + + if p_cleared == 0: + continue final_solid_geometry += cleared_geo # add the solid_geometry to the current too in self.paint_tools (or tools_storage) dictionary and @@ -3761,6 +3269,18 @@ class ToolPaint(FlatCAMTool, Gerber): geo_obj.tools.clear() geo_obj.tools = dict(tools_storage) + geo_obj.solid_geometry = cascaded_union(final_solid_geometry) + + 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_rest_machining() bounds error --> %s" % str(e)) + return + # 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: @@ -3782,19 +3302,27 @@ class ToolPaint(FlatCAMTool, Gerber): def job_thread(app_obj): try: if self.rest_cb.isChecked(): - app_obj.new_object("geometry", name, gen_paintarea_rest_machining, plot=plot) + ret = app_obj.new_object("geometry", name, gen_paintarea_rest_machining, plot=plot) else: - app_obj.new_object("geometry", name, gen_paintarea, plot=plot) + ret = app_obj.new_object("geometry", name, gen_paintarea, plot=plot) except FlatCAMApp.GracefulException: proc.done() return - except Exception: + except Exception as err: proc.done() + app_obj.inform.emit('[ERROR] %s --> %s' % ('PaintTool.paint_poly_area()', str(err))) traceback.print_stack() return proc.done() + + if ret == 'fail': + self.app.inform.emit('[ERROR] %s' % _("Paint Area failed.")) + return + # focus on Selected Tab - self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab) + # self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab) + + self.app.inform.emit('[success] %s' % _("Paint Poly Area Done.")) self.app.inform.emit(_("Polygon Paint started ...")) @@ -3807,35 +3335,21 @@ class ToolPaint(FlatCAMTool, Gerber): else: job_thread(app_obj=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, - plot=True, - run_threaded=True): + def paint_poly_ref(self, obj, sel_obj, tooldia=None, order=None, method=None, outname=None, + tools_storage=None, plot=True, run_threaded=True): """ Paints all polygons in this object that are within the sel_obj object - :param run_threaded: - :param plot: :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. + :param run_threaded: + :param plot: :return: """ geo = sel_obj.solid_geometry @@ -3850,20 +3364,16 @@ class ToolPaint(FlatCAMTool, Gerber): 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)) + log.debug("ToolPaint.paint_poly_ref() --> %s" % str(e)) self.app.inform.emit('[ERROR_NOTCL] %s' % _("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, plot=plot, run_threaded=run_threaded) diff --git a/tclCommands/TclCommandCopperClear.py b/tclCommands/TclCommandCopperClear.py index 29c73cd5..b82efdfd 100644 --- a/tclCommands/TclCommandCopperClear.py +++ b/tclCommands/TclCommandCopperClear.py @@ -122,8 +122,21 @@ class TclCommandCopperClear(TclCommand): if 'method' in args: method = args['method'] + if method == "standard": + method_data = _("Standard") + elif method == "seed": + method_data = _("Seed") + else: + method_data = _("Lines") else: method = str(self.app.defaults["tools_nccmethod"]) + method_data = method + if method == _("Standard"): + method = "standard" + elif method == _("Seed"): + method = "seed" + else: + method = "lines" if 'connect' in args: try: @@ -155,11 +168,34 @@ class TclCommandCopperClear(TclCommand): except AttributeError: tools = [float(tooldia)] + if 'rest' in args: + try: + par = args['rest'].capitalize() + except AttributeError: + par = args['rest'] + rest = bool(eval(par)) + else: + rest = bool(eval(str(self.app.defaults["tools_nccrest"]))) + + if 'outname' in args: + outname = args['outname'] + else: + if rest is True: + outname = name + "_ncc" + else: + outname = name + "_ncc_rm" + + # used only to have correct information's in the obj.tools[tool]['data'] dict + if "all" in args: + select = _("Itself") + else: + select = _("Reference Object") + # store here the default data for Geometry Data default_data = {} default_data.update({ - "name": '_paint', - "plot": self.app.defaults["geometry_plot"], + "name": outname, + "plot": False, "cutz": self.app.defaults["geometry_cutz"], "vtipdia": 0.1, "vtipangle": 30, @@ -183,12 +219,12 @@ class TclCommandCopperClear(TclCommand): "startz": self.app.defaults["geometry_startz"], "tooldia": self.app.defaults["tools_painttooldia"], - "paintmargin": self.app.defaults["tools_paintmargin"], - "paintmethod": self.app.defaults["tools_paintmethod"], - "selectmethod": self.app.defaults["tools_selectmethod"], - "pathconnect": self.app.defaults["tools_pathconnect"], - "paintcontour": self.app.defaults["tools_paintcontour"], - "paintoverlap": self.app.defaults["tools_paintoverlap"] + "tools_nccmargin": margin, + "tools_nccmethod": method_data, + "tools_nccref": select, + "tools_nccconnect": connect, + "tools_ncccontour": contour, + "tools_nccoverlap": overlap }) ncc_tools = {} @@ -206,23 +242,7 @@ class TclCommandCopperClear(TclCommand): 'solid_geometry': [] } }) - - if 'rest' in args: - try: - par = args['rest'].capitalize() - except AttributeError: - par = args['rest'] - rest = bool(eval(par)) - else: - rest = bool(eval(str(self.app.defaults["tools_nccrest"]))) - - if 'outname' in args: - outname = args['outname'] - else: - if rest is True: - outname = name + "_ncc" - else: - outname = name + "_ncc_rm" + ncc_tools[int(tooluid)]['data']['tooldia'] = float('%.*f' % (obj.decimals, tool)) # Non-Copper clear all polygons in the non-copper clear object if 'all' in args: diff --git a/tclCommands/TclCommandPaint.py b/tclCommands/TclCommandPaint.py index 7e54e1c6..6b8a85ba 100644 --- a/tclCommands/TclCommandPaint.py +++ b/tclCommands/TclCommandPaint.py @@ -66,7 +66,7 @@ class TclCommandPaint(TclCommand): '"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".'), + ('method', 'Algorithm for painting. Can be: "standard", "seed", "lines", "laser_lines", "combo".'), ('connect', 'Draw lines to minimize tool lifts. True (1) or False (0)'), ('contour', 'Cut around the perimeter of the painting. True (1) or False (0)'), ('all', 'If used, paint all polygons in the object.'), @@ -121,6 +121,16 @@ class TclCommandPaint(TclCommand): if 'method' in args: method = args['method'] + if method == "standard": + method = _("Standard") + elif method == "seed": + method = _("Seed") + elif method == "lines": + method = _("Lines") + elif method == "laser_lines": + method = _("Laser_lines") + else: + method = _("Combo") else: method = str(self.app.defaults["tools_paintmethod"]) @@ -147,6 +157,14 @@ class TclCommandPaint(TclCommand): else: outname = name + "_paint" + # used only to have correct information's in the obj.tools[tool]['data'] dict + if "all" in args: + select = _("All Polygons") + elif "single" in args: + select = _("Polygon Selection") + else: + select = _("Reference Object") + try: tools = [float(eval(dia)) for dia in tooldia.split(",") if dia != ''] except AttributeError: @@ -154,8 +172,8 @@ class TclCommandPaint(TclCommand): # store here the default data for Geometry Data default_data = {} default_data.update({ - "name": '_paint', - "plot": self.app.defaults["geometry_plot"], + "name": outname, + "plot": False, "cutz": self.app.defaults["geometry_cutz"], "vtipdia": 0.1, "vtipangle": 30, @@ -180,12 +198,12 @@ class TclCommandPaint(TclCommand): "startz": self.app.defaults["geometry_startz"], "tooldia": self.app.defaults["tools_painttooldia"], - "paintmargin": self.app.defaults["tools_paintmargin"], - "paintmethod": self.app.defaults["tools_paintmethod"], - "selectmethod": self.app.defaults["tools_selectmethod"], - "pathconnect": self.app.defaults["tools_pathconnect"], - "paintcontour": self.app.defaults["tools_paintcontour"], - "paintoverlap": self.app.defaults["tools_paintoverlap"] + "paintmargin": margin, + "paintmethod": method, + "selectmethod": select, + "pathconnect": connect, + "paintcontour": contour, + "paintoverlap": overlap }) paint_tools = {} @@ -203,6 +221,7 @@ class TclCommandPaint(TclCommand): 'solid_geometry': [] } }) + paint_tools[int(tooluid)]['data']['tooldia'] = float('%.*f' % (obj.decimals, tool)) if obj is None: return "Object not found: %s" % name @@ -211,13 +230,9 @@ class TclCommandPaint(TclCommand): if 'all' in args: 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, plot=False, run_threaded=False) @@ -234,13 +249,9 @@ class TclCommandPaint(TclCommand): 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, plot=False, run_threaded=False) @@ -264,13 +275,9 @@ class TclCommandPaint(TclCommand): 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, plot=False, run_threaded=False)