From 1a131eb71e534a917dd4b2834d6ab3211f693c9c Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 12 Nov 2020 16:22:50 +0200 Subject: [PATCH] - some fixes in the app_Main class - removed the "follow" functionality from the Isolation Tool - created a new application Tool named Follow Tool - added the "follow" functionality in the Follow Tool and added the new feature of allowing to perform "follow" on an area selection --- CHANGELOG.md | 7 + appDatabase.py | 22 - appGUI/MainGUI.py | 4 + appGUI/ObjectUI.py | 17 +- appGUI/preferences/PreferencesUIManager.py | 1 - .../preferences/tools/ToolsISOPrefGroupUI.py | 17 - appObjects/AppObject.py | 6 +- appObjects/FlatCAMGeometry.py | 5 +- appObjects/FlatCAMGerber.py | 1 + appParsers/ParseHPGL2.py | 1 - appTools/ToolFollow.py | 712 ++++++++++++++++++ appTools/ToolIsolation.py | 115 +-- appTools/ToolPaint.py | 3 +- appTools/__init__.py | 1 + app_Main.py | 41 +- camlib.py | 55 +- defaults.py | 1 - 17 files changed, 814 insertions(+), 195 deletions(-) create mode 100644 appTools/ToolFollow.py diff --git a/CHANGELOG.md b/CHANGELOG.md index fec46966..2bd7f594 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ CHANGELOG for FlatCAM beta ================================================= +12.11.2020 + +- some fixes in the app_Main class +- removed the "follow" functionality from the Isolation Tool +- created a new application Tool named Follow Tool +- added the "follow" functionality in the Follow Tool and added the new feature of allowing to perform "follow" on an area selection + 11.11.2020 - removed the forcing of multigeo geometry usage when creating CNCJobs diff --git a/appDatabase.py b/appDatabase.py index 38f1e182..3f285d94 100644 --- a/appDatabase.py +++ b/appDatabase.py @@ -869,23 +869,6 @@ class ToolsDB2UI: self.grid4.addWidget(self.iso_milling_type_label, 4, 0) self.grid4.addWidget(self.iso_milling_type_radio, 4, 1) - # Follow - self.follow_label = FCLabel('%s:' % _('Follow')) - self.follow_label.setToolTip( - _("Generate a 'Follow' geometry.\n" - "This means that it will cut through\n" - "the middle of the trace.") - ) - - self.iso_follow_cb = FCCheckBox() - self.iso_follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n" - "This means that it will cut through\n" - "the middle of the trace.")) - self.iso_follow_cb.setObjectName("gdb_i_follow") - - self.grid4.addWidget(self.follow_label, 6, 0) - self.grid4.addWidget(self.iso_follow_cb, 6, 1) - # Isolation Type self.iso_type_label = FCLabel('%s:' % _('Isolation Type')) self.iso_type_label.setToolTip( @@ -1493,7 +1476,6 @@ class ToolsDB2(QtWidgets.QWidget): "tools_iso_passes": self.ui.iso_passes_entry, "tools_iso_overlap": self.ui.iso_overlap_entry, "tools_iso_milling_type": self.ui.iso_milling_type_radio, - "tools_iso_follow": self.ui.iso_follow_cb, "tools_iso_isotype": self.ui.iso_type_radio, # Drilling @@ -1577,7 +1559,6 @@ class ToolsDB2(QtWidgets.QWidget): "gdb_i_passes": "tools_iso_passes", "gdb_i_overlap": "tools_iso_overlap", "gdb_i_milling_type": "tools_iso_milling_type", - "gdb_i_follow": "tools_iso_follow", "gdb_i_iso_type": "tools_iso_isotype", # Drilling @@ -1951,7 +1932,6 @@ class ToolsDB2(QtWidgets.QWidget): "tools_iso_passes": int(self.app.defaults["tools_iso_passes"]), "tools_iso_overlap": float(self.app.defaults["tools_iso_overlap"]), "tools_iso_milling_type": self.app.defaults["tools_iso_milling_type"], - "tools_iso_follow": self.app.defaults["tools_iso_follow"], "tools_iso_isotype": self.app.defaults["tools_iso_isotype"], # Drilling @@ -2519,8 +2499,6 @@ class ToolsDB2(QtWidgets.QWidget): self.db_tool_dict[tool_id]['data']['tools_iso_overlap'] = val elif wdg_name == "gdb_i_milling_type": self.db_tool_dict[tool_id]['data']['tools_iso_milling_type'] = val - elif wdg_name == "gdb_i_follow": - self.db_tool_dict[tool_id]['data']['tools_iso_follow'] = val elif wdg_name == "gdb_i_iso_type": self.db_tool_dict[tool_id]['data']['tools_iso_isotype'] = val diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index 13da5919..23366434 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -1050,6 +1050,8 @@ class MainGUI(QtWidgets.QMainWindow): QtGui.QIcon(self.app.resource_location + '/paint20_1.png'), _("Paint Tool")) self.isolation_btn = self.toolbartools.addAction( QtGui.QIcon(self.app.resource_location + '/iso_16.png'), _("Isolation Tool")) + self.follow_btn = self.toolbartools.addAction( + QtGui.QIcon(self.app.resource_location + '/follow32.png'), _("Follow Tool")) self.drill_btn = self.toolbartools.addAction( QtGui.QIcon(self.app.resource_location + '/extract_drill32.png'), _("Drilling Tool")) self.toolbartools.addSeparator() @@ -2219,6 +2221,8 @@ class MainGUI(QtWidgets.QMainWindow): QtGui.QIcon(self.app.resource_location + '/paint20_1.png'), _("Paint Tool")) self.isolation_btn = self.toolbartools.addAction( QtGui.QIcon(self.app.resource_location + '/iso_16.png'), _("Isolation Tool")) + self.follow_btn = self.toolbartools.addAction( + QtGui.QIcon(self.app.resource_location + '/follow32.png'), _("Follow Tool")) self.drill_btn = self.toolbartools.addAction( QtGui.QIcon(self.app.resource_location + '/extract_drill32.png'), _("Drilling Tool")) self.toolbartools.addSeparator() diff --git a/appGUI/ObjectUI.py b/appGUI/ObjectUI.py index b6d3a1fb..d645e30b 100644 --- a/appGUI/ObjectUI.py +++ b/appGUI/ObjectUI.py @@ -398,10 +398,21 @@ class GerberObjectUI(ObjectUI): # """) grid0.addWidget(self.generate_cutout_button, 20, 0, 1, 3) + # Follow Tool + self.generate_follow_button = FCButton(_('Follow Tool')) + self.generate_follow_button.setIcon(QtGui.QIcon(self.app.resource_location + '/follow32.png')) + self.generate_follow_button.setToolTip( + _("Generate a 'Follow' geometry.\n" + "This means that it will cut through\n" + "the middle of the trace.")) + # self.generate_cutout_button.setStyleSheet(""" + + grid0.addWidget(self.generate_follow_button, 22, 0, 1, 3) + separator_line = QtWidgets.QFrame() separator_line.setFrameShape(QtWidgets.QFrame.HLine) separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - grid0.addWidget(separator_line, 22, 0, 1, 3) + grid0.addWidget(separator_line, 24, 0, 1, 3) # UTILITIES BUTTON self.util_button = FCButton('%s' % _("UTILTIES"), checkable=True) @@ -413,12 +424,12 @@ class GerberObjectUI(ObjectUI): font-weight: bold; } """) - grid0.addWidget(self.util_button, 24, 0, 1, 3) + grid0.addWidget(self.util_button, 26, 0, 1, 3) # UTILITIES Frame self.util_frame = QtWidgets.QFrame() self.util_frame.setContentsMargins(0, 0, 0, 0) - grid0.addWidget(self.util_frame, 25, 0, 1, 3) + grid0.addWidget(self.util_frame, 28, 0, 1, 3) self.util_box = QtWidgets.QVBoxLayout() self.util_box.setContentsMargins(0, 0, 0, 0) self.util_frame.setLayout(self.util_box) diff --git a/appGUI/preferences/PreferencesUIManager.py b/appGUI/preferences/PreferencesUIManager.py index 8f1d60c1..3d8b81be 100644 --- a/appGUI/preferences/PreferencesUIManager.py +++ b/appGUI/preferences/PreferencesUIManager.py @@ -347,7 +347,6 @@ class PreferencesUIManager: "tools_iso_passes": self.ui.tools_defaults_form.tools_iso_group.passes_entry, "tools_iso_overlap": self.ui.tools_defaults_form.tools_iso_group.overlap_entry, "tools_iso_milling_type": self.ui.tools_defaults_form.tools_iso_group.milling_type_radio, - "tools_iso_follow": self.ui.tools_defaults_form.tools_iso_group.follow_cb, "tools_iso_isotype": self.ui.tools_defaults_form.tools_iso_group.iso_type_radio, "tools_iso_rest": self.ui.tools_defaults_form.tools_iso_group.rest_cb, diff --git a/appGUI/preferences/tools/ToolsISOPrefGroupUI.py b/appGUI/preferences/tools/ToolsISOPrefGroupUI.py index bc4d51d3..4c34ad09 100644 --- a/appGUI/preferences/tools/ToolsISOPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsISOPrefGroupUI.py @@ -197,23 +197,6 @@ class ToolsISOPrefGroupUI(OptionsGroupUI): grid0.addWidget(self.milling_type_label, 10, 0) grid0.addWidget(self.milling_type_radio, 10, 1, 1, 2) - # Follow - self.follow_label = QtWidgets.QLabel('%s:' % _('Follow')) - self.follow_label.setToolTip( - _("Generate a 'Follow' geometry.\n" - "This means that it will cut through\n" - "the middle of the trace.") - ) - - self.follow_cb = FCCheckBox() - self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n" - "This means that it will cut through\n" - "the middle of the trace.")) - self.follow_cb.setObjectName("i_follow") - - grid0.addWidget(self.follow_label, 11, 0) - grid0.addWidget(self.follow_cb, 11, 1, 1, 2) - # Isolation Type self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type')) self.iso_type_label.setToolTip( diff --git a/appObjects/AppObject.py b/appObjects/AppObject.py index fcf9bcff..ce77af1c 100644 --- a/appObjects/AppObject.py +++ b/appObjects/AppObject.py @@ -7,7 +7,6 @@ # Modified by Marius Stanciu (2020) # # ########################################################### -from PyQt5 import QtCore from appObjects.ObjectCollection import * from appObjects.FlatCAMCNCJob import CNCJobObject from appObjects.FlatCAMDocument import DocumentObject @@ -56,7 +55,8 @@ class AppObject(QtCore.QObject): self.object_plotted.connect(self.on_object_plotted) self.plots_updated.connect(self.app.on_plots_updated) - def new_object(self, kind, name, initialize, plot=True, autoselected=True, callback=None, callback_params=None): + def new_object(self, kind, name, initialize: object, plot=True, autoselected=True, callback=None, + callback_params=None): """ Creates a new specialized FlatCAMObj and attaches it to the application, this is, updates the GUI accordingly, any other records and plots it. @@ -74,7 +74,7 @@ class AppObject(QtCore.QObject): :param initialize: Function to run after creation of the object but before it is attached to the application. The function is called with 2 parameters: the new object and the App instance. - :type initialize: function + :type initialize: object :param plot: If to plot the resulting object :param autoselected: if the resulting object is autoselected in the Project tab and therefore in the self.collection diff --git a/appObjects/FlatCAMGeometry.py b/appObjects/FlatCAMGeometry.py index 54b1b0e9..0b1675c5 100644 --- a/appObjects/FlatCAMGeometry.py +++ b/appObjects/FlatCAMGeometry.py @@ -2302,7 +2302,8 @@ class GeometryObject(FlatCAMObj, Geometry): 'tooldia': tooldia_val }) if "optimization_type" not in tools_dict[tooluid_key]['data']: - tools_dict[tooluid_key]['data']["optimization_type"] = self.app.defaults["geometry_optimization_type"] + tools_dict[tooluid_key]['data']["optimization_type"] = \ + self.app.defaults["geometry_optimization_type"] # find the tool_dia associated with the tooluid_key # search in the self.tools for the sel_tool_dia and when found see what tooluid has @@ -2574,7 +2575,7 @@ class GeometryObject(FlatCAMObj, Geometry): def job_thread(app_obj): with self.app.proc_container.new(_("Generating CNC Code")): app_obj.app_obj.new_object("cncjob", outname, job_init, plot=plot) - app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created")), outname) + app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname)) # Create a promise with the name self.app.collection.promise(outname) diff --git a/appObjects/FlatCAMGerber.py b/appObjects/FlatCAMGerber.py index 6d23f705..83eece83 100644 --- a/appObjects/FlatCAMGerber.py +++ b/appObjects/FlatCAMGerber.py @@ -156,6 +156,7 @@ class GerberObject(FlatCAMObj, Gerber): self.ui.iso_button.clicked.connect(self.app.isolation_tool.run) self.ui.generate_ncc_button.clicked.connect(self.app.ncclear_tool.run) self.ui.generate_cutout_button.clicked.connect(self.app.cutout_tool.run) + self.ui.generate_follow_button.clicked.connect(self.app.follow_tool.run) # Utilties self.ui.generate_bb_button.clicked.connect(self.on_generatebb_button_click) diff --git a/appParsers/ParseHPGL2.py b/appParsers/ParseHPGL2.py index fa2c2a7a..1a73513c 100644 --- a/appParsers/ParseHPGL2.py +++ b/appParsers/ParseHPGL2.py @@ -105,7 +105,6 @@ class HPGL2: "tools_iso_passes": self.app.defaults["tools_iso_passes"], "tools_iso_overlap": self.app.defaults["tools_iso_overlap"], "tools_iso_milling_type": self.app.defaults["tools_iso_milling_type"], - "tools_iso_follow": self.app.defaults["tools_iso_follow"], "tools_iso_isotype": self.app.defaults["tools_iso_isotype"], "tools_iso_rest": self.app.defaults["tools_iso_rest"], diff --git a/appTools/ToolFollow.py b/appTools/ToolFollow.py new file mode 100644 index 00000000..a6bcda57 --- /dev/null +++ b/appTools/ToolFollow.py @@ -0,0 +1,712 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# File by: Marius Adrian Stanciu (c) # +# Date: 5/25/2020 # +# License: MIT Licence # +# ########################################################## + +from PyQt5 import QtWidgets, QtCore, QtGui + +from appTool import AppTool +from appGUI.GUIElements import RadioSet, FCButton, FCComboBox, FCLabel, FCComboBox2 +from appParsers.ParseGerber import Gerber + +from copy import deepcopy + +import numpy as np + +from shapely.ops import unary_union +from shapely.geometry import MultiPolygon, Polygon, MultiLineString, LineString, LinearRing, Point + +from matplotlib.backend_bases import KeyEvent as mpl_key_event + +import logging +import gettext +import appTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + +log = logging.getLogger('base') + + +class ToolFollow(AppTool, Gerber): + + optimal_found_sig = QtCore.pyqtSignal(float) + + def __init__(self, app): + self.app = app + self.decimals = self.app.decimals + + AppTool.__init__(self, app) + Gerber.__init__(self, steps_per_circle=self.app.defaults["gerber_circle_steps"]) + + # ############################################################################# + # ######################### Tool GUI ########################################## + # ############################################################################# + self.ui = FollowUI(layout=self.layout, app=self.app) + self.toolName = self.ui.toolName + + # disconnect flags + self.area_sel_disconnect_flag = False + + self.first_click = False + self.cursor_pos = None + self.mouse_is_dragging = False + + self.mm = None + self.mp = None + self.mr = None + self.kp = None + + self.sel_rect = [] + + # store here the points for the "Polygon" area selection shape + self.points = [] + # set this as True when in middle of drawing a "Polygon" area selection shape + # it is made False by first click to signify that the shape is complete + self.poly_drawn = False + + # Signals + self.ui.selectmethod_radio.activated_custom.connect(self.ui.on_selection) + self.ui.generate_geometry_button.clicked.connect(self.on_generate_geometry_click) + + def install(self, icon=None, separator=None, **kwargs): + AppTool.install(self, icon, separator, shortcut='', **kwargs) + + def run(self, toggle=True): + self.app.defaults.report_usage("ToolFollow()") + log.debug("ToolFOllow().run() was launched ...") + + if toggle: + # if the splitter is hidden, display it, else hide it but only if the current widget is the same + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + else: + try: + if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName: + # if tab is populated with the tool but it does not have the focus, focus on it + if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab: + # focus on Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab) + else: + self.app.ui.splitter.setSizes([0, 1]) + except AttributeError: + pass + else: + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + AppTool.run(self) + self.set_tool_ui() + + self.app.ui.notebook.setTabText(2, _("Follow Tool")) + + def set_tool_ui(self): + self.units = self.app.defaults['units'].upper() + self.ui.selectmethod_radio.set_value('all') # _("All") + self.ui.area_shape_radio.set_value('square') + + self.sel_rect[:] = [] + self.points = [] + self.poly_drawn = False + self.area_sel_disconnect_flag = False + + def on_generate_geometry_click(self): + obj_name = self.ui.object_combo.currentText() + + # Get source object. + try: + obj = self.app.collection.get_by_name(obj_name) + except Exception as e: + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name))) + return "Could not retrieve object: %s with error: %s" % (obj_name, str(e)) + + if obj is None: + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(self.obj_name))) + return + + formatted_name = obj_name.rpartition('.')[0] + if formatted_name == '': + formatted_name = obj_name + outname = '%s_follow' % formatted_name + + select_method = self.ui.selectmethod_radio.get_value() + if select_method == 'all': # _("All") + self.follow_all(obj, outname) + else: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area.")) + + if self.app.is_legacy is False: + self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot) + self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot) + self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot) + else: + self.app.plotcanvas.graph_event_disconnect(self.app.mp) + self.app.plotcanvas.graph_event_disconnect(self.app.mm) + self.app.plotcanvas.graph_event_disconnect(self.app.mr) + + 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) + self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press) + + # disconnect flags + self.area_sel_disconnect_flag = True + + def follow_all(self, obj, outname): + def job_thread(tool_obj): + tool_obj.follow_geo(obj, outname) + + self.app.worker_task.emit({'fcn': job_thread, 'params': [self]}) + + def follow_area(self): + obj_name = self.ui.object_combo.currentText() + + # Get source object. + try: + obj = self.app.collection.get_by_name(obj_name) + except Exception as e: + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name))) + return "Could not retrieve object: %s with error: %s" % (obj_name, str(e)) + + if obj is None: + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(self.obj_name))) + return + + formatted_name = obj_name.rpartition('.')[0] + if formatted_name == '': + formatted_name = obj_name + outname = '%s_follow' % formatted_name + + def job_thread(tool_obj): + tool_obj.follow_geo_area(obj, outname) + + self.app.worker_task.emit({'fcn': job_thread, 'params': [self]}) + + def follow_geo(self, followed_obj, outname): + """ + Creates a geometry object "following" the gerber paths. + + :param followed_obj: Gerber object for which to generate the follow geometry + :type followed_obj: AppObjects.FlatCAMGerber.GerberObject + :param outname: Nme of the resulting Geometry object + :type outname: str + :return: None + """ + + def follow_init(new_obj, app_obj): + if type(app_obj.defaults["geometry_cnctooldia"]) == float: + tools_list = [app_obj.defaults["geometry_cnctooldia"]] + else: + try: + temp_tools = app_obj.defaults["geometry_cnctooldia"].split(",") + tools_list = [ + float(eval(dia)) for dia in temp_tools if dia != '' + ] + except Exception as e: + log.error("ToolFollow.follow_geo -> At least one tool diameter needed. -> %s" % str(e)) + return 'fail' + + # store here the default data for Geometry Data + new_data = {} + + for opt_key, opt_val in app_obj.options.items(): + if opt_key.find('geometry' + "_") == 0: + oname = opt_key[len('geometry') + 1:] + new_data[oname] = app_obj.options[opt_key] + if opt_key.find('tools_mill' + "_") == 0: + oname = opt_key[len('tools_mill') + 1:] + new_data[oname] = app_obj.options[opt_key] + + # Propagate options + new_obj.options["cnctooldia"] = app_obj.defaults["geometry_cnctooldia"] + new_obj.solid_geometry = deepcopy(followed_obj.follow_geometry) + new_obj.tools = { + 1: { + 'tooldia': app_obj.dec_format(float(tools_list[0]), self.decimals), + 'offset': 'Path', + 'offset_value': 0.0, + 'type': 'Rough', + 'tool_type': 'C1', + 'data': deepcopy(new_data), + 'solid_geometry': new_obj.solid_geometry + } + } + + ret = self.app.app_obj.new_object("geometry", outname, follow_init) + if ret == 'fail': + self.app.inform.emit("[ERROR_NOTCL] %s" % _("Failed to create Follow Geometry.")) + else: + self.app.inform.emit("[success] %s" % _("Done.")) + + def follow_geo_area(self, followed_obj, outname): + """ + Creates a geometry object "following" the gerber paths. + + :param followed_obj: Gerber object for which to generate the follow geometry + :type followed_obj: AppObjects.FlatCAMGerber.GerberObject + :param outname: Nme of the resulting Geometry object + :type outname: str + :return: None + """ + + def follow_init(new_obj, app_obj): + if type(app_obj.defaults["geometry_cnctooldia"]) == float: + tools_list = [app_obj.defaults["geometry_cnctooldia"]] + else: + try: + temp_tools = app_obj.defaults["geometry_cnctooldia"].split(",") + tools_list = [ + float(eval(dia)) for dia in temp_tools if dia != '' + ] + except Exception as e: + log.error("ToolFollow.follow_geo -> At least one tool diameter needed. -> %s" % str(e)) + return 'fail' + + # store here the default data for Geometry Data + new_data = {} + + for opt_key, opt_val in app_obj.options.items(): + if opt_key.find('geometry' + "_") == 0: + oname = opt_key[len('geometry') + 1:] + new_data[oname] = app_obj.options[opt_key] + if opt_key.find('tools_mill' + "_") == 0: + oname = opt_key[len('tools_mill') + 1:] + new_data[oname] = app_obj.options[opt_key] + + # Propagate options + new_obj.options["cnctooldia"] = app_obj.defaults["geometry_cnctooldia"] + + target_geo = unary_union(followed_obj.follow_geometry) + area_follow = target_geo.intersection(deepcopy(unary_union(self.sel_rect))) + self.sel_rect[:] = [] + self.points = [] + + new_obj.solid_geometry = deepcopy(area_follow) + new_obj.tools = { + 1: { + 'tooldia': app_obj.dec_format(float(tools_list[0]), self.decimals), + 'offset': 'Path', + 'offset_value': 0.0, + 'type': 'Rough', + 'tool_type': 'C1', + 'data': deepcopy(new_data), + 'solid_geometry': new_obj.solid_geometry + } + } + + ret = self.app.app_obj.new_object("geometry", outname, follow_init) + if ret == 'fail': + self.app.inform.emit("[ERROR_NOTCL] %s" % _("Failed to create Follow Geometry.")) + else: + self.app.inform.emit("[success] %s" % _("Done.")) + + # To be called after clicking on the plot. + def on_mouse_release(self, event): + if self.app.is_legacy is False: + event_pos = event.pos + # event_is_dragging = event.is_dragging + right_button = 2 + else: + event_pos = (event.xdata, event.ydata) + # event_is_dragging = self.app.plotcanvas.is_dragging + right_button = 3 + + try: + x = float(event_pos[0]) + y = float(event_pos[1]) + except TypeError: + return + + event_pos = (x, y) + + shape_type = self.ui.area_shape_radio.get_value() + + curr_pos = self.app.plotcanvas.translate_coords(event_pos) + if self.app.grid_status(): + curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1]) + + x1, y1 = curr_pos[0], curr_pos[1] + + # do paint single only for left mouse clicks + if event.button == 1: + if shape_type == "square": + if not self.first_click: + self.first_click = True + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the area.")) + + self.cursor_pos = self.app.plotcanvas.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(_("Zone added. Click to start adding next zone or right click to finish.")) + self.app.delete_selection_shape() + + x0, y0 = self.cursor_pos[0], self.cursor_pos[1] + pt1 = (x0, y0) + pt2 = (x1, y0) + pt3 = (x1, y1) + pt4 = (x0, y1) + + new_rectangle = Polygon([pt1, pt2, pt3, pt4]) + self.sel_rect.append(new_rectangle) + + # add a temporary shape on canvas + self.draw_tool_selection_shape(old_coords=(x0, y0), coords=(x1, y1)) + + self.first_click = False + return + else: + self.points.append((x1, y1)) + + if len(self.points) > 1: + self.poly_drawn = True + self.app.inform.emit(_("Click on next Point or click right mouse button to complete ...")) + + return "" + elif event.button == right_button and self.mouse_is_dragging is False: + + shape_type = self.ui.area_shape_radio.get_value() + + if shape_type == "square": + self.first_click = False + else: + # if we finish to add a polygon + if self.poly_drawn is True: + try: + # try to add the point where we last clicked if it is not already in the self.points + last_pt = (x1, y1) + if last_pt != self.points[-1]: + self.points.append(last_pt) + except IndexError: + pass + + # we need to add a Polygon and a Polygon can be made only from at least 3 points + if len(self.points) > 2: + self.delete_moving_selection_shape() + pol = Polygon(self.points) + # do not add invalid polygons even if they are drawn by utility geometry + if pol.is_valid: + self.sel_rect.append(pol) + self.draw_selection_shape_polygon(points=self.points) + self.app.inform.emit( + _("Zone added. Click to start adding next zone or right click to finish.")) + + self.points = [] + self.poly_drawn = False + return + + self.delete_tool_selection_shape() + + if self.app.is_legacy is False: + self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release) + self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move) + self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press) + else: + self.app.plotcanvas.graph_event_disconnect(self.mr) + self.app.plotcanvas.graph_event_disconnect(self.mm) + self.app.plotcanvas.graph_event_disconnect(self.kp) + + self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', + self.app.on_mouse_click_over_plot) + self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move', + self.app.on_mouse_move_over_plot) + self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release', + self.app.on_mouse_click_release_over_plot) + + # disconnect flags + self.area_sel_disconnect_flag = False + + if len(self.sel_rect) == 0: + return + + self.follow_area() + + # called on mouse move + def on_mouse_move(self, event): + shape_type = self.ui.area_shape_radio.get_value() + + if self.app.is_legacy is False: + event_pos = event.pos + event_is_dragging = event.is_dragging + # right_button = 2 + else: + event_pos = (event.xdata, event.ydata) + event_is_dragging = self.app.plotcanvas.is_dragging + # right_button = 3 + + try: + x = float(event_pos[0]) + y = float(event_pos[1]) + except TypeError: + return + + curr_pos = self.app.plotcanvas.translate_coords((x, y)) + + # detect mouse dragging motion + if event_is_dragging == 1: + self.mouse_is_dragging = True + else: + self.mouse_is_dragging = False + + # update the cursor position + if self.app.grid_status(): + # 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=self.app.cursor_color_3D, + edge_width=self.app.defaults["global_cursor_width"], + size=self.app.defaults["global_cursor_size"]) + + if self.cursor_pos is None: + self.cursor_pos = (0, 0) + + self.app.dx = curr_pos[0] - float(self.cursor_pos[0]) + self.app.dy = curr_pos[1] - float(self.cursor_pos[1]) + + # # update the positions on status bar + self.app.ui.position_label.setText(" X: %.4f   " + "Y: %.4f " % (curr_pos[0], curr_pos[1])) + self.app.ui.rel_position_label.setText("Dx: %.4f   Dy: " + "%.4f    " % (self.app.dx, self.app.dy)) + + units = self.app.defaults["units"].lower() + self.app.plotcanvas.text_hud.text = \ + 'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX: \t{:<.4f} [{:s}]\nY: \t{:<.4f} [{:s}]'.format( + self.app.dx, units, self.app.dy, units, curr_pos[0], units, curr_pos[1], units) + + # draw the utility geometry + if shape_type == "square": + 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])) + else: + self.delete_moving_selection_shape() + 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 + + # events out of the self.app.collection view (it's about Project Tab) are of type int + if type(event) is int: + key = event + # events from the GUI are of type QKeyEvent + 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 + + key = event.key + key = QtGui.QKeySequence(key) + + # check for modifiers + key_string = key.toString().lower() + if '+' in key_string: + mod, __, key_text = key_string.rpartition('+') + if mod.lower() == 'ctrl': + # modifiers = QtCore.Qt.ControlModifier + pass + elif mod.lower() == 'alt': + # modifiers = QtCore.Qt.AltModifier + pass + elif mod.lower() == 'shift': + # modifiers = QtCore.Qt.ShiftModifier + pass + else: + # modifiers = QtCore.Qt.NoModifier + pass + key = QtGui.QKeySequence(key_text) + + # events from Vispy are of type KeyEvent + else: + key = event.key + + if key == QtCore.Qt.Key_Escape or key == 'Escape': + if self.area_sel_disconnect_flag is True: + try: + if self.app.is_legacy is False: + self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release) + self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move) + self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press) + else: + self.app.plotcanvas.graph_event_disconnect(self.mr) + self.app.plotcanvas.graph_event_disconnect(self.mm) + self.app.plotcanvas.graph_event_disconnect(self.kp) + except Exception as e: + log.debug("ToolFollow.on_key_press() _1 --> %s" % str(e)) + + self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', + self.app.on_mouse_click_over_plot) + self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move', + self.app.on_mouse_move_over_plot) + self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release', + self.app.on_mouse_click_release_over_plot) + self.points = [] + self.poly_drawn = False + self.sel_rect[:] = [] + + self.delete_moving_selection_shape() + self.delete_tool_selection_shape() + + +class FollowUI: + + toolName = _("Follow Tool") + + def __init__(self, layout, app): + self.app = app + self.decimals = self.app.decimals + self.layout = layout + + self.tools_frame = QtWidgets.QFrame() + self.tools_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.tools_frame) + self.tools_box = QtWidgets.QVBoxLayout() + self.tools_box.setContentsMargins(0, 0, 0, 0) + self.tools_frame.setLayout(self.tools_box) + + self.title_box = QtWidgets.QHBoxLayout() + self.tools_box.addLayout(self.title_box) + + # ## Title + title_label = FCLabel("%s" % self.toolName) + title_label.setStyleSheet(""" + QLabel + { + font-size: 16px; + font-weight: bold; + } + """) + title_label.setToolTip( + _("Create a Geometry object with\n" + "toolpaths to cut around polygons.") + ) + + self.title_box.addWidget(title_label) + + self.obj_combo_label = FCLabel('%s:' % _("GERBER")) + self.obj_combo_label.setToolTip( + _("Source object for following geometry.") + ) + + self.tools_box.addWidget(self.obj_combo_label) + + # ############################################################################################################# + # ################################ The object to be followed ################################################## + # ############################################################################################################# + self.object_combo = FCComboBox() + self.object_combo.setModel(self.app.collection) + self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + # self.object_combo.setCurrentIndex(1) + self.object_combo.is_last = True + + self.tools_box.addWidget(self.object_combo) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.tools_box.addWidget(separator_line) + + grid0 = QtWidgets.QGridLayout() + grid0.setColumnStretch(0, 0) + grid0.setColumnStretch(1, 1) + self.tools_box.addLayout(grid0) + + # Parameter Title + self.param_title = FCLabel("%s:" % _("Parameters")) + grid0.addWidget(self.param_title, 0, 0, 1, 2) + + # Polygon selection + selectlabel = FCLabel('%s:' % _('Selection')) + selectlabel.setToolTip( + _("Selection of area to be processed.\n" + "- 'All Polygons' - the process will start after click.\n" + "- 'Area Selection' - left mouse click to start selection of the area to be processed.") + ) + + self.selectmethod_radio = RadioSet([{'label': _("All"), 'value': 'all'}, + {'label': _("Area Selection"), 'value': 'area'}]) + + grid0.addWidget(selectlabel, 2, 0) + grid0.addWidget(self.selectmethod_radio, 2, 1) + + # Area Selection shape + self.area_shape_label = FCLabel('%s:' % _("Shape")) + self.area_shape_label.setToolTip( + _("The kind of selection shape used for area selection.") + ) + + self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'}, + {'label': _("Polygon"), 'value': 'polygon'}]) + + grid0.addWidget(self.area_shape_label, 4, 0) + grid0.addWidget(self.area_shape_radio, 4, 1) + + self.area_shape_label.hide() + self.area_shape_radio.hide() + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 6, 0, 1, 2) + + self.generate_geometry_button = FCButton("%s" % _("Generate Geometry")) + self.generate_geometry_button.setIcon(QtGui.QIcon(self.app.resource_location + '/geometry32.png')) + self.generate_geometry_button.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.generate_geometry_button.setToolTip(_("Generate a 'Follow' geometry.\n" + "This means that it will cut through\n" + "the middle of the trace.")) + self.tools_box.addWidget(self.generate_geometry_button) + + self.tools_box.addStretch() + + # ## Reset Tool + self.reset_button = FCButton(_("Reset Tool")) + self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png')) + self.reset_button.setToolTip( + _("Will reset the tool parameters.") + ) + self.reset_button.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.tools_box.addWidget(self.reset_button) + # ############################ FINSIHED GUI ################################### + # ############################################################################# + + def on_selection(self, val): + if val == 'area': # _("Area Selection") + self.area_shape_label.show() + self.area_shape_radio.show() + else: # All + self.area_shape_label.hide() + self.area_shape_radio.hide() + + def confirmation_message(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"), + self.decimals, + minval, + self.decimals, + maxval), False) + else: + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) + + def confirmation_message_int(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' % + (_("Edited value is out of range"), minval, maxval), False) + else: + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) diff --git a/appTools/ToolIsolation.py b/appTools/ToolIsolation.py index 907884c1..ddaedfe7 100644 --- a/appTools/ToolIsolation.py +++ b/appTools/ToolIsolation.py @@ -136,7 +136,6 @@ class ToolIsolation(AppTool, Gerber): "tools_iso_overlap": self.ui.iso_overlap_entry, "tools_iso_milling_type": self.ui.milling_type_radio, "tools_iso_combine": self.ui.combine_passes_cb, - "tools_iso_follow": self.ui.follow_cb, "tools_iso_isotype": self.ui.iso_type_radio } @@ -145,7 +144,6 @@ class ToolIsolation(AppTool, Gerber): "i_overlap": "tools_iso_overlap", "i_milling_type": "tools_iso_milling_type", "i_combine": "tools_iso_combine", - "i_follow": "tools_iso_follow", "i_iso_type": "tools_iso_isotype" } @@ -253,10 +251,6 @@ class ToolIsolation(AppTool, Gerber): self.ui.iso_type_radio.set_value('full') self.ui.iso_type_radio.hide() - self.ui.follow_cb.set_value(False) - self.ui.follow_cb.hide() - self.ui.follow_label.hide() - self.ui.rest_cb.set_value(False) self.ui.rest_cb.hide() self.ui.forced_rest_iso_cb.hide() @@ -280,10 +274,6 @@ class ToolIsolation(AppTool, Gerber): self.ui.iso_type_radio.set_value(self.app.defaults["tools_iso_isotype"]) self.ui.iso_type_radio.show() - self.ui.follow_cb.set_value(self.app.defaults["tools_iso_follow"]) - self.ui.follow_cb.show() - self.ui.follow_label.show() - self.ui.rest_cb.set_value(self.app.defaults["tools_iso_rest"]) self.ui.rest_cb.show() self.ui.forced_rest_iso_cb.show() @@ -373,7 +363,6 @@ class ToolIsolation(AppTool, Gerber): "tools_iso_passes": self.app.defaults["tools_iso_passes"], "tools_iso_overlap": self.app.defaults["tools_iso_overlap"], "tools_iso_milling_type": self.app.defaults["tools_iso_milling_type"], - "tools_iso_follow": self.app.defaults["tools_iso_follow"], "tools_iso_isotype": self.app.defaults["tools_iso_isotype"], "tools_iso_rest": self.app.defaults["tools_iso_rest"], @@ -1477,41 +1466,6 @@ class ToolIsolation(AppTool, Gerber): self.app.worker_task.emit({'fcn': worker_task, 'params': [self.grb_obj]}) - def follow_geo(self, followed_obj, outname): - """ - Creates a geometry object "following" the gerber paths. - - :param followed_obj: Gerber object for which to generate the follow geometry - :type followed_obj: AppObjects.FlatCAMGerber.GerberObject - :param outname: Nme of the resulting Geometry object - :type outname: str - :return: None - """ - - def follow_init(follow_obj, app_obj): - # Propagate options - follow_obj.options["cnctooldia"] = str(tooldia) - follow_obj.solid_geometry = self.grb_obj.follow_geometry - app_obj.inform.emit('[success] %s.' % _("Following geometry was generated")) - - # in the end toggle the visibility of the origin object so we can see the generated Geometry - followed_obj.ui.plot_cb.set_value(False) - follow_name = outname - - for tool in self.iso_tools: - tooldia = self.iso_tools[tool]['tooldia'] - new_name = "%s_%.*f" % (follow_name, self.decimals, tooldia) - - follow_state = self.iso_tools[tool]['data']['tools_iso_follow'] - if follow_state: - ret = self.app.app_obj.new_object("geometry", new_name, follow_init) - if ret == 'fail': - self.app.inform.emit("[ERROR_NOTCL] %s: %.*f" % ( - _("Failed to create Follow Geometry with tool diameter"), self.decimals, tooldia)) - else: - self.app.inform.emit("[success] %s: %.*f" % ( - _("Follow Geometry was created with tool diameter"), self.decimals, tooldia)) - def isolate_handler(self, isolated_obj): """ Creates a geometry object with paths around the gerber features. @@ -1623,11 +1577,10 @@ class ToolIsolation(AppTool, Gerber): for tool in sorted_tools: tool_data = tools_storage[tool]['data'] - to_follow = tool_data['tools_iso_follow'] work_geo = geometry if work_geo is None: - work_geo = isolated_obj.follow_geometry if to_follow else isolated_obj.solid_geometry + work_geo = isolated_obj.solid_geometry iso_t = { 'ext': 0, @@ -1670,7 +1623,7 @@ class ToolIsolation(AppTool, Gerber): mill_dir = 1 if milling_type == 'cl' else 0 iso_geo = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t, - follow=to_follow, nr_passes=i, prog_plot=prog_plot) + nr_passes=i, prog_plot=prog_plot) if iso_geo == 'fail': self.app.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated.")) continue @@ -1996,13 +1949,9 @@ class ToolIsolation(AppTool, Gerber): tool_cut_type = tools_storage[tool]['type'] tool_data = tools_storage[tool]['data'] - to_follow = tool_data['tools_iso_follow'] - - # TODO what to do when the iso2geo param is not None but the Follow cb is checked - # for the case when limited area is used .... the follow geo should be clipped too work_geo = geometry if work_geo is None: - work_geo = iso_obj.follow_geometry if to_follow else iso_obj.solid_geometry + work_geo = iso_obj.solid_geometry iso_t = { 'ext': 0, @@ -2040,7 +1989,7 @@ class ToolIsolation(AppTool, Gerber): mill_dir = 1 if milling_type == 'cl' else 0 iso_geo = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t, - follow=to_follow, nr_passes=nr_pass, prog_plot=prog_plot) + nr_passes=nr_pass, prog_plot=prog_plot) if iso_geo == 'fail': self.app.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated.")) continue @@ -2097,15 +2046,12 @@ class ToolIsolation(AppTool, Gerber): if len(tools_storage) > 1: geo_obj.multigeo = True else: - if to_follow: - geo_obj.multigeo = False - else: - passes_no = 1 - for ky in tools_storage.keys(): - passes_no = float(tools_storage[ky]['data']['tools_iso_passes']) - geo_obj.multigeo = True - break + passes_no = 1 + for ky in tools_storage.keys(): + passes_no = float(tools_storage[ky]['data']['tools_iso_passes']) geo_obj.multigeo = True + break + geo_obj.multigeo = True # detect if solid_geometry is empty and this require list flattening which is "heavy" # or just looking in the lists (they are one level depth) and if any is not empty @@ -2789,7 +2735,7 @@ class ToolIsolation(AppTool, Gerber): def poly2ints(poly): return [interior for interior in poly.interiors] - def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0, + def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, nr_passes=0, prog_plot=False): """ Isolation_geometry produces an envelope that is going on the left of the geometry @@ -2807,8 +2753,6 @@ class ToolIsolation(AppTool, Gerber): :type geometry: :param env_iso_type: type of isolation, can be 0 = exteriors or 1 = interiors or 2 = both (complete) :type env_iso_type: int - :param follow: If the kind of isolation is a "follow" one - :type follow: bool :param nr_passes: Number of passes :type nr_passes: int :param prog_plot: Type of plotting: "normal" or "progressive" @@ -2817,16 +2761,12 @@ class ToolIsolation(AppTool, Gerber): :rtype: MultiPolygon or Polygon """ - if follow: - geom = self.grb_obj.isolation_geometry(offset, geometry=geometry, follow=follow, prog_plot=prog_plot) - return geom - else: - try: - geom = self.grb_obj.isolation_geometry(offset, geometry=geometry, iso_type=env_iso_type, - passes=nr_passes, prog_plot=prog_plot) - except Exception as e: - log.debug('ToolIsolation.generate_envelope() --> %s' % str(e)) - return 'fail' + try: + geom = self.grb_obj.isolation_geometry(offset, geometry=geometry, iso_type=env_iso_type, + passes=nr_passes, prog_plot=prog_plot) + except Exception as e: + log.debug('ToolIsolation.generate_envelope() --> %s' % str(e)) + return 'fail' if invert: try: @@ -3091,9 +3031,9 @@ class IsoUI: self.tools_box.addWidget(self.obj_combo_label) - # ################################################ - # ##### The object to be copper cleaned ########## - # ################################################ + # ############################################################################################################# + # ################################ The object to be isolated ################################################## + # ############################################################################################################# self.object_combo = FCComboBox() self.object_combo.setModel(self.app.collection) self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) @@ -3321,23 +3261,6 @@ class IsoUI: self.grid3.addWidget(self.milling_type_label, 15, 0) self.grid3.addWidget(self.milling_type_radio, 15, 1) - # Follow - self.follow_label = FCLabel('%s:' % _('Follow')) - self.follow_label.setToolTip( - _("Generate a 'Follow' geometry.\n" - "This means that it will cut through\n" - "the middle of the trace.") - ) - - self.follow_cb = FCCheckBox() - self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n" - "This means that it will cut through\n" - "the middle of the trace.")) - self.follow_cb.setObjectName("i_follow") - - self.grid3.addWidget(self.follow_label, 16, 0) - self.grid3.addWidget(self.follow_cb, 16, 1) - # Isolation Type self.iso_type_label = FCLabel('%s:' % _('Isolation Type')) self.iso_type_label.setToolTip( diff --git a/appTools/ToolPaint.py b/appTools/ToolPaint.py index 73ed5bac..0f4648a6 100644 --- a/appTools/ToolPaint.py +++ b/appTools/ToolPaint.py @@ -68,7 +68,6 @@ class ToolPaint(AppTool, Gerber): self.tooldia_list = [] self.tooldia = None - self.sel_rect = None self.o_name = None self.overlap = None self.connect = None @@ -2406,7 +2405,7 @@ class ToolPaint(AppTool, Gerber): return self.flat_geometry - # this is were heavy lifting is done and creating the geometry to be painted + # this is where 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????!!!! diff --git a/appTools/__init__.py b/appTools/__init__.py index 924b9a45..aab619fa 100644 --- a/appTools/__init__.py +++ b/appTools/__init__.py @@ -19,6 +19,7 @@ from appTools.ToolCutOut import CutOut from appTools.ToolNCC import NonCopperClear from appTools.ToolPaint import ToolPaint from appTools.ToolIsolation import ToolIsolation +from appTools.ToolFollow import ToolFollow from appTools.ToolDrilling import ToolDrilling from appTools.ToolMilling import ToolMilling diff --git a/app_Main.py b/app_Main.py index 47d00f3e..dbc3d684 100644 --- a/app_Main.py +++ b/app_Main.py @@ -551,7 +551,7 @@ class App(QtCore.QObject): # ----------------------------------------- WARNING -------------------------------------------------------- # Preprocessors need to be loaded before the Preferences Manager builds the Preferences - # That's because the number of preprocessors can vary and here the comboboxes are populated + # That's because the number of preprocessors can vary and here the combobox is populated # ----------------------------------------------------------------------------------------------------------- # a dictionary that have as keys the name of the preprocessor files and the value is the class from @@ -718,7 +718,6 @@ class App(QtCore.QObject): alignment=Qt.AlignBottom | Qt.AlignLeft, color=QtGui.QColor("gray")) start_plot_time = time.time() # debug - self.plotcanvas = None self.app_cursor = None self.hover_shapes = None @@ -726,7 +725,9 @@ class App(QtCore.QObject): self.log.debug("Setting up canvas: %s" % str(self.defaults["global_graphic_engine"])) # setup the PlotCanvas - self.on_plotcanvas_setup() + self.plotcanvas = self.on_plotcanvas_setup() + if self.plotcanvas == 'fail': + return end_plot_time = time.time() self.used_time = end_plot_time - start_plot_time @@ -1031,6 +1032,7 @@ class App(QtCore.QObject): self.ncclear_tool = None self.paint_tool = None self.isolation_tool = None + self.follow_tool = None self.drilling_tool = None self.optimal_tool = None @@ -1401,7 +1403,6 @@ class App(QtCore.QObject): # Canvas Context Menu self.connect_canvas_context_signals() - # Notebook tab clicking self.ui.notebook.tabBarClicked.connect(self.on_properties_tab_click) @@ -1874,6 +1875,10 @@ class App(QtCore.QObject): self.isolation_tool.install(icon=QtGui.QIcon(self.resource_location + '/iso_16.png'), pos=self.ui.menutool, before=self.sub_tool.menuAction, separator=True) + self.follow_tool = ToolFollow(self) + self.follow_tool.install(icon=QtGui.QIcon(self.resource_location + '/follow32.png'), pos=self.ui.menutool, + before=self.sub_tool.menuAction, separator=True) + self.drilling_tool = ToolDrilling(self) self.drilling_tool.install(icon=QtGui.QIcon(self.resource_location + '/extract_drill32.png'), pos=self.ui.menutool, before=self.sub_tool.menuAction, separator=True) @@ -2155,6 +2160,8 @@ class App(QtCore.QObject): self.ui.ncc_btn.triggered.connect(lambda: self.ncclear_tool.run(toggle=True)) self.ui.paint_btn.triggered.connect(lambda: self.paint_tool.run(toggle=True)) self.ui.isolation_btn.triggered.connect(lambda: self.isolation_tool.run(toggle=True)) + self.ui.follow_btn.triggered.connect(lambda: self.follow_tool.run(toggle=True)) + self.ui.drill_btn.triggered.connect(lambda: self.drilling_tool.run(toggle=True)) self.ui.panelize_btn.triggered.connect(lambda: self.panelize_tool.run(toggle=True)) @@ -7733,10 +7740,10 @@ class App(QtCore.QObject): if self.is_legacy is True or modifier == QtCore.Qt.ControlModifier: self.is_legacy = True self.defaults["global_graphic_engine"] = "2D" - self.plotcanvas = PlotCanvasLegacy(plot_container, self) + plotcanvas = PlotCanvasLegacy(plot_container, self) else: try: - self.plotcanvas = PlotCanvas(plot_container, self) + plotcanvas = PlotCanvas(plot_container, self) except Exception as er: msg_txt = traceback.format_exc() self.log.debug("App.on_plotcanvas_setup() failed -> %s" % str(er)) @@ -7749,25 +7756,25 @@ class App(QtCore.QObject): return 'fail' # So it can receive key presses - self.plotcanvas.native.setFocus() + plotcanvas.native.setFocus() if self.is_legacy is False: pan_button = 2 if self.defaults["global_pan_button"] == '2' else 3 # Set the mouse button for panning - self.plotcanvas.view.camera.pan_button_setting = pan_button + plotcanvas.view.camera.pan_button_setting = pan_button - self.mm = self.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move_over_plot) - self.mp = self.plotcanvas.graph_event_connect('mouse_press', self.on_mouse_click_over_plot) - self.mr = self.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release_over_plot) - self.mdc = self.plotcanvas.graph_event_connect('mouse_double_click', self.on_mouse_double_click_over_plot) + self.mm = plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move_over_plot) + self.mp = plotcanvas.graph_event_connect('mouse_press', self.on_mouse_click_over_plot) + self.mr = plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release_over_plot) + self.mdc = plotcanvas.graph_event_connect('mouse_double_click', self.on_mouse_double_click_over_plot) # Keys over plot enabled - self.kp = self.plotcanvas.graph_event_connect('key_press', self.ui.keyPressEvent) + self.kp = plotcanvas.graph_event_connect('key_press', self.ui.keyPressEvent) if self.defaults['global_cursor_type'] == 'small': - self.app_cursor = self.plotcanvas.new_cursor() + self.app_cursor = plotcanvas.new_cursor() else: - self.app_cursor = self.plotcanvas.new_cursor(big=True) + self.app_cursor = plotcanvas.new_cursor(big=True) if self.ui.grid_snap_btn.isChecked(): self.app_cursor.enabled = True @@ -7775,11 +7782,13 @@ class App(QtCore.QObject): self.app_cursor.enabled = False if self.is_legacy is False: - self.hover_shapes = ShapeCollection(parent=self.plotcanvas.view.scene, layers=1) + self.hover_shapes = ShapeCollection(parent=plotcanvas.view.scene, layers=1) else: # will use the default Matplotlib axes self.hover_shapes = ShapeCollectionLegacy(obj=self, app=self, name='hover') + return plotcanvas + def on_zoom_fit(self): """ Callback for zoom-fit request. This can be either from the corresponding diff --git a/camlib.py b/camlib.py index 59291d06..4181d271 100644 --- a/camlib.py +++ b/camlib.py @@ -398,7 +398,7 @@ class ApertureMacro: nrings = val[5] cross_th = val[6] cross_len = val[7] - angle = val[8] + # angle = val[8] r = dia / 2 - thickness / 2 result = Point((x, y)).buffer(r).exterior.buffer(thickness / 2.0) @@ -441,7 +441,7 @@ class ApertureMacro: dout = val[2] din = val[3] t = val[4] - angle = val[5] + # angle = val[5] ring = Point((x, y)).buffer(dout / 2.0).difference(Point((x, y)).buffer(din / 2.0)) hline = LineString([(x - dout / 2.0, y), (x + dout / 2.0, y)]).buffer(t / 2.0, cap_style=3) @@ -1036,8 +1036,7 @@ class Geometry(object): # # return self.flat_geometry, self.flat_geometry_rtree - def isolation_geometry(self, offset, geometry=None, iso_type=2, corner=None, follow=None, passes=0, - prog_plot=False): + def isolation_geometry(self, offset, geometry=None, iso_type=2, corner=None, passes=0, prog_plot=False): """ Creates contours around geometry at a given offset distance. @@ -1048,7 +1047,6 @@ class Geometry(object): :param iso_type: type of isolation, can be 0 = exteriors or 1 = interiors or 2 = both (complete) :param corner: type of corner for the isolation: 0 = round; 1 = square; 2= beveled (line that connects the ends) - :param follow: whether the geometry to be isolated is a follow_geometry :param passes: current pass out of possible multiple passes for which the isolation is done :param prog_plot: type of plotting: "normal" or "progressive" :return: The buffered geometry. @@ -1061,9 +1059,6 @@ class Geometry(object): geo_iso = [] - if follow: - return geometry - if geometry: working_geo = geometry else: @@ -1123,7 +1118,7 @@ class Geometry(object): ret_geo = self.get_exteriors(geo_iso) elif iso_type == 1: self.app.proc_container.update_view_text(' %s' % _("Get Interiors")) - ret_geo = self.get_interiors(geo_iso) + ret_geo = self.get_interiors(geo_iso) else: log.debug("Geometry.isolation_geometry() --> Type of isolation not supported") return "fail" @@ -1186,8 +1181,8 @@ class Geometry(object): merged_lines = linemerge(geos_lines) geos = geos_polys try: - for l in merged_lines: - geos.append(l) + for ml in merged_lines: + geos.append(ml) except TypeError: geos.append(merged_lines) @@ -1264,8 +1259,8 @@ class Geometry(object): merged_lines = linemerge(geos_lines) geos = geos_polys - for l in merged_lines: - geos.append(l) + for ml in merged_lines: + geos.append(ml) # Add to object if self.solid_geometry is None: @@ -2402,7 +2397,6 @@ class Geometry(object): except AttributeError: self.app.inform.emit('[ERROR_NOTCL] %s %s' % (_("Failed."), _("No object is selected."))) - self.app.proc_container.new_text = '' def rotate(self, angle, point): @@ -3661,7 +3655,7 @@ class CNCjob(Geometry): geo_storage = {} for geo in temp_solid_geometry: - if not geo is None: + if geo is not None: geo_storage[geo.coords[0]] = geo locations = list(geo_storage.keys()) @@ -3862,7 +3856,7 @@ class CNCjob(Geometry): if self.z_cut == 'fail': return 'fail' # multidepth use this - old_zcut = deepcopy(self.z_cut) + # old_zcut = deepcopy(self.z_cut) # XY_toolchange parameter try: @@ -4183,8 +4177,8 @@ class CNCjob(Geometry): drill.y ) optimized_path.append(unoptimized_coords) - # ######################################################################################################### - # ######################################################################################################### + # ##################################################################################################### + # ##################################################################################################### # Only if there are locations to drill if not optimized_path: @@ -5290,19 +5284,19 @@ class CNCjob(Geometry): self.z_cut = float(z_cut) if z_cut else None self.z_move = float(z_move) if z_move is not None else None - self.feedrate = float(feedrate) if feedrate else self.app.defaults["geometry_feedrate"] + self.feedrate = float(feedrate) if feedrate else self.app.defaults["geometry_feedrate"] self.z_feedrate = float(feedrate_z) if feedrate_z is not None else self.app.defaults["geometry_feedrate_z"] self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else self.app.defaults["geometry_feedrate_rapid"] self.spindlespeed = int(spindlespeed) if spindlespeed != 0 else None self.spindledir = spindledir self.dwell = dwell - self.dwelltime = float(dwelltime) if dwelltime else self.app.defaults["geometry_dwelltime"] + self.dwelltime = float(dwelltime) if dwelltime else self.app.defaults["geometry_dwelltime"] - self.startz = float(startz) if startz is not None else self.app.defaults["geometry_startz"] - self.z_end = float(endz) if endz is not None else self.app.defaults["geometry_endz"] + self.startz = float(startz) if startz is not None else self.app.defaults["geometry_startz"] + self.z_end = float(endz) if endz is not None else self.app.defaults["geometry_endz"] - self.xy_end = re.sub('[()\[\]]', '', str(endxy)) if endxy else self.app.defaults["geometry_endxy"] + self.xy_end = re.sub('[()\[\]]', '', str(endxy)) if endxy else self.app.defaults["geometry_endxy"] if self.xy_end and self.xy_end != '': self.xy_end = [float(eval(a)) for a in self.xy_end.split(",")] @@ -6937,8 +6931,7 @@ class CNCjob(Geometry): else: dd = d / 2 other = dim ^ 1 - return (line[0][dim] + dd * sign, line[0][other] + \ - dd * (line[1][other] - line[0][other]) / d) + return line[0][dim] + dd * sign, line[0][other] + dd * (line[1][other] - line[0][other]) / d return None # recursively breaks down a given line until it is within the @@ -7545,16 +7538,16 @@ class CNCjob(Geometry): cmaxx = -np.Inf cmaxy = -np.Inf - for k in obj: - if type(k) is dict: - for key in k: - minx_, miny_, maxx_, maxy_ = bounds_rec(k[key]) + for oo in obj: + if type(oo) is dict: + for key in oo: + minx_, miny_, maxx_, maxy_ = bounds_rec(oo[key]) cminx = min(cminx, minx_) cminy = min(cminy, miny_) cmaxx = max(cmaxx, maxx_) cmaxy = max(cmaxy, maxy_) else: - minx_, miny_, maxx_, maxy_ = bounds_rec(k) + minx_, miny_, maxx_, maxy_ = bounds_rec(oo) cminx = min(cminx, minx_) cminy = min(cminy, miny_) cmaxx = max(cmaxx, maxx_) @@ -7744,7 +7737,7 @@ class CNCjob(Geometry): temp_gcode += line lines.close() - header_stop = False + # header_stop = False return temp_gcode if self.multitool is False: diff --git a/defaults.py b/defaults.py index 818b1fa3..e2fc9572 100644 --- a/defaults.py +++ b/defaults.py @@ -415,7 +415,6 @@ class FlatCAMDefaults: "tools_iso_passes": 1, "tools_iso_overlap": 10, "tools_iso_milling_type": "cl", - "tools_iso_follow": False, "tools_iso_isotype": "full", "tools_iso_rest": False,