diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 82574b1a..94316ff3 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1631,7 +1631,7 @@ class App(QtCore.QObject): self.toggle_units_ignore = False # ############################################################################# - # ########################## LOAD POSTPROCESSORS ############################## + # ########################## LOAD PREPROCESSORS ############################### # ############################################################################# # a dictionary that have as keys the name of the preprocessor files and the value is the class from @@ -1846,7 +1846,7 @@ class App(QtCore.QObject): # signal to be called when the app is quiting self.app_quit.connect(self.quit_application, type=Qt.QueuedConnection) self.message.connect(self.message_dialog) - self.progress.connect(self.set_progress_bar) + # self.progress.connect(self.set_progress_bar) # signals that are emitted when object state changes self.object_created.connect(self.on_object_created) @@ -2464,7 +2464,10 @@ class App(QtCore.QObject): self.fiducial_tool = None # always install tools only after the shell is initialized because the self.inform.emit() depends on shell - self.install_tools() + try: + self.install_tools() + except AttributeError: + pass # ################################################################################## # ########################### SETUP RECENT ITEMS ################################### @@ -2628,7 +2631,10 @@ class App(QtCore.QObject): # Storage for shapes, storage that can be used by FlatCAm tools for utility geometry # VisPy visuals if self.is_legacy is False: - self.tool_shapes = ShapeCollection(parent=self.plotcanvas.view.scene, layers=1) + try: + self.tool_shapes = ShapeCollection(parent=self.plotcanvas.view.scene, layers=1) + except AttributeError: + self.tool_shapes = None else: from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy self.tool_shapes = ShapeCollectionLegacy(obj=self, app=self, name="tool") @@ -2639,9 +2645,20 @@ class App(QtCore.QObject): # watch out for the position of the editors instantiation ... if it is done before a save of the default values # at the first launch of the App , the editors will not be functional. - self.geo_editor = FlatCAMGeoEditor(self, disabled=True) - self.exc_editor = FlatCAMExcEditor(self) - self.grb_editor = FlatCAMGrbEditor(self) + try: + self.geo_editor = FlatCAMGeoEditor(self, disabled=True) + except AttributeError: + pass + + try: + self.exc_editor = FlatCAMExcEditor(self) + except AttributeError: + pass + + try: + self.grb_editor = FlatCAMGrbEditor(self) + except AttributeError: + pass self.log.debug("Finished adding FlatCAM Editor's.") self.set_ui_title(name=_("New Project - Not saved")) @@ -3136,7 +3153,11 @@ class App(QtCore.QObject): self.ui.menutoolshell.triggered.connect(self.on_toggle_shell) # third install all of them - self.install_tools() + try: + self.install_tools() + except AttributeError: + pass + self.log.debug("Tools are initialized.") # def parse_system_fonts(self): @@ -6625,7 +6646,7 @@ class App(QtCore.QObject): self.plotcanvas.draw_workspace(workspace_size=self.defaults['global_workspaceT']) def on_workspace(self): - if self.ui.general_defaults_form.general_gui_group.workspace_cb.get_value(): + if self.ui.general_defaults_form.general_app_set_group.workspace_cb.get_value(): self.plotcanvas.draw_workspace(workspace_size=self.defaults['global_workspaceT']) else: self.plotcanvas.delete_workspace() @@ -6633,13 +6654,13 @@ class App(QtCore.QObject): # self.save_defaults(silent=True) def on_workspace_toggle(self): - state = False if self.ui.general_defaults_form.general_gui_group.workspace_cb.get_value() else True + state = False if self.ui.general_defaults_form.general_app_set_group.workspace_cb.get_value() else True try: - self.ui.general_defaults_form.general_gui_group.workspace_cb.stateChanged.disconnect(self.on_workspace) + self.ui.general_defaults_form.general_app_set_group.workspace_cb.stateChanged.disconnect(self.on_workspace) except TypeError: pass - self.ui.general_defaults_form.general_gui_group.workspace_cb.set_value(state) - self.ui.general_defaults_form.general_gui_group.workspace_cb.stateChanged.connect(self.on_workspace) + self.ui.general_defaults_form.general_app_set_group.workspace_cb.set_value(state) + self.ui.general_defaults_form.general_app_set_group.workspace_cb.stateChanged.connect(self.on_workspace) self.on_workspace() def on_layout(self, index=None, lay=None): @@ -10874,13 +10895,11 @@ class App(QtCore.QObject): name = outname or filename.split('/')[-1].split('\\')[-1] self.new_object(obj_type, name, obj_init, autoselected=False) - self.progress.emit(20) # Register recent file self.file_opened.emit("dxf", filename) # GUI feedback self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) - self.progress.emit(100) def open_gerber(self, filename, outname=None): """ @@ -10956,7 +10975,6 @@ class App(QtCore.QObject): # How the object should be initialized def obj_init(excellon_obj, app_obj): - # self.progress.emit(20) try: ret = excellon_obj.parse_file(filename=filename) @@ -10969,7 +10987,6 @@ class App(QtCore.QObject): app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Cannot open file"), filename)) log.debug("Could not open Excellon object.") - self.progress.emit(0) # TODO: self and app_bjj mixed return "fail" except Exception: msg = '[ERROR_NOTCL] %s' % \ @@ -11611,6 +11628,11 @@ class App(QtCore.QObject): } + try: + image_opener = self.image_tool.import_image + except AttributeError: + image_opener = None + openers = { 'gerber': lambda fname: self.worker_task.emit({'fcn': self.open_gerber, 'params': [fname]}), 'excellon': lambda fname: self.worker_task.emit({'fcn': self.open_excellon, 'params': [fname]}), @@ -11621,7 +11643,7 @@ class App(QtCore.QObject): 'project': self.open_project, 'svg': self.import_svg, 'dxf': self.import_dxf, - 'image': self.image_tool.import_image, + 'image': image_opener, 'pdf': lambda fname: self.worker_task.emit({'fcn': self.pdf_tool.open_pdf, 'params': [fname]}) } @@ -11950,7 +11972,7 @@ class App(QtCore.QObject): def on_plotcanvas_setup(self, container=None): """ - This is doing the setup for the plot area (VisPy canvas) + This is doing the setup for the plot area (canvas) :param container: widget where to install the canvas :return: None @@ -11961,7 +11983,18 @@ class App(QtCore.QObject): plot_container = self.ui.right_layout if self.is_legacy is False: - self.plotcanvas = PlotCanvas(plot_container, self) + try: + self.plotcanvas = PlotCanvas(plot_container, self) + except Exception as er: + msg_txt = traceback.format_exc() + log.debug("App.on_plotcanvas_setup() failed -> %s" % str(er)) + log.debug("OpenGL canvas initialization failed with the following error.\n" + msg_txt) + msg = '[ERROR_NOTCL] %s' % _("An internal error has occurred. See shell.\n") + msg += _("OpenGL canvas initialization failed. HW or HW configuration not supported." + "Change the graphic engine to Legacy(2D) in Edit -> Preferences -> General tab.\n\n") + msg += msg_txt + self.inform.emit(msg) + return 'fail' else: self.plotcanvas = PlotCanvasLegacy(plot_container, self) @@ -12044,8 +12077,7 @@ class App(QtCore.QObject): log.debug("App.on_enable_sel_plot()") object_list = self.collection.get_selected() self.enable_plots(objects=object_list) - self.inform.emit('[success] %s' % - _("Selected plots enabled...")) + self.inform.emit('[success] %s' % _("Selected plots enabled...")) def on_disable_sel_plots(self): log.debug("App.on_disable_sel_plot()") @@ -12053,8 +12085,7 @@ class App(QtCore.QObject): # self.inform.emit(_("Disabling plots ...")) object_list = self.collection.get_selected() self.disable_plots(objects=object_list) - self.inform.emit('[success] %s' % - _("Selected plots disabled...")) + self.inform.emit('[success] %s' % _("Selected plots disabled...")) def enable_plots(self, objects): """ @@ -12070,6 +12101,20 @@ class App(QtCore.QObject): if obj.options['plot'] is False: obj.options.set_change_callback(lambda x: None) obj.options['plot'] = True + try: + # only the Gerber obj has on_plot_cb_click() method + obj.ui.plot_cb.stateChanged.disconnect(obj.on_plot_cb_click) + # disable this cb while disconnected, + # in case the operation takes time the user is not allowed to change it + obj.ui.plot_cb.setDisabled(True) + except AttributeError: + pass + obj.set_form_item("plot") + try: + obj.ui.plot_cb.stateChanged.connect(obj.on_plot_cb_click) + obj.ui.plot_cb.setDisabled(False) + except AttributeError: + pass obj.options.set_change_callback(obj.on_options_change) def worker_task(objs): @@ -12104,6 +12149,18 @@ class App(QtCore.QObject): if obj.options['plot'] is True: obj.options.set_change_callback(lambda x: None) obj.options['plot'] = False + try: + # only the Gerber obj has on_plot_cb_click() method + obj.ui.plot_cb.stateChanged.disconnect(obj.on_plot_cb_click) + obj.ui.plot_cb.setDisabled(True) + except AttributeError: + pass + obj.set_form_item("plot") + try: + obj.ui.plot_cb.stateChanged.connect(obj.on_plot_cb_click) + obj.ui.plot_cb.setDisabled(False) + except AttributeError: + pass obj.options.set_change_callback(obj.on_options_change) try: @@ -12242,7 +12299,7 @@ class App(QtCore.QObject): try: self.collection.get_active().read_form() except Exception as e: - self.log.debug("There was no active object. %s" % str(e)) + self.log.debug("save_project() --> There was no active object. Skipping read_form. %s" % str(e)) pass # Serialize the whole project diff --git a/FlatCAMObj.py b/FlatCAMObj.py index f28aa9ea..712d766a 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -1811,7 +1811,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber): visibility = kwargs['visible'] with self.app.proc_container.new(_("Plotting Apertures")): - self.app.progress.emit(30) def job_thread(app_obj): try: @@ -3080,7 +3079,6 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): def geo_init(geo_obj, app_obj): assert isinstance(geo_obj, FlatCAMGeometry), \ "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) - app_obj.progress.emit(20) # ## Add properties to the object @@ -3110,7 +3108,6 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): if use_thread: def geo_thread(app_obj): app_obj.new_object("geometry", outname, geo_init, plot=plot) - app_obj.progress.emit(100) # Create a promise with the new name self.app.collection.promise(outname) @@ -3173,7 +3170,6 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): def geo_init(geo_obj, app_obj): assert isinstance(geo_obj, FlatCAMGeometry), \ "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) - app_obj.progress.emit(20) # ## Add properties to the object @@ -3217,7 +3213,6 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): if use_thread: def geo_thread(app_obj): app_obj.new_object("geometry", outname + '_slot', geo_init, plot=plot) - app_obj.progress.emit(100) # Create a promise with the new name self.app.collection.promise(outname) @@ -3358,7 +3353,6 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): def job_thread(app_obj): with self.app.proc_container.new(_("Generating CNC Code")): app_obj.new_object("cncjob", job_name, job_init) - app_obj.progress.emit(100) # Create promise for the new name. self.app.collection.promise(job_name) @@ -5425,11 +5419,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): # Propagate options job_obj.options["tooldia"] = tooldia - app_obj.progress.emit(20) - job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"] job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"] - app_obj.progress.emit(40) job_obj.options['type'] = 'Geometry' job_obj.options['tool_dia'] = tooldia @@ -5459,24 +5450,18 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): pp_geometry_name=ppname_g ) - app_obj.progress.emit(50) # tell gcode_parse from which point to start drawing the lines depending on what kind of object is the # source of gcode job_obj.toolchange_xy_type = "geometry" job_obj.gcode_parse() - self.app.inform.emit('[success] %s' % - _("Finished G-Code processing...")) - - app_obj.progress.emit(80) + self.app.inform.emit('[success] %s' % _("Finished G-Code processing...")) if use_thread: # To be run in separate thread def job_thread(app_obj): with self.app.proc_container.new(_("Generating CNC Code")): app_obj.new_object("cncjob", outname, job_init, plot=plot) - app_obj.inform.emit('[success] %s: %s' % - (_("CNCjob created")), outname) - app_obj.progress.emit(100) + app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created")), outname) # Create a promise with the name self.app.collection.promise(outname) diff --git a/README.md b/README.md index b4d6ad46..db7ea631 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,29 @@ CAD program, and create G-Code for Isolation routing. ================================================= +8.01.2019 + +- working in NCC Tool +- selected rows in the Tools Tables will stay colored in blue after loosing focus instead of the default gray +- in NCC Tool the Tool name in the Parameters section will be the Tool ID in the Tool Table +- added an exception catch in case the plotcanvas init failed for the OpenGL graphic engine and warn user about what happened + +7.01.2019 + +- solved issue #368 - when using the Enable/Disable prj context menu entries the plotted status is not updated in the object properties +- updates in NCC Tool + +6.01.2019 + +- working on new NCC Tool + 2.01.2020 - started to rework the NCC Tool GUI in preparation for adding a Tool DB feature - for auto-completer, now clicking an entry in the completer popup will select that entry and insert it - made available only for Linux and Windows (not OSX) the starting of the thread that checks if another instance of FlatCAM is already running at the launch of FLatCAM +- modified Toggle Workspace function to work in the new Preferences UI configuration +- cleaned the app from progress signal usage since it is not used anymore 1.01.2020 diff --git a/flatcamEditors/FlatCAMExcEditor.py b/flatcamEditors/FlatCAMExcEditor.py index c97f6573..b77019cd 100644 --- a/flatcamEditors/FlatCAMExcEditor.py +++ b/flatcamEditors/FlatCAMExcEditor.py @@ -3305,7 +3305,6 @@ class FlatCAMExcEditor(QtCore.QObject): # How the object should be initialized def obj_init(excellon_obj, app_obj): - # self.progress.emit(20) excellon_obj.drills = deepcopy(new_drills) excellon_obj.tools = deepcopy(new_tools) excellon_obj.slots = deepcopy(new_slots) @@ -3335,12 +3334,9 @@ class FlatCAMExcEditor(QtCore.QObject): use_thread=False) except Exception as e: log.error("Error on Edited object creation: %s" % str(e)) - self.app.progress.emit(100) return - self.app.inform.emit('[success] %s' % - _("Excellon editing finished.")) - # self.progress.emit(100) + self.app.inform.emit('[success] %s' % _("Excellon editing finished.")) def on_tool_select(self, tool): """ diff --git a/flatcamEditors/FlatCAMGeoEditor.py b/flatcamEditors/FlatCAMGeoEditor.py index cccd2db4..20ccb225 100644 --- a/flatcamEditors/FlatCAMGeoEditor.py +++ b/flatcamEditors/FlatCAMGeoEditor.py @@ -1272,8 +1272,6 @@ class TransformEditorTool(FlatCAMTool): xmaximal = max(xmaxlist) ymaximal = max(ymaxlist) - self.app.progress.emit(20) - for sel_sha in shape_list: px = 0.5 * (xminimal + xmaximal) py = 0.5 * (yminimal + ymaximal) @@ -1284,11 +1282,7 @@ class TransformEditorTool(FlatCAMTool): # self.draw_app.transform_complete.emit() - self.app.inform.emit('[success] %s' % - _("Done. Rotate completed.")) - - self.app.progress.emit(100) - + self.app.inform.emit('[success] %s' % _("Done. Rotate completed.")) except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Rotation action was not executed"), str(e))) return @@ -1329,8 +1323,6 @@ class TransformEditorTool(FlatCAMTool): px = 0.5 * (xminimal + xmaximal) py = 0.5 * (yminimal + ymaximal) - self.app.progress.emit(20) - # execute mirroring for sha in shape_list: if axis is 'X': @@ -1347,8 +1339,6 @@ class TransformEditorTool(FlatCAMTool): # # self.draw_app.transform_complete.emit() - self.app.progress.emit(100) - except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Flip action was not executed"), str(e))) return @@ -1375,8 +1365,6 @@ class TransformEditorTool(FlatCAMTool): xminimal = min(xminlist) yminimal = min(yminlist) - self.app.progress.emit(20) - for sha in shape_list: if axis is 'X': sha.skew(num, 0, point=(xminimal, yminimal)) @@ -1388,12 +1376,9 @@ class TransformEditorTool(FlatCAMTool): # # self.draw_app.transform_complete.emit() if axis == 'X': - self.app.inform.emit('[success] %s...' % - _('Skew on the X axis done')) + self.app.inform.emit('[success] %s...' % _('Skew on the X axis done')) else: - self.app.inform.emit('[success] %s...' % - _('Skew on the Y axis done')) - self.app.progress.emit(100) + self.app.inform.emit('[success] %s...' % _('Skew on the Y axis done')) except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Skew action was not executed"), str(e))) @@ -1407,8 +1392,7 @@ class TransformEditorTool(FlatCAMTool): ymaxlist = [] if not shape_list: - self.app.inform.emit('[WARNING_NOTCL] %s' % - _("No shape selected. Please Select a shape to scale!")) + self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected. Please Select a shape to scale!")) return else: with self.app.proc_container.new(_("Applying Scale")): @@ -1427,8 +1411,6 @@ class TransformEditorTool(FlatCAMTool): xmaximal = max(xmaxlist) ymaximal = max(ymaxlist) - self.app.progress.emit(20) - if point is None: px = 0.5 * (xminimal + xmaximal) py = 0.5 * (yminimal + ymaximal) @@ -1445,12 +1427,9 @@ class TransformEditorTool(FlatCAMTool): # self.draw_app.transform_complete.emit() if str(axis) == 'X': - self.app.inform.emit('[success] %s...' % - _('Scale on the X axis done')) + self.app.inform.emit('[success] %s...' % _('Scale on the X axis done')) else: - self.app.inform.emit('[success] %s...' % - _('Scale on the Y axis done')) - self.app.progress.emit(100) + self.app.inform.emit('[success] %s...' % _('Scale on the Y axis done')) except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Scale action was not executed"), str(e))) return @@ -1467,8 +1446,6 @@ class TransformEditorTool(FlatCAMTool): else: with self.app.proc_container.new(_("Applying Offset")): try: - self.app.progress.emit(20) - for sha in shape_list: if axis is 'X': sha.offset((num, 0)) @@ -1477,12 +1454,9 @@ class TransformEditorTool(FlatCAMTool): self.draw_app.replot() if axis == 'X': - self.app.inform.emit('[success] %s...' % - _('Offset on the X axis done')) + self.app.inform.emit('[success] %s...' % _('Offset on the X axis done')) else: - self.app.inform.emit('[success] %s...' % - _('Offset on the Y axis done')) - self.app.progress.emit(100) + self.app.inform.emit('[success] %s...' % _('Offset on the Y axis done')) except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Offset action was not executed"), str(e))) diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py index c3477d2d..5d1b4ae5 100644 --- a/flatcamEditors/FlatCAMGrbEditor.py +++ b/flatcamEditors/FlatCAMGrbEditor.py @@ -5761,7 +5761,6 @@ class TransformEditorTool(FlatCAMTool): xmaximal = max(xmaxlist) ymaximal = max(ymaxlist) - self.app.progress.emit(20) px = 0.5 * (xminimal + xmaximal) py = 0.5 * (yminimal + ymaximal) @@ -5775,12 +5774,9 @@ class TransformEditorTool(FlatCAMTool): sel_el['clear'] = affinity.rotate(sel_el['clear'], angle=-num, origin=(px, py)) self.draw_app.plot_all() - self.app.inform.emit('[success] %s' % - _("Done. Rotate completed.")) - self.app.progress.emit(100) + self.app.inform.emit('[success] %s' % _("Done. Rotate completed.")) except Exception as e: - self.app.inform.emit('[ERROR_NOTCL] %s: %s' % - (_("Rotation action was not executed."), str(e))) + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Rotation action was not executed."), str(e))) return def on_flip(self, axis): @@ -5827,8 +5823,6 @@ class TransformEditorTool(FlatCAMTool): px = 0.5 * (xminimal + xmaximal) py = 0.5 * (yminimal + ymaximal) - self.app.progress.emit(20) - # execute mirroring for sel_el_shape in elem_list: sel_el = sel_el_shape.geo @@ -5851,8 +5845,6 @@ class TransformEditorTool(FlatCAMTool): self.app.inform.emit('[success] %s...' % _('Flip on the X axis done')) self.draw_app.plot_all() - self.app.progress.emit(100) - except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Flip action was not executed."), str(e))) @@ -5889,8 +5881,6 @@ class TransformEditorTool(FlatCAMTool): xminimal = min(xminlist) yminimal = min(yminlist) - self.app.progress.emit(20) - for sel_el_shape in elem_list: sel_el = sel_el_shape.geo if axis is 'X': @@ -5913,8 +5903,6 @@ class TransformEditorTool(FlatCAMTool): self.app.inform.emit('[success] %s...' % _('Skew on the X axis done')) else: self.app.inform.emit('[success] %s...' % _('Skew on the Y axis done')) - self.app.progress.emit(100) - except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Skew action was not executed."), str(e))) return @@ -5958,8 +5946,6 @@ class TransformEditorTool(FlatCAMTool): xmaximal = max(xmaxlist) ymaximal = max(ymaxlist) - self.app.progress.emit(20) - if point is None: px = 0.5 * (xminimal + xmaximal) py = 0.5 * (yminimal + ymaximal) diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py index ade4b856..111a9895 100644 --- a/flatcamGUI/FlatCAMGUI.py +++ b/flatcamGUI/FlatCAMGUI.py @@ -2221,9 +2221,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.infobar.addWidget(self.units_label) # disabled - self.progress_bar = QtWidgets.QProgressBar() - self.progress_bar.setMinimum(0) - self.progress_bar.setMaximum(100) + # self.progress_bar = QtWidgets.QProgressBar() + # self.progress_bar.setMinimum(0) + # self.progress_bar.setMaximum(100) # infobar.addWidget(self.progress_bar) # ######################################################################## @@ -2262,6 +2262,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.grb_editor_cmenu.menuAction().setVisible(False) self.e_editor_cmenu.menuAction().setVisible(False) + # ######################################################################## + # ######################## BUILD PREFERENCES ############################# + # ######################################################################## + self.general_defaults_form = GeneralPreferencesUI(decimals=self.decimals) self.gerber_defaults_form = GerberPreferencesUI(decimals=self.decimals) self.excellon_defaults_form = ExcellonPreferencesUI(decimals=self.decimals) @@ -2358,7 +2362,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): :param event: QT event to filter :return: """ - if self.general_defaults_form.general_app_set_group.toggle_tooltips_cb.get_value() is False: + if self.app.defaults["global_toggle_tooltips"] is False: if event.type() == QtCore.QEvent.ToolTip: return True else: diff --git a/flatcamGUI/GUIElements.py b/flatcamGUI/GUIElements.py index b604a414..f52aced9 100644 --- a/flatcamGUI/GUIElements.py +++ b/flatcamGUI/GUIElements.py @@ -2006,6 +2006,11 @@ class FCTable(QtWidgets.QTableWidget): def __init__(self, drag_drop=False, protected_rows=None, parent=None): super(FCTable, self).__init__(parent) + palette = QtGui.QPalette() + palette.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Highlight, + palette.color(QtGui.QPalette.Active, QtGui.QPalette.Highlight)) + self.setPalette(palette) + if drag_drop: self.setDragEnabled(True) self.setAcceptDrops(True) diff --git a/flatcamGUI/PreferencesUI.py b/flatcamGUI/PreferencesUI.py index ad16c99c..365fd17e 100644 --- a/flatcamGUI/PreferencesUI.py +++ b/flatcamGUI/PreferencesUI.py @@ -1661,18 +1661,13 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): separator_line = QtWidgets.QFrame() separator_line.setFrameShape(QtWidgets.QFrame.HLine) separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - self.layout.addWidget(separator_line) - - grid1 = QtWidgets.QGridLayout() - self.layout.addLayout(grid1) - grid1.setColumnStretch(0, 0) - grid1.setColumnStretch(1, 1) + grid0.addWidget(separator_line, 31, 0, 1, 2) self.pdf_param_label = QtWidgets.QLabel('%s:' % _("Text to PDF parameters")) self.pdf_param_label.setToolTip( _("Used when saving text in Code Editor or in FlatCAM Document objects.") ) - grid1.addWidget(self.pdf_param_label, 0, 0, 1, 2) + grid0.addWidget(self.pdf_param_label, 32, 0, 1, 2) # Top Margin value self.tmargin_entry = FCDoubleSpinner() @@ -1684,8 +1679,8 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): _("Distance between text body and the top of the PDF file.") ) - grid1.addWidget(self.tmargin_label, 1, 0) - grid1.addWidget(self.tmargin_entry, 1, 1) + grid0.addWidget(self.tmargin_label, 33, 0) + grid0.addWidget(self.tmargin_entry, 33, 1) # Bottom Margin value self.bmargin_entry = FCDoubleSpinner() @@ -1697,8 +1692,8 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): _("Distance between text body and the bottom of the PDF file.") ) - grid1.addWidget(self.bmargin_label, 2, 0) - grid1.addWidget(self.bmargin_entry, 2, 1) + grid0.addWidget(self.bmargin_label, 34, 0) + grid0.addWidget(self.bmargin_entry, 34, 1) # Left Margin value self.lmargin_entry = FCDoubleSpinner() @@ -1710,8 +1705,8 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): _("Distance between text body and the left of the PDF file.") ) - grid1.addWidget(self.lmargin_label, 3, 0) - grid1.addWidget(self.lmargin_entry, 3, 1) + grid0.addWidget(self.lmargin_label, 35, 0) + grid0.addWidget(self.lmargin_entry, 35, 1) # Right Margin value self.rmargin_entry = FCDoubleSpinner() @@ -1723,8 +1718,8 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): _("Distance between text body and the right of the PDF file.") ) - grid1.addWidget(self.rmargin_label, 4, 0) - grid1.addWidget(self.rmargin_entry, 4, 1) + grid0.addWidget(self.rmargin_label, 36, 0) + grid0.addWidget(self.rmargin_entry, 36, 1) self.layout.addStretch() diff --git a/flatcamTools/ToolNonCopperClear.py b/flatcamTools/ToolNonCopperClear.py index 00583859..9a6e047e 100644 --- a/flatcamTools/ToolNonCopperClear.py +++ b/flatcamTools/ToolNonCopperClear.py @@ -6,10 +6,11 @@ # ########################################################## from PyQt5 import QtWidgets, QtCore, QtGui + from FlatCAMTool import FlatCAMTool from flatcamGUI.GUIElements import FCCheckBox, FCDoubleSpinner, RadioSet, FCTable, FCInputDialog, FCButton from flatcamParsers.ParseGerber import Gerber -from FlatCAMObj import FlatCAMGeometry, FlatCAMGerber + import FlatCAMApp from copy import deepcopy @@ -175,6 +176,7 @@ class NonCopperClear(FlatCAMTool, Gerber): "- climb / best for precision milling and to reduce tool usage\n" "- conventional / useful when there is no backlash compensation") ) + self.milling_type_radio.setObjectName(_("Milling Type")) grid1.addWidget(self.milling_type_label, 0, 0) grid1.addWidget(self.milling_type_radio, 0, 1) @@ -218,7 +220,7 @@ class NonCopperClear(FlatCAMTool, Gerber): self.grid3.setColumnStretch(0, 0) self.grid3.setColumnStretch(1, 1) - self.tool_sel_label = QtWidgets.QLabel('%s' % _("Tool Selection")) + self.tool_sel_label = QtWidgets.QLabel('%s' % _("New Tool")) self.grid3.addWidget(self.tool_sel_label, 1, 0, 1, 2) # Tool Type Radio Button @@ -236,6 +238,8 @@ class NonCopperClear(FlatCAMTool, Gerber): "- 'V-shape'\n" "- Circular") ) + self.tool_type_radio.setObjectName(_("Tool Type")) + self.grid3.addWidget(self.tool_type_label, 2, 0) self.grid3.addWidget(self.tool_type_radio, 2, 1) @@ -245,7 +249,9 @@ class NonCopperClear(FlatCAMTool, Gerber): _("The tip diameter for V-Shape Tool")) self.tipdia_entry = FCDoubleSpinner() self.tipdia_entry.set_precision(self.decimals) + self.tipdia_entry.set_range(0.0000, 9999.9999) self.tipdia_entry.setSingleStep(0.1) + self.tipdia_entry.setObjectName(_("V-Tip Dia")) self.grid3.addWidget(self.tipdialabel, 3, 0) self.grid3.addWidget(self.tipdia_entry, 3, 1) @@ -257,7 +263,9 @@ class NonCopperClear(FlatCAMTool, Gerber): "In degree.")) self.tipangle_entry = FCDoubleSpinner() self.tipangle_entry.set_precision(self.decimals) + self.tipangle_entry.set_range(0.0000, 180.0000) self.tipangle_entry.setSingleStep(5) + self.tipangle_entry.setObjectName(_("V-Tip Angle")) self.grid3.addWidget(self.tipanglelabel, 4, 0) self.grid3.addWidget(self.tipangle_entry, 4, 1) @@ -271,6 +279,7 @@ class NonCopperClear(FlatCAMTool, Gerber): self.cutz_entry = FCDoubleSpinner() self.cutz_entry.set_precision(self.decimals) self.cutz_entry.set_range(-99999.9999, 0.0000) + self.cutz_entry.setObjectName(_("Cut Z")) self.cutz_entry.setToolTip( _("Depth of cut into material. Negative value.\n" @@ -288,17 +297,12 @@ class NonCopperClear(FlatCAMTool, Gerber): ) self.addtool_entry = FCDoubleSpinner() self.addtool_entry.set_precision(self.decimals) + self.addtool_entry.set_range(0.000, 9999.9999) + self.addtool_entry.setObjectName(_("Tool Dia")) self.grid3.addWidget(self.addtool_entry_lbl, 6, 0) self.grid3.addWidget(self.addtool_entry, 6, 1) - self.addtool_from_db_btn = QtWidgets.QPushButton(_('Add Tool from DataBase')) - self.addtool_from_db_btn.setToolTip( - _("Add a new tool to the Tool Table\n" - "from the Tool DataBase.") - ) - self.grid3.addWidget(self.addtool_from_db_btn, 7, 0, 1, 2) - hlay = QtWidgets.QHBoxLayout() self.addtool_btn = QtWidgets.QPushButton(_('Add')) @@ -316,7 +320,14 @@ class NonCopperClear(FlatCAMTool, Gerber): hlay.addWidget(self.addtool_btn) hlay.addWidget(self.deltool_btn) - self.grid3.addLayout(hlay, 8, 0, 1, 2) + self.grid3.addLayout(hlay, 7, 0, 1, 2) + + self.addtool_from_db_btn = QtWidgets.QPushButton(_('Add Tool from DataBase')) + self.addtool_from_db_btn.setToolTip( + _("Add a new tool to the Tool Table\n" + "from the Tool DataBase.") + ) + self.grid3.addWidget(self.addtool_from_db_btn, 8, 0, 1, 2) self.grid3.addWidget(QtWidgets.QLabel(''), 9, 0, 1, 2) @@ -351,16 +362,20 @@ class NonCopperClear(FlatCAMTool, Gerber): self.ncc_overlap_entry.setWrapping(True) self.ncc_overlap_entry.setRange(0.000, 99.9999) self.ncc_overlap_entry.setSingleStep(0.1) + self.ncc_overlap_entry.setObjectName(_("Overlap Rate")) self.grid3.addWidget(nccoverlabel, 12, 0) self.grid3.addWidget(self.ncc_overlap_entry, 12, 1) + # Margin nccmarginlabel = QtWidgets.QLabel('%s:' % _('Margin')) nccmarginlabel.setToolTip( _("Bounding box margin.") ) self.ncc_margin_entry = FCDoubleSpinner() self.ncc_margin_entry.set_precision(self.decimals) + self.ncc_margin_entry.set_range(-9999.9999, 9999.9999) + self.ncc_margin_entry.setObjectName(_("Margin")) self.grid3.addWidget(nccmarginlabel, 13, 0) self.grid3.addWidget(self.ncc_margin_entry, 13, 1) @@ -378,19 +393,25 @@ class NonCopperClear(FlatCAMTool, Gerber): {"label": _("Seed-based"), "value": "seed"}, {"label": _("Straight lines"), "value": "lines"} ], orientation='vertical', stretch=False) + self.ncc_method_radio.setObjectName(_("Method")) self.grid3.addWidget(methodlabel, 14, 0) self.grid3.addWidget(self.ncc_method_radio, 14, 1) # Connect lines self.ncc_connect_cb = FCCheckBox('%s' % _("Connect")) + self.ncc_connect_cb.setObjectName(_("Connect")) + self.ncc_connect_cb.setToolTip( _("Draw lines between resulting\n" "segments to minimize tool lifts.") ) self.grid3.addWidget(self.ncc_connect_cb, 15, 0, 1, 2) + # Contour self.ncc_contour_cb = FCCheckBox('%s' % _("Contour")) + self.ncc_contour_cb.setObjectName(_("Contour")) + self.ncc_contour_cb.setToolTip( _("Cut around the perimeter of the polygon\n" "to trim rough edges.") @@ -399,6 +420,8 @@ class NonCopperClear(FlatCAMTool, Gerber): # Rest Machining self.ncc_rest_cb = FCCheckBox('%s' % _("Rest Machining")) + self.ncc_rest_cb.setObjectName(_("Rest Machining")) + self.ncc_rest_cb.setToolTip( _("If checked, use 'rest machining'.\n" "Basically it will clear copper outside PCB features,\n" @@ -413,6 +436,8 @@ class NonCopperClear(FlatCAMTool, Gerber): # ## NCC Offset choice self.ncc_choice_offset_cb = FCCheckBox('%s' % _("Offset")) + self.ncc_choice_offset_cb.setObjectName(_("Offset")) + self.ncc_choice_offset_cb.setToolTip( _("If used, it will add an offset to the copper features.\n" "The copper clearing will finish to a distance\n" @@ -433,6 +458,7 @@ class NonCopperClear(FlatCAMTool, Gerber): self.ncc_offset_spinner.set_range(0.00, 10.00) self.ncc_offset_spinner.set_precision(4) self.ncc_offset_spinner.setWrapping(True) + self.ncc_offset_spinner.setObjectName(_("Offset value")) units = self.app.defaults['units'].upper() if units == 'MM': @@ -452,7 +478,9 @@ class NonCopperClear(FlatCAMTool, Gerber): {"label": _("Area Selection"), "value": "area"}, {'label': _("Reference Object"), 'value': 'box'} ], orientation='vertical', stretch=False) - self.reference_label = QtWidgets.QLabel(_("Reference:")) + self.reference_radio.setObjectName(_("Reference")) + + self.reference_label = QtWidgets.QLabel('%s:' % _("Reference")) self.reference_label.setToolTip( _("- 'Itself' - the non copper clearing extent is based on the object that is copper cleared.\n " "- 'Area Selection' - left mouse click to start selection of the area to be painted.\n" @@ -586,6 +614,32 @@ class NonCopperClear(FlatCAMTool, Gerber): self.tooldia = None + self.form_fields = { + "nccoverlap": self.ncc_overlap_entry, + "nccmargin": self.ncc_margin_entry, + "nccmethod": self.ncc_method_radio, + "nccconnect": self.ncc_connect_cb, + "ncccontour": self.ncc_contour_cb, + "nccrest": self.ncc_rest_cb, + "nccoffset": self.ncc_choice_offset_cb, + "nccoffset_value": self.ncc_offset_spinner, + "nccref": self.reference_radio, + "milling_type": self.milling_type_radio + } + + self.name2option = { + _('Overlap Rate'): "nccoverlap", + _('Margin'): "nccmargin", + _('Method'): "nccmethod", + _("Connect"): "nccconnect", + _("Contour"): "ncccontour", + _("Rest Machining"): "nccrest", + _("Offset"): "nccoffset", + _("Offset value"): "nccoffset_value", + _("Reference"): "nccref", + _('Milling Type'): "milling_type", + } + # ############################################################################# # ############################ SGINALS ######################################## # ############################################################################# @@ -618,7 +672,7 @@ class NonCopperClear(FlatCAMTool, Gerber): self.update_ui() def update_ui(self, row=None): - self.ui_disconnect() + self.blockSignals(True) if row is None: try: @@ -634,7 +688,7 @@ class NonCopperClear(FlatCAMTool, Gerber): # populate the form with the data from the tool associated with the row parameter try: item = self.tools_table.item(current_row, 3) - if type(item) is not None: + if item is not None: tooluid = int(item.text()) else: return @@ -644,7 +698,7 @@ class NonCopperClear(FlatCAMTool, Gerber): # update the QLabel that shows for which Tool we have the parameters in the UI form self.tool_data_label.setText( - "%s: %s %d" % (_('Parameters for'), _("Tool"), tooluid) + "%s: %s %d" % (_('Parameters for'), _("Tool"), (current_row + 1)) ) try: @@ -654,70 +708,46 @@ class NonCopperClear(FlatCAMTool, Gerber): for key, value in tooluid_value.items(): if key == 'data': form_value_storage = tooluid_value[key] - self.update_form(form_value_storage) - if key == 'offset_value': - # update the offset value in the entry even if the entry is hidden - self.ncc_offset_spinner.set_value(tooluid_value[key]) - - if key == 'tool_type' and value == 'V': - self.update_cutz() + self.storage_to_form(form_value_storage) except Exception as e: log.debug("FlatCAMObj ---> update_ui() " + str(e)) - self.ui_connect() - def update_cutz(self): - vdia = float(self.tipdia_entry.get_value()) - half_vangle = float(self.tipangle_entry.get_value()) / 2 + self.blockSignals(False) - row = self.tools_table.currentRow() - tool_uid_item = self.tools_table.item(row, 3) - if tool_uid_item is None: - return - tool_uid = int(tool_uid_item.text()) - - tool_dia_item = self.tools_table.item(row, 1) - if tool_dia_item is None: - return - tooldia = float(tool_dia_item.text()) - - new_cutz = (tooldia - vdia) / (2 * math.tan(math.radians(half_vangle))) - new_cutz = float('%.*f' % (self.decimals, new_cutz)) * -1.0 # this value has to be negative - - self.cutz_entry.set_value(new_cutz) - - # store the new CutZ value into storage (self.ncc_tools) - for tooluid_key, tooluid_value in self.ncc_tools.items(): - if int(tooluid_key) == tool_uid: - tooluid_value['data']['cutz'] = new_cutz - - def on_tooltable_cellwidget_change(self): - cw = self.sender() - cw_index = self.tools_table.indexAt(cw.pos()) - cw_row = cw_index.row() - cw_col = cw_index.column() - current_uid = int(self.tools_table.item(cw_row, 3).text()) - - # store the text of the cellWidget that changed it's index in the self.tools - for tooluid_key, tooluid_value in self.ncc_tools.items(): - if int(tooluid_key) == current_uid: - cb_txt = cw.currentText() - if cw_col == 2: - tooluid_value['tool_type'] = cb_txt - - def update_form(self, dict_storage): + def storage_to_form(self, dict_storage): for form_key in self.form_fields: for storage_key in dict_storage: if form_key == storage_key: try: self.form_fields[form_key].set_value(dict_storage[form_key]) - except Exception as e: - log.debug(str(e)) + except Exception: + pass - # this is done here because those buttons control through OptionalInputSelection if some entry's are Enabled - # or not. But due of using the ui_disconnect() status is no longer updated and I had to do it here - self.ui.ois_dwell_geo.on_cb_change() - self.ui.ois_mpass_geo.on_cb_change() - self.ui.ois_tcz_geo.on_cb_change() + def form_to_storage(self): + if self.tools_table.rowCount() == 0: + # there is no tool in tool table so we can't save the GUI elements values to storage + return + + self.blockSignals(True) + + widget_changed = self.sender() + wdg_objname = widget_changed.objectName() + option_changed = self.name2option[wdg_objname] + + row = self.tools_table.currentRow() + if row < 0: + row = 0 + tooluid_item = int(self.tools_table.item(row, 3).text()) + + for tooluid_key, tooluid_val in self.ncc_tools.items(): + if int(tooluid_key) == tooluid_item: + new_option_value = self.form_fields[option_changed].get_value() + if option_changed in tooluid_val: + tooluid_val[option_changed] = new_option_value + if option_changed in tooluid_val['data']: + tooluid_val['data'][option_changed] = new_option_value + + self.blockSignals(False) def on_apply_param_to_all_clicked(self): if self.tools_table.rowCount() == 0: @@ -725,7 +755,7 @@ class NonCopperClear(FlatCAMTool, Gerber): log.debug("NonCopperClear.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.") return - self.ui_disconnect() + self.blockSignals(True) row = self.tools_table.currentRow() if row < 0: @@ -736,8 +766,8 @@ class NonCopperClear(FlatCAMTool, Gerber): type_item = self.tools_table.cellWidget(row, 2).currentText() operation_type_item = self.ui.geo_tools_table.cellWidget(row, 4).currentText() - offset_item = self.ncc_choice_offset_cb.get_value() - offset_value_item = float(self.ncc_offset_spinner.get_value()) + nccoffset_item = self.ncc_choice_offset_cb.get_value() + nccoffset_value_item = float(self.ncc_offset_spinner.get_value()) # this new dict will hold the actual useful data, another dict that is the value of key 'data' temp_tools = {} @@ -746,16 +776,6 @@ class NonCopperClear(FlatCAMTool, Gerber): for tooluid_key, tooluid_value in self.ncc_tools.items(): for key, value in tooluid_value.items(): - if key == 'tooldia': - temp_dia[key] = tooldia_item - # update the 'offset', 'type' and 'tool_type' sections - if key == 'offset': - temp_dia[key] = offset_item - if key == 'type': - temp_dia[key] = type_item - if key == 'offset_value': - temp_dia[key] = offset_value_item - if key == 'data': # update the 'data' section for data_key in tooluid_value[key].keys(): @@ -769,8 +789,10 @@ class NonCopperClear(FlatCAMTool, Gerber): temp_dia[key] = deepcopy(temp_data) temp_data.clear() - if key == 'solid_geometry': + elif key == 'solid_geometry': temp_dia[key] = deepcopy(self.tools[tooluid_key]['solid_geometry']) + else: + temp_dia[key] = deepcopy(value) temp_tools[tooluid_key] = deepcopy(temp_dia) @@ -778,93 +800,7 @@ class NonCopperClear(FlatCAMTool, Gerber): self.ncc_tools = deepcopy(temp_tools) temp_tools.clear() - self.ui_connect() - - def gui_form_to_storage(self): - if self.tools_table.rowCount() == 0: - # there is no tool in tool table so we can't save the GUI elements values to storage - log.debug("NonCopperClear.gui_form_to_storage() --> no tool in Tools Table, aborting.") - return - - self.ui_disconnect() - widget_changed = self.sender() - try: - widget_idx = self.grid3.indexOf(widget_changed) - except Exception as e: - return - - # those are the indexes for the V-Tip Dia and V-Tip Angle, if edited calculate the new Cut Z - if widget_idx == 1 or widget_idx == 3: - self.update_cutz() - - # the original connect() function of the OptionalInputSelection is no longer working because of the - # ui_diconnect() so I use this 'hack' - # if isinstance(widget_changed, FCCheckBox): - # if widget_changed.text() == 'Multi-Depth:': - # self.ui.ois_mpass_geo.on_cb_change() - # - # if widget_changed.text() == 'Tool change': - # self.ui.ois_tcz_geo.on_cb_change() - # - # if widget_changed.text() == 'Dwell:': - # self.ui.ois_dwell_geo.on_cb_change() - - row = self.tools_table.currentRow() - if row < 0: - row = 0 - - # store all the data associated with the row parameter to the self.tools storage - tooldia_item = float(self.tools_table.item(row, 1).text()) - tool_type_item = self.tools_table.cellWidget(row, 2).currentText() - operation_type_item = self.ui.geo_tools_table.cellWidget(row, 4).currentText() - - offset_item = self.ncc_choice_offset_cb.get_value() - offset_value_item = float(self.ncc_offset_spinner.get_value()) - - tooluid_item = int(self.ui.geo_tools_table.item(row, 3).text()) - - # this new dict will hold the actual useful data, another dict that is the value of key 'data' - temp_tools = {} - temp_dia = {} - temp_data = {} - - for tooluid_key, tooluid_value in self.tools.items(): - if int(tooluid_key) == tooluid_item: - for key, value in tooluid_value.items(): - if key == 'tooldia': - temp_dia[key] = tooldia_item - # update the 'offset', 'type' and 'tool_type' sections - if key == 'offset': - temp_dia[key] = offset_item - if key == 'tool_type': - temp_dia[key] = tool_type_item - if key == 'offset_value': - temp_dia[key] = offset_value_item - - if key == 'data': - # update the 'data' section - for data_key in tooluid_value[key].keys(): - for form_key, form_value in self.form_fields.items(): - if form_key == data_key: - temp_data[data_key] = form_value.get_value() - # make sure we make a copy of the keys not in the form (we may use 'data' keys that are - # updated from self.app.defaults - if data_key not in self.form_fields: - temp_data[data_key] = value[data_key] - temp_dia[key] = deepcopy(temp_data) - temp_data.clear() - - if key == 'solid_geometry': - temp_dia[key] = deepcopy(self.ncc_tools[tooluid_key]['solid_geometry']) - - temp_tools[tooluid_key] = deepcopy(temp_dia) - else: - temp_tools[tooluid_key] = deepcopy(tooluid_value) - - self.ncc_tools.clear() - self.ncc_tools = deepcopy(temp_tools) - temp_tools.clear() - self.ui_connect() + self.blockSignals(False) def on_add_tool_by_key(self): tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"), @@ -931,6 +867,9 @@ class NonCopperClear(FlatCAMTool, Gerber): self.ncc_connect_cb.set_value(self.app.defaults["tools_nccconnect"]) self.ncc_contour_cb.set_value(self.app.defaults["tools_ncccontour"]) self.ncc_rest_cb.set_value(self.app.defaults["tools_nccrest"]) + self.ncc_choice_offset_cb.set_value(self.app.defaults["tools_ncc_offset_choice"]) + self.ncc_offset_spinner.set_value(self.app.defaults["tools_ncc_offset_value"]) + self.reference_radio.set_value(self.app.defaults["tools_nccref"]) self.milling_type_radio.set_value(self.app.defaults["tools_nccmilling_type"]) self.cutz_entry.set_value(self.app.defaults["tools_ncccutz"]) @@ -943,7 +882,7 @@ class NonCopperClear(FlatCAMTool, Gerber): # init the working variables self.default_data.clear() - self.default_data.update({ + self.default_data = { "name": '_ncc', "plot": self.app.defaults["geometry_plot"], "cutz": float(self.cutz_entry.get_value()), @@ -967,21 +906,17 @@ class NonCopperClear(FlatCAMTool, Gerber): "toolchangexy": self.app.defaults["geometry_toolchangexy"], "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"], - "nccmargin": self.app.defaults["tools_nccmargin"], "nccmethod": self.app.defaults["tools_nccmethod"], "nccconnect": self.app.defaults["tools_nccconnect"], "ncccontour": self.app.defaults["tools_ncccontour"], "nccoverlap": self.app.defaults["tools_nccoverlap"], - "nccrest": self.app.defaults["tools_nccrest"] - }) + "nccrest": self.app.defaults["tools_nccrest"], + "nccref": self.app.defaults["tools_nccref"], + "nccoffset": self.app.defaults["tools_ncc_offset_choice"], + "nccoffset_value": self.app.defaults["tools_ncc_offset_value"], + + } try: dias = [float(self.app.defaults["tools_ncctools"])] @@ -1118,21 +1053,56 @@ class NonCopperClear(FlatCAMTool, Gerber): self.tools_table.itemChanged.connect(self.on_tool_edit) for row in range(self.tools_table.rowCount()): - for col in [2, 4]: - self.tools_table.cellWidget(row, col).currentIndexChanged.connect(self.on_tooltable_cellwidget_change) + try: + self.tools_table.cellWidget(row, 2).currentIndexChanged.connect(self.on_tooltable_cellwidget_change) + except AttributeError: + pass + + try: + self.tools_table.cellWidget(row, 4).currentIndexChanged.connect(self.on_tooltable_cellwidget_change) + except AttributeError: + pass self.tool_type_radio.activated_custom.connect(self.on_tool_type) + # first disconnect + for opt in self.form_fields: + current_widget = self.form_fields[opt] + if isinstance(current_widget, FCCheckBox): + try: + current_widget.stateChanged.disconnect(self.form_to_storage) + except (TypeError, ValueError): + pass + if isinstance(current_widget, RadioSet): + try: + current_widget.activated_custom.disconnect(self.form_to_storage) + except (TypeError, ValueError): + pass + elif isinstance(current_widget, FCDoubleSpinner): + try: + current_widget.returnPressed.disconnect(self.form_to_storage) + except (TypeError, ValueError): + pass + + for opt in self.form_fields: + current_widget = self.form_fields[opt] + if isinstance(current_widget, FCCheckBox): + current_widget.stateChanged.connect(self.form_to_storage) + if isinstance(current_widget, RadioSet): + current_widget.activated_custom.connect(self.form_to_storage) + elif isinstance(current_widget, FCDoubleSpinner): + current_widget.returnPressed.connect(self.form_to_storage) + def ui_disconnect(self): try: # if connected, disconnect the signal from the slot on item_changed as it creates issues - self.tools_table.itemChanged.disconnect(self.on_tool_edit) + self.tools_table.itemChanged.disconnect() except (TypeError, AttributeError): pass try: # if connected, disconnect the signal from the slot on item_changed as it creates issues - self.tool_type_radio.activated_custom.disconnect(self.on_tool_type) + self.tool_type_radio.activated_custom.disconnect() except (TypeError, AttributeError): pass @@ -1143,6 +1113,24 @@ class NonCopperClear(FlatCAMTool, Gerber): except (TypeError, AttributeError): pass + for opt in self.form_fields: + current_widget = self.form_fields[opt] + if isinstance(current_widget, FCCheckBox): + try: + current_widget.stateChanged.disconnect() + except (TypeError, ValueError): + pass + if isinstance(current_widget, RadioSet): + try: + current_widget.activated_custom.disconnect() + except (TypeError, ValueError): + pass + elif isinstance(current_widget, FCDoubleSpinner): + try: + current_widget.returnPressed.disconnect() + except (TypeError, ValueError): + pass + def on_combo_box_type(self): obj_type = self.box_combo_type.currentIndex() self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex())) @@ -1189,32 +1177,30 @@ class NonCopperClear(FlatCAMTool, Gerber): current_uid = int(self.tools_table.item(cw_row, 3).text()) - hide_iso_type = True - for row in range(self.tools_table.rowCount()): - if self.tools_table.cellWidget(row, 4).currentText() == 'iso_op': - hide_iso_type = False - break - - if hide_iso_type is False: - self.milling_type_label.show() - self.milling_type_radio.show() - else: - self.milling_type_label.hide() - self.milling_type_radio.hide() - # if the sender is in the column with index 2 then we update the tool_type key if cw_col == 2: tt = cw.currentText() - if tt == 'V': - typ = 'Iso' - else: - typ = "Rough" + typ = 'Iso' if tt == 'V' else "Rough" self.ncc_tools[current_uid].update({ 'type': typ, 'tool_type': tt, }) + if cw_col == 4: + op = cw.currentText() + + if op == 'iso_op': + self.milling_type_label.show() + self.milling_type_radio.show() + else: + self.milling_type_label.hide() + self.milling_type_radio.hide() + + self.ncc_tools[current_uid].update({ + 'operation': op + }) + def on_tool_type(self, val): if val == 'V': self.addtool_entry_lbl.setDisabled(True) @@ -1254,7 +1240,8 @@ class NonCopperClear(FlatCAMTool, Gerber): return float(self.addtool_entry.get_value()) def on_tool_add(self, dia=None, muted=None): - self.ui_disconnect() + self.blockSignals(True) + self.units = self.app.defaults['units'].upper() if dia: @@ -1296,7 +1283,8 @@ class NonCopperClear(FlatCAMTool, Gerber): if muted is None: self.app.inform.emit('[WARNING_NOTCL] %s' % _("Adding tool cancelled. Tool already in Tool Table.")) # self.tools_table.itemChanged.connect(self.on_tool_edit) - self.ui_connect() + self.blockSignals(False) + return else: if muted is None: @@ -1314,10 +1302,12 @@ class NonCopperClear(FlatCAMTool, Gerber): } }) + self.blockSignals(False) + self.build_ui() def on_tool_edit(self): - self.ui_disconnect() + self.blockSignals(True) old_tool_dia = '' tool_dias = [] @@ -1335,8 +1325,8 @@ class NonCopperClear(FlatCAMTool, Gerber): try: new_tool_dia = float(self.tools_table.item(row, 1).text().replace(',', '.')) except ValueError: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, " - "use a number.")) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number.")) + self.blockSignals(False) return tooluid = int(self.tools_table.item(row, 3).text()) @@ -1345,6 +1335,7 @@ class NonCopperClear(FlatCAMTool, Gerber): if new_tool_dia not in tool_dias: self.ncc_tools[tooluid]['tooldia'] = new_tool_dia self.app.inform.emit('[success] %s' % _("Tool from Tool Table was edited.")) + self.blockSignals(False) self.build_ui() return else: @@ -1357,6 +1348,7 @@ class NonCopperClear(FlatCAMTool, Gerber): restore_dia_item.setText(str(old_tool_dia)) self.app.inform.emit('[WARNING_NOTCL] %s' % _("Edit cancelled. " "New diameter value is already in the Tool Table.")) + self.blockSignals(False) self.build_ui() def on_tool_delete(self, rows_to_delete=None, all_tools=None): @@ -1367,12 +1359,13 @@ class NonCopperClear(FlatCAMTool, Gerber): :param all_tools: delete all tools in the tool table :return: """ - self.ui_disconnect() + self.blockSignals(True) deleted_tools_list = [] if all_tools: self.paint_tools.clear() + self.blockSignals(False) self.build_ui() return @@ -1382,10 +1375,13 @@ class NonCopperClear(FlatCAMTool, Gerber): tooluid_del = int(self.tools_table.item(row, 3).text()) deleted_tools_list.append(tooluid_del) except TypeError: - deleted_tools_list.append(rows_to_delete) + tooluid_del = int(self.tools_table.item(rows_to_delete, 3).text()) + deleted_tools_list.append(tooluid_del) for t in deleted_tools_list: self.ncc_tools.pop(t, None) + + self.blockSignals(False) self.build_ui() return @@ -1403,11 +1399,13 @@ class NonCopperClear(FlatCAMTool, Gerber): except AttributeError: self.app.inform.emit('[WARNING_NOTCL] %s' % _("Delete failed. Select a tool to delete.")) + self.blockSignals(False) return except Exception as e: log.debug(str(e)) self.app.inform.emit('[success] %s' % _("Tool(s) deleted from Tool Table.")) + self.blockSignals(False) self.build_ui() def on_ncc_click(self): @@ -1720,7 +1718,6 @@ class NonCopperClear(FlatCAMTool, Gerber): ncc_select = self.reference_radio.get_value() overlap = overlap if overlap is not None else float(self.app.defaults["tools_nccoverlap"]) / 100.0 - connect = connect if connect else self.app.defaults["tools_nccconnect"] contour = contour if contour else self.app.defaults["tools_ncccontour"] order = order if order else self.ncc_order_radio.get_value() @@ -1818,7 +1815,7 @@ class NonCopperClear(FlatCAMTool, Gerber): elif ncc_select == 'box': geo_n = ncc_sel_obj.solid_geometry - if isinstance(ncc_sel_obj, FlatCAMGeometry): + if ncc_sel_obj.kind == 'geometry': try: __ = iter(geo_n) except Exception as e: @@ -1833,7 +1830,7 @@ class NonCopperClear(FlatCAMTool, Gerber): geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)) bounding_box = cascaded_union(geo_buff_list) - elif isinstance(ncc_sel_obj, FlatCAMGerber): + elif ncc_sel_obj.kind == 'gerber': geo_n = cascaded_union(geo_n).convex_hull bounding_box = cascaded_union(self.ncc_obj.solid_geometry).convex_hull.intersection(geo_n) bounding_box = bounding_box.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre) @@ -1856,7 +1853,7 @@ class NonCopperClear(FlatCAMTool, Gerber): # Initializes the new geometry object ###################################################### # ########################################################################################## def gen_clear_area(geo_obj, app_obj): - assert isinstance(geo_obj, FlatCAMGeometry), \ + assert geo_obj.kind == 'geometry', \ "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) # provide the app with a way to process the GUI events when in a blocking loop @@ -1898,7 +1895,7 @@ class NonCopperClear(FlatCAMTool, Gerber): log.debug("NCC Tool. Calculate 'empty' area.") self.app.inform.emit(_("NCC Tool. Calculate 'empty' area.")) - if isinstance(ncc_obj, FlatCAMGerber) and not isotooldia: + if ncc_obj.kind == 'gerber' and not isotooldia: # unfortunately for this function to work time efficient, # if the Gerber was loaded without buffering then it require the buffering now. if self.app.defaults['gerber_buffering'] == 'no': @@ -1919,7 +1916,7 @@ class NonCopperClear(FlatCAMTool, Gerber): app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Could not get the extent of the area to be non copper cleared.")) return 'fail' - elif isinstance(ncc_obj, FlatCAMGerber) and isotooldia: + elif ncc_obj.kind == 'gerber' and isotooldia: isolated_geo = [] # unfortunately for this function to work time efficient, @@ -2024,7 +2021,7 @@ class NonCopperClear(FlatCAMTool, Gerber): _("Isolation geometry is broken. Margin is less than isolation tool diameter.")) return 'fail' - elif isinstance(ncc_obj, FlatCAMGeometry): + elif ncc_obj.kind == 'geometry': sol_geo = cascaded_union(ncc_obj.solid_geometry) if has_offset is True: app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) @@ -2295,7 +2292,7 @@ class NonCopperClear(FlatCAMTool, Gerber): # Initializes the new geometry object for the case of the rest-machining #################### # ########################################################################################### def gen_clear_area_rest(geo_obj, app_obj): - assert isinstance(geo_obj, FlatCAMGeometry), \ + assert geo_obj.kind == 'geometry', \ "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) log.debug("NCC Tool. Rest machining copper clearing task started.") @@ -2328,7 +2325,7 @@ class NonCopperClear(FlatCAMTool, Gerber): # ################################################################################################### # Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry ## # ################################################################################################### - if isinstance(ncc_obj, FlatCAMGerber) and not isotooldia: + if ncc_obj.kind == 'gerber' and not isotooldia: sol_geo = ncc_obj.solid_geometry if has_offset is True: app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) @@ -2342,7 +2339,7 @@ class NonCopperClear(FlatCAMTool, Gerber): app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Could not get the extent of the area to be non copper cleared.")) return 'fail' - elif isinstance(ncc_obj, FlatCAMGerber) and isotooldia: + elif ncc_obj.kind == 'gerber' and isotooldia: isolated_geo = [] self.solid_geometry = ncc_obj.solid_geometry @@ -2444,7 +2441,7 @@ class NonCopperClear(FlatCAMTool, Gerber): _("Isolation geometry is broken. Margin is less than isolation tool diameter.")) return 'fail' - elif isinstance(ncc_obj, FlatCAMGeometry): + elif ncc_obj.kind == 'geometry': sol_geo = cascaded_union(ncc_obj.solid_geometry) if has_offset is True: app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) @@ -2715,359 +2712,1003 @@ class NonCopperClear(FlatCAMTool, Gerber): else: job_thread(app_obj=self.app) - # def on_ncc(self): - # - # # Prepare non-copper polygons - # if self.reference_radio.get_value() == 'area': - # geo_n = self.sel_rect - # - # geo_buff_list = [] - # for poly in geo_n: - # geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)) - # bounding_box = cascaded_union(geo_buff_list) - # else: - # geo_n = self.bound_obj.solid_geometry - # - # try: - # if isinstance(geo_n, MultiPolygon): - # env_obj = geo_n.convex_hull - # elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \ - # (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon): - # env_obj = cascaded_union(geo_n) - # else: - # env_obj = cascaded_union(geo_n) - # env_obj = env_obj.convex_hull - # bounding_box = env_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre) - # except Exception as e: - # log.debug("NonCopperClear.on_ncc() --> %s" % str(e)) - # self.app.inform.emit(_("[ERROR_NOTCL] No object available.")) - # return - # - # # calculate the empty area by subtracting the solid_geometry from the object bounding box geometry - # if isinstance(self.ncc_obj, FlatCAMGerber): - # if self.ncc_choice_offset_cb.isChecked(): - # self.app.inform.emit(_("[WARNING_NOTCL] Buffering ...")) - # offseted_geo = self.ncc_obj.solid_geometry.buffer(distance=ncc_offset_value) - # self.app.inform.emit(_("[success] Buffering finished ...")) - # empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box) - # else: - # empty = self.get_ncc_empty_area(target=self.ncc_obj.solid_geometry, boundary=bounding_box) - # elif isinstance(self.ncc_obj, FlatCAMGeometry): - # sol_geo = cascaded_union(self.ncc_obj.solid_geometry) - # if self.ncc_choice_offset_cb.isChecked(): - # self.app.inform.emit(_("[WARNING_NOTCL] Buffering ...")) - # offseted_geo = sol_geo.buffer(distance=ncc_offset_value) - # self.app.inform.emit(_("[success] Buffering finished ...")) - # empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box) - # else: - # empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) - # else: - # self.inform.emit(_('[ERROR_NOTCL] The selected object is not suitable for copper clearing.')) - # return - # - # if type(empty) is Polygon: - # empty = MultiPolygon([empty]) - # - # if empty.is_empty: - # self.app.inform.emit(_("[ERROR_NOTCL] Could not get the extent of the area to be non copper cleared.")) - # return - # - # # clear non copper using standard algorithm - # if clearing_method is False: - # self.clear_non_copper( - # empty=empty, - # over=over, - # pol_method=pol_method, - # connect=connect, - # contour=contour - # ) - # # clear non copper using rest machining algorithm - # else: - # self.clear_non_copper_rest( - # empty=empty, - # over=over, - # pol_method=pol_method, - # connect=connect, - # contour=contour - # ) - # - # def clear_non_copper(self, empty, over, pol_method, outname=None, connect=True, contour=True): - # - # name = outname if outname else self.obj_name + "_ncc" - # - # # Sort tools in descending order - # sorted_tools = [] - # for k, v in self.ncc_tools.items(): - # sorted_tools.append(float('%.4f' % float(v['tooldia']))) - # - # order = self.ncc_order_radio.get_value() - # if order == 'fwd': - # sorted_tools.sort(reverse=False) - # elif order == 'rev': - # sorted_tools.sort(reverse=True) - # else: - # pass - # - # # Do job in background - # proc = self.app.proc_container.new(_("Clearing Non-Copper areas.")) - # - # def initialize(geo_obj, app_obj): - # assert isinstance(geo_obj, FlatCAMGeometry), \ - # "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) - # - # cleared_geo = [] - # # Already cleared area - # cleared = MultiPolygon() - # - # # flag for polygons not cleared - # app_obj.poly_not_cleared = False - # - # # Generate area for each tool - # offset = sum(sorted_tools) - # current_uid = int(1) - # tool = eval(self.app.defaults["tools_ncctools"])[0] - # - # for tool in sorted_tools: - # self.app.inform.emit(_('[success] Non-Copper Clearing with ToolDia = %s started.') % str(tool)) - # cleared_geo[:] = [] - # - # # Get remaining tools offset - # offset -= (tool - 1e-12) - # - # # Area to clear - # area = empty.buffer(-offset) - # try: - # area = area.difference(cleared) - # except Exception as e: - # continue - # - # # Transform area to MultiPolygon - # if type(area) is Polygon: - # area = MultiPolygon([area]) - # - # if area.geoms: - # if len(area.geoms) > 0: - # for p in area.geoms: - # try: - # if pol_method == 'standard': - # cp = self.clear_polygon(p, tool, self.app.defaults["gerber_circle_steps"], - # overlap=over, contour=contour, connect=connect) - # elif pol_method == 'seed': - # cp = self.clear_polygon2(p, tool, self.app.defaults["gerber_circle_steps"], - # overlap=over, contour=contour, connect=connect) - # else: - # cp = self.clear_polygon3(p, tool, self.app.defaults["gerber_circle_steps"], - # overlap=over, contour=contour, connect=connect) - # if cp: - # cleared_geo += list(cp.get_objects()) - # except Exception as e: - # log.warning("Polygon can not be cleared. %s" % str(e)) - # app_obj.poly_not_cleared = True - # continue - # - # # check if there is a geometry at all in the cleared geometry - # if cleared_geo: - # # Overall cleared area - # cleared = empty.buffer(-offset * (1 + over)).buffer(-tool / 1.999999).buffer( - # tool / 1.999999) - # - # # clean-up cleared geo - # cleared = cleared.buffer(0) - # - # # find the tooluid associated with the current tool_dia so we know where to add the tool - # # solid_geometry - # for k, v in self.ncc_tools.items(): - # if float('%.4f' % v['tooldia']) == float('%.4f' % tool): - # current_uid = int(k) - # - # # add the solid_geometry to the current too in self.paint_tools dictionary - # # and then reset the temporary list that stored that solid_geometry - # v['solid_geometry'] = deepcopy(cleared_geo) - # v['data']['name'] = name - # break - # geo_obj.tools[current_uid] = dict(self.ncc_tools[current_uid]) - # else: - # log.debug("There are no geometries in the cleared polygon.") - # - # geo_obj.options["cnctooldia"] = str(tool) - # geo_obj.multigeo = True - # - # def job_thread(app_obj): - # try: - # app_obj.new_object("geometry", name, initialize) - # except Exception as e: - # proc.done() - # self.app.inform.emit(_('[ERROR_NOTCL] NCCTool.clear_non_copper() --> %s') % str(e)) - # return - # proc.done() - # - # if app_obj.poly_not_cleared is False: - # self.app.inform.emit(_('[success] NCC Tool finished.')) - # else: - # self.app.inform.emit(_('[WARNING_NOTCL] NCC Tool finished but some PCB features could not be cleared. ' - # 'Check the result.')) - # # reset the variable for next use - # app_obj.poly_not_cleared = False - # - # # focus on Selected Tab - # self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab) - # - # # Promise object with the new name - # self.app.collection.promise(name) - # - # # Background - # self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) - # - # # clear copper with 'rest-machining' algorithm - # def clear_non_copper_rest(self, empty, over, pol_method, outname=None, connect=True, contour=True): - # - # name = outname if outname is not None else self.obj_name + "_ncc_rm" - # - # # Sort tools in descending order - # sorted_tools = [] - # for k, v in self.ncc_tools.items(): - # sorted_tools.append(float('%.4f' % float(v['tooldia']))) - # sorted_tools.sort(reverse=True) - # - # # Do job in background - # proc = self.app.proc_container.new(_("Clearing Non-Copper areas.")) - # - # def initialize_rm(geo_obj, app_obj): - # assert isinstance(geo_obj, FlatCAMGeometry), \ - # "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) - # - # cleared_geo = [] - # cleared_by_last_tool = [] - # rest_geo = [] - # current_uid = 1 - # tool = eval(self.app.defaults["tools_ncctools"])[0] - # - # # repurposed flag for final object, geo_obj. True if it has any solid_geometry, False if not. - # app_obj.poly_not_cleared = True - # - # area = empty.buffer(0) - # # Generate area for each tool - # while sorted_tools: - # tool = sorted_tools.pop(0) - # self.app.inform.emit(_('[success] Non-Copper Rest Clearing with ToolDia = %s started.') % str(tool)) - # - # tool_used = tool - 1e-12 - # cleared_geo[:] = [] - # - # # Area to clear - # for poly in cleared_by_last_tool: - # try: - # area = area.difference(poly) - # except Exception as e: - # pass - # cleared_by_last_tool[:] = [] - # - # # Transform area to MultiPolygon - # if type(area) is Polygon: - # area = MultiPolygon([area]) - # - # # add the rest that was not able to be cleared previously; area is a MultyPolygon - # # and rest_geo it's a list - # allparts = [p.buffer(0) for p in area.geoms] - # allparts += deepcopy(rest_geo) - # rest_geo[:] = [] - # area = MultiPolygon(deepcopy(allparts)) - # allparts[:] = [] - # - # if area.geoms: - # if len(area.geoms) > 0: - # for p in area.geoms: - # try: - # if pol_method == 'standard': - # cp = self.clear_polygon(p, tool_used, self.app.defaults["gerber_circle_steps"], - # overlap=over, contour=contour, connect=connect) - # elif pol_method == 'seed': - # cp = self.clear_polygon2(p, tool_used, - # self.app.defaults["gerber_circle_steps"], - # overlap=over, contour=contour, connect=connect) - # else: - # cp = self.clear_polygon3(p, tool_used, - # self.app.defaults["gerber_circle_steps"], - # overlap=over, contour=contour, connect=connect) - # cleared_geo.append(list(cp.get_objects())) - # except: - # log.warning("Polygon can't be cleared.") - # # this polygon should be added to a list and then try clear it with a smaller tool - # rest_geo.append(p) - # - # # check if there is a geometry at all in the cleared geometry - # if cleared_geo: - # # Overall cleared area - # cleared_area = list(self.flatten_list(cleared_geo)) - # - # # cleared = MultiPolygon([p.buffer(tool_used / 2).buffer(-tool_used / 2) - # # for p in cleared_area]) - # - # # here we store the poly's already processed in the original geometry by the current tool - # # into cleared_by_last_tool list - # # this will be sustracted from the original geometry_to_be_cleared and make data for - # # the next tool - # buffer_value = tool_used / 2 - # for p in cleared_area: - # poly = p.buffer(buffer_value) - # cleared_by_last_tool.append(poly) - # - # # find the tooluid associated with the current tool_dia so we know - # # where to add the tool solid_geometry - # for k, v in self.ncc_tools.items(): - # if float('%.4f' % v['tooldia']) == float('%.4f' % tool): - # current_uid = int(k) - # - # # add the solid_geometry to the current too in self.paint_tools dictionary - # # and then reset the temporary list that stored that solid_geometry - # v['solid_geometry'] = deepcopy(cleared_area) - # v['data']['name'] = name - # cleared_area[:] = [] - # break - # - # geo_obj.tools[current_uid] = dict(self.ncc_tools[current_uid]) - # else: - # log.debug("There are no geometries in the cleared polygon.") - # - # geo_obj.multigeo = True - # geo_obj.options["cnctooldia"] = str(tool) - # - # # check to see if geo_obj.tools is empty - # # it will be updated only if there is a solid_geometry for tools - # if geo_obj.tools: - # return - # else: - # # I will use this variable for this purpose although it was meant for something else - # # signal that we have no geo in the object therefore don't create it - # app_obj.poly_not_cleared = False - # return "fail" - # - # def job_thread(app_obj): - # try: - # app_obj.new_object("geometry", name, initialize_rm) - # except Exception as e: - # proc.done() - # app_obj.inform.emit(_('[ERROR_NOTCL] NCCTool.clear_non_copper_rest() --> %s') % str(e)) - # return - # - # if app_obj.poly_not_cleared is True: - # app_obj.inform.emit('[success] NCC Tool finished.') - # # focus on Selected Tab - # app_obj.ui.notebook.setCurrentWidget(self.app.ui.selected_tab) - # else: - # app_obj.inform.emit(_('[ERROR_NOTCL] NCC Tool finished but could not clear the object ' - # 'with current settings.')) - # # focus on Project Tab - # app_obj.ui.notebook.setCurrentWidget(self.app.ui.project_tab) - # proc.done() - # # reset the variable for next use - # app_obj.poly_not_cleared = False - # - # # Promise object with the new name - # self.app.collection.promise(name) - # - # # Background - # self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) + def clear_copper_tcl(self, ncc_obj, + sel_obj=None, + ncctooldia=None, + isotooldia=None, + margin=None, + has_offset=None, + offset=None, + select_method=None, + outname=None, + overlap=None, + connect=None, + contour=None, + order=None, + method=None, + rest=None, + tools_storage=None, + plot=True, + run_threaded=False): + """ + Clear the excess copper from the entire object. To be used only in a TCL command. + + :param ncc_obj: ncc cleared object + :param ncctooldia: a tuple or single element made out of diameters of the tools to be used to ncc clear + :param isotooldia: a tuple or single element made out of diameters of the tools to be used for isolation + :param overlap: value by which the paths will overlap + :param order: if the tools are ordered and how + :param select_method: if to do ncc on the whole object, on an defined area or on an area defined by + another object + :param has_offset: True if an offset is needed + :param offset: distance from the copper features where the copper clearing is stopping + :param margin: a border around cleared 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 rest: True if to use rest-machining + :param tools_storage: whether to use the current tools_storage self.ncc_tools or a different one. + Usage of the different one is related to when this function is called from a TcL command. + :param plot: if True after the job is finished the result will be plotted, else it will not. + :param run_threaded: If True the method will be run in a threaded way suitable for GUI usage; if False it will + run non-threaded for TclShell usage + :return: + """ + if run_threaded: + proc = self.app.proc_container.new(_("Non-Copper clearing ...")) + else: + self.app.proc_container.view.set_busy(_("Non-Copper clearing ...")) + QtWidgets.QApplication.processEvents() + + # ##################################################################### + # ####### Read the parameters ######################################### + # ##################################################################### + + units = self.app.defaults['units'] + + log.debug("NCC Tool started. Reading parameters.") + self.app.inform.emit(_("NCC Tool started. Reading parameters.")) + + ncc_method = method + ncc_margin = margin + ncc_select = select_method + overlap = overlap + + connect = connect + contour = contour + order = order + + if tools_storage is not None: + tools_storage = tools_storage + else: + tools_storage = self.ncc_tools + + ncc_offset = 0.0 + if has_offset is True: + ncc_offset = offset + + # ###################################################################################################### + # # Read the tooldia parameter and create a sorted list out them - they may be more than one diameter ## + # ###################################################################################################### + sorted_tools = [] + try: + sorted_tools = [float(eval(dia)) for dia in ncctooldia.split(",") if dia != ''] + except AttributeError: + if not isinstance(ncctooldia, list): + sorted_tools = [float(ncctooldia)] + else: + sorted_tools = ncctooldia + + # ############################################################################################################## + # Prepare non-copper polygons. Create the bounding box area from which the copper features will be subtracted ## + # ############################################################################################################## + log.debug("NCC Tool. Preparing non-copper polygons.") + self.app.inform.emit(_("NCC Tool. Preparing non-copper polygons.")) + + try: + if sel_obj is None or sel_obj == 'itself': + ncc_sel_obj = ncc_obj + else: + ncc_sel_obj = sel_obj + except Exception as e: + log.debug("NonCopperClear.clear_copper() --> %s" % str(e)) + return 'fail' + + bounding_box = None + if ncc_select == 'itself': + geo_n = ncc_sel_obj.solid_geometry + + try: + if isinstance(geo_n, MultiPolygon): + env_obj = geo_n.convex_hull + elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \ + (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon): + env_obj = cascaded_union(geo_n) + else: + env_obj = cascaded_union(geo_n) + env_obj = env_obj.convex_hull + + bounding_box = env_obj.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre) + except Exception as e: + log.debug("NonCopperClear.clear_copper() 'itself' --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("No object available.")) + return 'fail' + + elif ncc_select == 'area': + geo_n = cascaded_union(self.sel_rect) + try: + __ = iter(geo_n) + except Exception as e: + log.debug("NonCopperClear.clear_copper() 'area' --> %s" % str(e)) + geo_n = [geo_n] + + geo_buff_list = [] + for poly in geo_n: + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)) + + bounding_box = cascaded_union(geo_buff_list) + + elif ncc_select == 'box': + geo_n = ncc_sel_obj.solid_geometry + if ncc_sel_obj.kind == 'geometry': + try: + __ = iter(geo_n) + except Exception as e: + log.debug("NonCopperClear.clear_copper() 'box' --> %s" % str(e)) + geo_n = [geo_n] + + geo_buff_list = [] + for poly in geo_n: + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)) + + bounding_box = cascaded_union(geo_buff_list) + elif ncc_sel_obj.kind == 'gerber': + geo_n = cascaded_union(geo_n).convex_hull + bounding_box = cascaded_union(self.ncc_obj.solid_geometry).convex_hull.intersection(geo_n) + bounding_box = bounding_box.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre) + else: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("The reference object type is not supported.")) + return 'fail' + + log.debug("NCC Tool. Finished non-copper polygons.") + # ######################################################################################################## + # set the name for the future Geometry object + # I do it here because it is also stored inside the gen_clear_area() and gen_clear_area_rest() methods + # ######################################################################################################## + rest_machining_choice = rest + if rest_machining_choice is True: + name = outname if outname is not None else self.obj_name + "_ncc_rm" + else: + name = outname if outname is not None else self.obj_name + "_ncc" + + # ########################################################################################## + # Initializes the new geometry object ###################################################### + # ########################################################################################## + def gen_clear_area(geo_obj, app_obj): + assert geo_obj.kind == 'geometry', \ + "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) + + # provide the app with a way to process the GUI events when in a blocking loop + if not run_threaded: + QtWidgets.QApplication.processEvents() + + log.debug("NCC Tool. Normal copper clearing task started.") + self.app.inform.emit(_("NCC Tool. Finished non-copper polygons. Normal copper clearing task started.")) + + # a flag to signal that the isolation is broken by the bounding box in 'area' and 'box' cases + # will store the number of tools for which the isolation is broken + warning_flag = 0 + + if order == 'fwd': + sorted_tools.sort(reverse=False) + elif order == 'rev': + sorted_tools.sort(reverse=True) + else: + pass + + cleared_geo = [] + # Already cleared area + cleared = MultiPolygon() + + # flag for polygons not cleared + app_obj.poly_not_cleared = False + + # Generate area for each tool + offset = sum(sorted_tools) + current_uid = int(1) + try: + tool = eval(self.app.defaults["tools_ncctools"])[0] + except TypeError: + tool = eval(self.app.defaults["tools_ncctools"]) + + # ################################################################################################### + # Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry ## + # ################################################################################################### + log.debug("NCC Tool. Calculate 'empty' area.") + self.app.inform.emit(_("NCC Tool. Calculate 'empty' area.")) + + if ncc_obj.kind == 'gerber' and not isotooldia: + # unfortunately for this function to work time efficient, + # if the Gerber was loaded without buffering then it require the buffering now. + if self.app.defaults['gerber_buffering'] == 'no': + sol_geo = ncc_obj.solid_geometry.buffer(0) + else: + sol_geo = ncc_obj.solid_geometry + + if has_offset is True: + app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) + sol_geo = sol_geo.buffer(distance=ncc_offset) + app_obj.inform.emit('[success] %s ...' % _("Buffering finished")) + + empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) + if empty == 'fail': + return 'fail' + + if empty.is_empty: + app_obj.inform.emit('[ERROR_NOTCL] %s' % + _("Could not get the extent of the area to be non copper cleared.")) + return 'fail' + elif ncc_obj.kind == 'gerber' and isotooldia: + isolated_geo = [] + + # unfortunately for this function to work time efficient, + # if the Gerber was loaded without buffering then it require the buffering now. + if self.app.defaults['gerber_buffering'] == 'no': + self.solid_geometry = ncc_obj.solid_geometry.buffer(0) + else: + self.solid_geometry = ncc_obj.solid_geometry + + # if milling type is climb then the move is counter-clockwise around features + milling_type = self.app.defaults["tools_nccmilling_type"] + + for tool_iso in isotooldia: + new_geometry = [] + + if milling_type == 'cl': + isolated_geo = self.generate_envelope(tool_iso / 2, 1) + else: + isolated_geo = self.generate_envelope(tool_iso / 2, 0) + + if isolated_geo == 'fail': + app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated.")) + else: + if ncc_margin < tool_iso: + app_obj.inform.emit('[WARNING_NOTCL] %s' % _("Isolation geometry is broken. Margin is less " + "than isolation tool diameter.")) + try: + for geo_elem in isolated_geo: + # 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 isinstance(geo_elem, Polygon): + for ring in self.poly2rings(geo_elem): + new_geo = ring.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, MultiPolygon): + for poly in geo_elem: + for ring in self.poly2rings(poly): + new_geo = ring.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, LineString): + new_geo = geo_elem.intersection(bounding_box) + if new_geo: + if not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, MultiLineString): + for line_elem in geo_elem: + new_geo = line_elem.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + except TypeError: + if isinstance(isolated_geo, Polygon): + for ring in self.poly2rings(isolated_geo): + new_geo = ring.intersection(bounding_box) + if new_geo: + if not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(isolated_geo, LineString): + new_geo = isolated_geo.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(isolated_geo, MultiLineString): + for line_elem in isolated_geo: + new_geo = line_elem.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + + # a MultiLineString geometry element will show that the isolation is broken for this tool + for geo_e in new_geometry: + if type(geo_e) == MultiLineString: + warning_flag += 1 + break + + for k, v in tools_storage.items(): + if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, + tool_iso)): + current_uid = int(k) + # add the solid_geometry to the current too in self.paint_tools dictionary + # and then reset the temporary list that stored that solid_geometry + v['solid_geometry'] = deepcopy(new_geometry) + v['data']['name'] = name + break + geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) + + sol_geo = cascaded_union(isolated_geo) + if has_offset is True: + app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) + sol_geo = sol_geo.buffer(distance=ncc_offset) + app_obj.inform.emit('[success] %s ...' % _("Buffering finished")) + empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) + if empty == 'fail': + return 'fail' + + if empty.is_empty: + app_obj.inform.emit('[ERROR_NOTCL] %s' % + _("Isolation geometry is broken. Margin is less than isolation tool diameter.")) + return 'fail' + + elif ncc_obj.kind == 'geometry': + sol_geo = cascaded_union(ncc_obj.solid_geometry) + if has_offset is True: + app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) + sol_geo = sol_geo.buffer(distance=ncc_offset) + app_obj.inform.emit('[success] %s ...' % _("Buffering finished")) + empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) + if empty == 'fail': + return 'fail' + + if empty.is_empty: + app_obj.inform.emit('[ERROR_NOTCL] %s' % + _("Could not get the extent of the area to be non copper cleared.")) + return 'fail' + + else: + app_obj.inform.emit('[ERROR_NOTCL] %s' % _('The selected object is not suitable for copper clearing.')) + return 'fail' + + if type(empty) is Polygon: + empty = MultiPolygon([empty]) + + log.debug("NCC Tool. Finished calculation of 'empty' area.") + self.app.inform.emit(_("NCC Tool. Finished calculation of 'empty' area.")) + + # COPPER CLEARING # + cp = None + for tool in sorted_tools: + log.debug("Starting geometry processing for tool: %s" % str(tool)) + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + + app_obj.inform.emit('[success] %s = %s%s %s' % ( + _('NCC Tool clearing with tool diameter'), str(tool), units.lower(), _('started.')) + ) + app_obj.proc_container.update_view_text(' %d%%' % 0) + + cleared_geo[:] = [] + + # Get remaining tools offset + offset -= (tool - 1e-12) + + # Area to clear + area = empty.buffer(-offset) + try: + area = area.difference(cleared) + except Exception as e: + continue + + # Transform area to MultiPolygon + if type(area) is Polygon: + area = MultiPolygon([area]) + + # variables to display the percentage of work done + geo_len = len(area.geoms) + + old_disp_number = 0 + log.warning("Total number of polygons to be cleared. %s" % str(geo_len)) + + if area.geoms: + if len(area.geoms) > 0: + pol_nr = 0 + for p in area.geoms: + # 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 + + # clean the polygon + p = p.buffer(0) + + if p is not None and p.is_valid: + poly_processed = list() + try: + for pol in p: + if pol is not None and isinstance(pol, Polygon): + if ncc_method == 'standard': + cp = self.clear_polygon(pol, tool, + self.grb_circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) + elif ncc_method == 'seed': + cp = self.clear_polygon2(pol, tool, + self.grb_circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) + else: + cp = self.clear_polygon3(pol, tool, + self.grb_circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) + if cp: + cleared_geo += list(cp.get_objects()) + poly_processed.append(True) + else: + poly_processed.append(False) + log.warning("Polygon in MultiPolygon can not be cleared.") + else: + log.warning("Geo in Iterable can not be cleared because it is not Polygon. " + "It is: %s" % str(type(pol))) + except TypeError: + if isinstance(p, Polygon): + if ncc_method == 'standard': + cp = self.clear_polygon(p, tool, self.grb_circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) + elif ncc_method == 'seed': + cp = self.clear_polygon2(p, tool, self.grb_circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) + else: + cp = self.clear_polygon3(p, tool, self.grb_circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) + if cp: + cleared_geo += list(cp.get_objects()) + poly_processed.append(True) + else: + poly_processed.append(False) + log.warning("Polygon can not be cleared.") + else: + log.warning("Geo can not be cleared because it is: %s" % str(type(p))) + + 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 + + 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 + # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number)) + + # check if there is a geometry at all in the cleared geometry + if cleared_geo: + # Overall cleared area + cleared = empty.buffer(-offset * (1 + overlap)).buffer(-tool / 1.999999).buffer( + tool / 1.999999) + + # clean-up cleared geo + cleared = cleared.buffer(0) + + # find the tooluid associated with the current tool_dia so we know where to add the tool + # solid_geometry + for k, v in tools_storage.items(): + if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, + tool)): + current_uid = int(k) + + # add the solid_geometry to the current too in self.paint_tools dictionary + # and then reset the temporary list that stored that solid_geometry + v['solid_geometry'] = deepcopy(cleared_geo) + v['data']['name'] = name + break + geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) + else: + log.debug("There are no geometries in the cleared polygon.") + + # delete tools with empty geometry + # look for keys in the tools_storage dict that have 'solid_geometry' values empty + for uid, uid_val in list(tools_storage.items()): + try: + # if the solid_geometry (type=list) is empty + if not uid_val['solid_geometry']: + tools_storage.pop(uid, None) + except KeyError: + tools_storage.pop(uid, None) + + geo_obj.options["cnctooldia"] = str(tool) + + geo_obj.multigeo = True + geo_obj.tools.clear() + geo_obj.tools = dict(tools_storage) + + # test if at least one tool has solid_geometry. If no tool has solid_geometry we raise an Exception + has_solid_geo = 0 + for tooluid in geo_obj.tools: + if geo_obj.tools[tooluid]['solid_geometry']: + has_solid_geo += 1 + if has_solid_geo == 0: + app_obj.inform.emit('[ERROR] %s' % + _("There is no NCC 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 'fail' + + # check to see if geo_obj.tools is empty + # it will be updated only if there is a solid_geometry for tools + if geo_obj.tools: + if warning_flag == 0: + self.app.inform.emit('[success] %s' % _("NCC Tool clear all done.")) + else: + self.app.inform.emit('[WARNING] %s: %s %s.' % ( + _("NCC Tool clear all done but the copper features isolation is broken for"), + str(warning_flag), + _("tools"))) + return + + # create the solid_geometry + geo_obj.solid_geometry = list() + for tooluid in geo_obj.tools: + if geo_obj.tools[tooluid]['solid_geometry']: + try: + for geo in geo_obj.tools[tooluid]['solid_geometry']: + geo_obj.solid_geometry.append(geo) + except TypeError: + geo_obj.solid_geometry.append(geo_obj.tools[tooluid]['solid_geometry']) + else: + # I will use this variable for this purpose although it was meant for something else + # signal that we have no geo in the object therefore don't create it + app_obj.poly_not_cleared = False + return "fail" + + # ########################################################################################### + # Initializes the new geometry object for the case of the rest-machining #################### + # ########################################################################################### + def gen_clear_area_rest(geo_obj, app_obj): + assert geo_obj.kind == 'geometry', \ + "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) + + log.debug("NCC Tool. Rest machining copper clearing task started.") + app_obj.inform.emit('_(NCC Tool. Rest machining copper clearing task started.') + + # provide the app with a way to process the GUI events when in a blocking loop + if not run_threaded: + QtWidgets.QApplication.processEvents() + + # a flag to signal that the isolation is broken by the bounding box in 'area' and 'box' cases + # will store the number of tools for which the isolation is broken + warning_flag = 0 + + sorted_tools.sort(reverse=True) + + cleared_geo = [] + cleared_by_last_tool = [] + rest_geo = [] + current_uid = 1 + try: + tool = eval(self.app.defaults["tools_ncctools"])[0] + except TypeError: + tool = eval(self.app.defaults["tools_ncctools"]) + + # repurposed flag for final object, geo_obj. True if it has any solid_geometry, False if not. + app_obj.poly_not_cleared = True + log.debug("NCC Tool. Calculate 'empty' area.") + app_obj.inform.emit("NCC Tool. Calculate 'empty' area.") + + # ################################################################################################### + # Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry ## + # ################################################################################################### + if ncc_obj.kind == 'gerber' and not isotooldia: + sol_geo = ncc_obj.solid_geometry + if has_offset is True: + app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) + sol_geo = sol_geo.buffer(distance=ncc_offset) + app_obj.inform.emit('[success] %s ...' % _("Buffering finished")) + empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) + if empty == 'fail': + return 'fail' + + if empty.is_empty: + app_obj.inform.emit('[ERROR_NOTCL] %s' % + _("Could not get the extent of the area to be non copper cleared.")) + return 'fail' + elif ncc_obj.kind == 'gerber' and isotooldia: + isolated_geo = [] + self.solid_geometry = ncc_obj.solid_geometry + + # if milling type is climb then the move is counter-clockwise around features + milling_type = self.app.defaults["tools_nccmilling_type"] + + for tool_iso in isotooldia: + new_geometry = [] + + if milling_type == 'cl': + isolated_geo = self.generate_envelope(tool_iso, 1) + else: + isolated_geo = self.generate_envelope(tool_iso, 0) + + if isolated_geo == 'fail': + app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated.")) + else: + app_obj.inform.emit('[WARNING_NOTCL] %s' % _("Isolation geometry is broken. Margin is less " + "than isolation tool diameter.")) + + try: + for geo_elem in isolated_geo: + # 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 isinstance(geo_elem, Polygon): + for ring in self.poly2rings(geo_elem): + new_geo = ring.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, MultiPolygon): + for poly in geo_elem: + for ring in self.poly2rings(poly): + new_geo = ring.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, LineString): + new_geo = geo_elem.intersection(bounding_box) + if new_geo: + if not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, MultiLineString): + for line_elem in geo_elem: + new_geo = line_elem.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + except TypeError: + try: + if isinstance(isolated_geo, Polygon): + for ring in self.poly2rings(isolated_geo): + new_geo = ring.intersection(bounding_box) + if new_geo: + if not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(isolated_geo, LineString): + new_geo = isolated_geo.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(isolated_geo, MultiLineString): + for line_elem in isolated_geo: + new_geo = line_elem.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + except Exception as e: + pass + + # a MultiLineString geometry element will show that the isolation is broken for this tool + for geo_e in new_geometry: + if type(geo_e) == MultiLineString: + warning_flag += 1 + break + + for k, v in tools_storage.items(): + if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, + tool_iso)): + current_uid = int(k) + # add the solid_geometry to the current too in self.paint_tools dictionary + # and then reset the temporary list that stored that solid_geometry + v['solid_geometry'] = deepcopy(new_geometry) + v['data']['name'] = name + break + geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) + + sol_geo = cascaded_union(isolated_geo) + if has_offset is True: + app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) + sol_geo = sol_geo.buffer(distance=ncc_offset) + app_obj.inform.emit('[success] %s ...' % _("Buffering finished")) + empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) + if empty == 'fail': + return 'fail' + + if empty.is_empty: + app_obj.inform.emit('[ERROR_NOTCL] %s' % + _("Isolation geometry is broken. Margin is less than isolation tool diameter.")) + return 'fail' + + elif ncc_obj.kind == 'geometry': + sol_geo = cascaded_union(ncc_obj.solid_geometry) + if has_offset is True: + app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) + sol_geo = sol_geo.buffer(distance=ncc_offset) + app_obj.inform.emit('[success] %s ...' % _("Buffering finished")) + empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) + if empty == 'fail': + return 'fail' + + if empty.is_empty: + app_obj.inform.emit('[ERROR_NOTCL] %s' % + _("Could not get the extent of the area to be non copper cleared.")) + return 'fail' + else: + app_obj.inform.emit('[ERROR_NOTCL] %s' % _('The selected object is not suitable for copper clearing.')) + return + + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + if type(empty) is Polygon: + empty = MultiPolygon([empty]) + + area = empty.buffer(0) + + log.debug("NCC Tool. Finished calculation of 'empty' area.") + app_obj.inform.emit("NCC Tool. Finished calculation of 'empty' area.") + + # Generate area for each tool + while sorted_tools: + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + tool = sorted_tools.pop(0) + log.debug("Starting geometry processing for tool: %s" % str(tool)) + + app_obj.inform.emit('[success] %s = %s%s %s' % ( + _('NCC Tool clearing with tool diameter'), str(tool), units.lower(), _('started.')) + ) + app_obj.proc_container.update_view_text(' %d%%' % 0) + + tool_used = tool - 1e-12 + cleared_geo[:] = [] + + # Area to clear + for poly in cleared_by_last_tool: + # 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 + try: + area = area.difference(poly) + except Exception as e: + pass + cleared_by_last_tool[:] = [] + + # Transform area to MultiPolygon + if type(area) is Polygon: + area = MultiPolygon([area]) + + # add the rest that was not able to be cleared previously; area is a MultyPolygon + # and rest_geo it's a list + allparts = [p.buffer(0) for p in area.geoms] + allparts += deepcopy(rest_geo) + rest_geo[:] = [] + area = MultiPolygon(deepcopy(allparts)) + allparts[:] = [] + + # variables to display the percentage of work done + geo_len = len(area.geoms) + disp_number = 0 + old_disp_number = 0 + log.warning("Total number of polygons to be cleared. %s" % str(geo_len)) + + if area.geoms: + if len(area.geoms) > 0: + pol_nr = 0 + for p in area.geoms: + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + # clean the polygon + p = p.buffer(0) + + if p is not None and p.is_valid: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + + if isinstance(p, Polygon): + try: + if ncc_method == 'standard': + cp = self.clear_polygon(p, tool_used, + self.grb_circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) + elif ncc_method == 'seed': + cp = self.clear_polygon2(p, tool_used, + self.grb_circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) + else: + cp = self.clear_polygon3(p, tool_used, + self.grb_circle_steps, + overlap=overlap, contour=contour, connect=connect, + prog_plot=False) + cleared_geo.append(list(cp.get_objects())) + except Exception as e: + log.warning("Polygon can't be cleared. %s" % str(e)) + # this polygon should be added to a list and then try clear it with + # a smaller tool + rest_geo.append(p) + elif isinstance(p, MultiPolygon): + for poly in p: + if poly is not None: + # provide the app with a way to process the GUI events when + # in a blocking loop + QtWidgets.QApplication.processEvents() + + try: + if ncc_method == 'standard': + cp = self.clear_polygon(poly, tool_used, + self.grb_circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) + elif ncc_method == 'seed': + cp = self.clear_polygon2(poly, tool_used, + self.grb_circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) + else: + cp = self.clear_polygon3(poly, tool_used, + self.grb_circle_steps, + overlap=overlap, contour=contour, + connect=connect, + prog_plot=False) + cleared_geo.append(list(cp.get_objects())) + except Exception as e: + log.warning("Polygon can't be cleared. %s" % str(e)) + # this polygon should be added to a list and then try clear it with + # a smaller tool + rest_geo.append(poly) + + 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 + # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number)) + + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + # check if there is a geometry at all in the cleared geometry + if cleared_geo: + # Overall cleared area + cleared_area = list(self.flatten_list(cleared_geo)) + + # cleared = MultiPolygon([p.buffer(tool_used / 2).buffer(-tool_used / 2) + # for p in cleared_area]) + + # here we store the poly's already processed in the original geometry by the current tool + # into cleared_by_last_tool list + # this will be sutracted from the original geometry_to_be_cleared and make data for + # the next tool + buffer_value = tool_used / 2 + for p in cleared_area: + if self.app.abort_flag: + # graceful abort requested by the user + raise FlatCAMApp.GracefulException + + poly = p.buffer(buffer_value) + cleared_by_last_tool.append(poly) + + # find the tooluid associated with the current tool_dia so we know + # where to add the tool solid_geometry + for k, v in tools_storage.items(): + if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, + tool)): + current_uid = int(k) + + # add the solid_geometry to the current too in self.paint_tools dictionary + # and then reset the temporary list that stored that solid_geometry + v['solid_geometry'] = deepcopy(cleared_area) + v['data']['name'] = name + cleared_area[:] = [] + break + + geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) + else: + log.debug("There are no geometries in the cleared polygon.") + + geo_obj.multigeo = True + geo_obj.options["cnctooldia"] = str(tool) + + # check to see if geo_obj.tools is empty + # it will be updated only if there is a solid_geometry for tools + if geo_obj.tools: + if warning_flag == 0: + self.app.inform.emit('[success] %s' % _("NCC Tool Rest Machining clear all done.")) + else: + self.app.inform.emit( + '[WARNING] %s: %s %s.' % (_("NCC Tool Rest Machining clear all done but the copper features " + "isolation is broken for"), str(warning_flag), _("tools"))) + return + + # create the solid_geometry + geo_obj.solid_geometry = list() + for tooluid in geo_obj.tools: + if geo_obj.tools[tooluid]['solid_geometry']: + try: + for geo in geo_obj.tools[tooluid]['solid_geometry']: + geo_obj.solid_geometry.append(geo) + except TypeError: + geo_obj.solid_geometry.append(geo_obj.tools[tooluid]['solid_geometry']) + else: + # I will use this variable for this purpose although it was meant for something else + # signal that we have no geo in the object therefore don't create it + app_obj.poly_not_cleared = False + return "fail" + + # ########################################################################################### + # Create the Job function and send it to the worker to be processed in another thread ####### + # ########################################################################################### + def job_thread(app_obj): + try: + if rest_machining_choice is True: + app_obj.new_object("geometry", name, gen_clear_area_rest, plot=plot) + else: + app_obj.new_object("geometry", name, gen_clear_area, plot=plot) + except FlatCAMApp.GracefulException: + if run_threaded: + proc.done() + return + except Exception: + if run_threaded: + proc.done() + traceback.print_stack() + return + + if run_threaded: + proc.done() + else: + app_obj.proc_container.view.set_idle() + + # focus on Selected Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab) + + if run_threaded: + # Promise object with the new name + self.app.collection.promise(name) + + # Background + self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) + else: + job_thread(app_obj=self.app) def get_ncc_empty_area(self, target, boundary=None): """ diff --git a/flatcamTools/ToolPanelize.py b/flatcamTools/ToolPanelize.py index 3271a85b..7159e81f 100644 --- a/flatcamTools/ToolPanelize.py +++ b/flatcamTools/ToolPanelize.py @@ -484,11 +484,8 @@ class Panelize(FlatCAMTool): if panel_obj is not None: self.app.inform.emit(_("Generating panel ... ")) - self.app.progress.emit(0) - def job_init_excellon(obj_fin, app_obj): currenty = 0.0 - self.app.progress.emit(10) obj_fin.tools = copied_tools obj_fin.drills = [] obj_fin.slots = [] diff --git a/flatcamTools/ToolPcbWizard.py b/flatcamTools/ToolPcbWizard.py index fb61e0fa..753b6667 100644 --- a/flatcamTools/ToolPcbWizard.py +++ b/flatcamTools/ToolPcbWizard.py @@ -417,8 +417,6 @@ class PcbWizard(FlatCAMTool): # How the object should be initialized def obj_init(excellon_obj, app_obj): - # self.progress.emit(20) - try: ret = excellon_obj.parse_file(file_obj=excellon_fileobj) if ret == "fail": @@ -427,10 +425,8 @@ class PcbWizard(FlatCAMTool): _("This is not Excellon file.")) return "fail" except IOError: - app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % ( - _("Cannot parse file"), self.outname)) + app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Cannot parse file"), self.outname)) app_obj.log.debug("Could not import Excellon object.") - app_obj.progress.emit(0) return "fail" except Exception as e: app_obj.log.debug("PcbWizard.on_import_excellon().obj_init() %s" % str(e)) @@ -443,7 +439,7 @@ class PcbWizard(FlatCAMTool): if ret == 'fail': app_obj.log.debug("Could not create geometry for Excellon object.") return "fail" - app_obj.progress.emit(100) + for tool in excellon_obj.tools: if excellon_obj.tools[tool]['solid_geometry']: return diff --git a/flatcamTools/ToolSolderPaste.py b/flatcamTools/ToolSolderPaste.py index 9eed101c..c2393bd2 100644 --- a/flatcamTools/ToolSolderPaste.py +++ b/flatcamTools/ToolSolderPaste.py @@ -1348,8 +1348,6 @@ class SolderPaste(FlatCAMTool): job_obj.options['ymax'] = ymax for tooluid_key, tooluid_value in obj.tools.items(): - app_obj.progress.emit(20) - # find the tool_dia associated with the tooluid_key tool_dia = tooluid_value['tooldia'] tool_cnc_dict = deepcopy(tooluid_value) @@ -1380,8 +1378,6 @@ class SolderPaste(FlatCAMTool): # tell gcode_parse from which point to start drawing the lines depending on what kind of # object is the source of gcode job_obj.toolchange_xy_type = "geometry" - app_obj.progress.emit(80) - job_obj.cnc_tools.update({ tooluid_key: deepcopy(tool_cnc_dict) }) @@ -1394,8 +1390,6 @@ class SolderPaste(FlatCAMTool): if app_obj.new_object("cncjob", name, job_init) != 'fail': app_obj.inform.emit('[success] [success] %s: %s' % (_("ToolSolderPaste CNCjob created"), name)) - app_obj.progress.emit(100) - # Create a promise with the name self.app.collection.promise(name) # Send to worker diff --git a/flatcamTools/ToolTransform.py b/flatcamTools/ToolTransform.py index 821c0d68..aa0a9a1e 100644 --- a/flatcamTools/ToolTransform.py +++ b/flatcamTools/ToolTransform.py @@ -682,8 +682,6 @@ class ToolTransform(FlatCAMTool): xmaximal = max(xmaxlist) ymaximal = max(ymaxlist) - self.app.progress.emit(20) - px = 0.5 * (xminimal + xmaximal) py = 0.5 * (yminimal + ymaximal) for sel_obj in obj_list: @@ -697,8 +695,6 @@ class ToolTransform(FlatCAMTool): sel_obj.options['rotate'] = num sel_obj.plot() self.app.inform.emit('[success] %s...' % _('Rotate done')) - self.app.progress.emit(100) - except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' % (_("Due of"), str(e), _("action was not executed."))) @@ -743,8 +739,6 @@ class ToolTransform(FlatCAMTool): px = 0.5 * (xminimal + xmaximal) py = 0.5 * (yminimal + ymaximal) - self.app.progress.emit(20) - # execute mirroring for sel_obj in obj_list: if isinstance(sel_obj, FlatCAMCNCjob): @@ -768,12 +762,9 @@ class ToolTransform(FlatCAMTool): sel_obj.options['mirror_x'] = not sel_obj.options['mirror_x'] else: sel_obj.options['mirror_x'] = True - self.app.inform.emit('[success] %s...' % - _('Flip on the X axis done')) + self.app.inform.emit('[success] %s...' % _('Flip on the X axis done')) self.app.object_changed.emit(sel_obj) sel_obj.plot() - self.app.progress.emit(100) - except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' % (_("Due of"), str(e), _("action was not executed."))) @@ -809,8 +800,6 @@ class ToolTransform(FlatCAMTool): xminimal = min(xminlist) yminimal = min(yminlist) - self.app.progress.emit(20) - for sel_obj in obj_list: if isinstance(sel_obj, FlatCAMCNCjob): self.app.inform.emit(_("CNCJob objects can't be skewed.")) @@ -825,10 +814,7 @@ class ToolTransform(FlatCAMTool): sel_obj.options['skew_y'] = num self.app.object_changed.emit(sel_obj) sel_obj.plot() - self.app.inform.emit('[success] %s %s %s...' % - (_('Skew on the'), str(axis), _("axis done"))) - self.app.progress.emit(100) - + self.app.inform.emit('[success] %s %s %s...' % (_('Skew on the'), str(axis), _("axis done"))) except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' % (_("Due of"), str(e), _("action was not executed."))) @@ -865,8 +851,6 @@ class ToolTransform(FlatCAMTool): xmaximal = max(xmaxlist) ymaximal = max(ymaxlist) - self.app.progress.emit(20) - if point is None: px = 0.5 * (xminimal + xmaximal) py = 0.5 * (yminimal + ymaximal) @@ -887,7 +871,6 @@ class ToolTransform(FlatCAMTool): self.app.inform.emit('[success] %s %s %s...' % (_('Scale on the'), str(axis), _('axis done'))) - self.app.progress.emit(100) except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' % (_("Due of"), str(e), _("action was not executed."))) @@ -903,8 +886,6 @@ class ToolTransform(FlatCAMTool): else: with self.app.proc_container.new(_("Applying Offset")): try: - self.app.progress.emit(20) - for sel_obj in obj_list: if isinstance(sel_obj, FlatCAMCNCjob): self.app.inform.emit(_("CNCJob objects can't be offset.")) @@ -922,8 +903,6 @@ class ToolTransform(FlatCAMTool): self.app.inform.emit('[success] %s %s %s...' % (_('Offset on the'), str(axis), _('axis done'))) - self.app.progress.emit(100) - except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' % (_("Due of"), str(e), _("action was not executed."))) diff --git a/tclCommands/TclCommandCopperClear.py b/tclCommands/TclCommandCopperClear.py index d7f8e612..c57aa8a2 100644 --- a/tclCommands/TclCommandCopperClear.py +++ b/tclCommands/TclCommandCopperClear.py @@ -219,22 +219,22 @@ class TclCommandCopperClear(TclCommand): # Non-Copper clear all polygons in the non-copper clear object if 'all' in args and bool(args['all']): - self.app.ncclear_tool.clear_copper(ncc_obj=obj, - select_method='itself', - ncctooldia=tooldia, - overlap=overlap, - order=order, - margin=margin, - has_offset=has_offset, - offset=offset, - method=method, - outname=outname, - connect=connect, - contour=contour, - rest=rest, - tools_storage=ncc_tools, - plot=False, - run_threaded=False) + self.app.ncclear_tool.clear_copper_tcl(ncc_obj=obj, + select_method='itself', + ncctooldia=tooldia, + overlap=overlap, + order=order, + margin=margin, + has_offset=has_offset, + offset=offset, + method=method, + outname=outname, + connect=connect, + contour=contour, + rest=rest, + tools_storage=ncc_tools, + plot=False, + run_threaded=False) return # Non-Copper clear all polygons found within the box object from the the non_copper cleared object @@ -252,23 +252,23 @@ class TclCommandCopperClear(TclCommand): self.raise_tcl_error("%s: %s" % (_("Could not retrieve box object"), name)) return "Could not retrieve object: %s" % name - self.app.ncclear_tool.clear_copper(ncc_obj=obj, - sel_obj=box_obj, - select_method='box', - ncctooldia=tooldia, - overlap=overlap, - order=order, - margin=margin, - has_offset=has_offset, - offset=offset, - method=method, - outname=outname, - connect=connect, - contour=contour, - rest=rest, - tools_storage=ncc_tools, - plot=False, - run_threaded=False) + self.app.ncclear_tool.clear_copper_tcl(ncc_obj=obj, + sel_obj=box_obj, + select_method='box', + ncctooldia=tooldia, + overlap=overlap, + order=order, + margin=margin, + has_offset=has_offset, + offset=offset, + method=method, + outname=outname, + connect=connect, + contour=contour, + rest=rest, + tools_storage=ncc_tools, + plot=False, + run_threaded=False) return else: self.raise_tcl_error("%s:" % _("None of the following args: 'ref', 'all' were found or none was set to 1.\n" diff --git a/tclCommands/TclCommandPanelize.py b/tclCommands/TclCommandPanelize.py index 77fcb15d..2a8c7c05 100644 --- a/tclCommands/TclCommandPanelize.py +++ b/tclCommands/TclCommandPanelize.py @@ -177,11 +177,8 @@ class TclCommandPanelize(TclCommand): if obj is not None: self.app.inform.emit("Generating panel ... Please wait.") - self.app.progress.emit(0) - def job_init_excellon(obj_fin, app_obj): currenty = 0.0 - self.app.progress.emit(10) obj_fin.tools = obj.tools.copy() obj_fin.drills = [] obj_fin.slots = [] @@ -247,7 +244,6 @@ class TclCommandPanelize(TclCommand): for tool in obj.tools: obj_fin.tools[tool]['solid_geometry'][:] = [] - self.app.progress.emit(0) for row in range(rows): currentx = 0.0 @@ -271,10 +267,8 @@ class TclCommandPanelize(TclCommand): currenty += lenghty if isinstance(obj, FlatCAMExcellon): - self.app.progress.emit(50) self.app.new_object("excellon", outname, job_init_excellon, plot=False, autoselected=True) else: - self.app.progress.emit(50) self.app.new_object("geometry", outname, job_init_geometry, plot=False, autoselected=True) if threaded is True: