diff --git a/FlatCAMApp.py b/FlatCAMApp.py index dce2111f..e073978d 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -803,6 +803,12 @@ class App(QtCore.QObject): "tools_pathconnect": True, "tools_paintcontour": True, "tools_paint_plotting": 'normal', + "tools_paintrest": False, + "tools_painttool_type": 'V', + "tools_paintcutz": -0.05, + "tools_painttipdia": 0.1, + "tools_painttipangle": 30, + "tools_paintnewdia": 1.0, # 2-Sided Tool "tools_2sided_mirror_axis": "X", diff --git a/README.md b/README.md index 2c446e8a..f91a123c 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ CAD program, and create G-Code for Isolation routing. - added key shortcuts and toolbar icons for the new tools: Align Object Tool (ALT+A) and Extract Drills (ALT+I) - added new functionality (key shortcut SHIFT+J) to locate the corners of the bounding box (and center) in a selected object +- modified the NCC Tool GUI to prepare for accepting a tool from a tool database +- started to modify the Paint Tool to be similar to NCC Tool and to accept a tool from a database 14.01.2020 diff --git a/flatcamTools/ToolNonCopperClear.py b/flatcamTools/ToolNonCopperClear.py index b8e365c5..5b2e8b1d 100644 --- a/flatcamTools/ToolNonCopperClear.py +++ b/flatcamTools/ToolNonCopperClear.py @@ -411,7 +411,7 @@ class NonCopperClear(FlatCAMTool, Gerber): _("Draw lines between resulting\n" "segments to minimize tool lifts.") ) - self.grid3.addWidget(self.ncc_connect_cb, 16, 0, 1, 2) + self.grid3.addWidget(self.ncc_connect_cb, 16, 0) # Contour self.ncc_contour_cb = FCCheckBox('%s' % _("Contour")) @@ -421,7 +421,69 @@ class NonCopperClear(FlatCAMTool, Gerber): _("Cut around the perimeter of the polygon\n" "to trim rough edges.") ) - self.grid3.addWidget(self.ncc_contour_cb, 17, 0, 1, 2) + self.grid3.addWidget(self.ncc_contour_cb, 16, 1) + + # ## 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" + "from the copper features.\n" + "The value can be between 0 and 10 FlatCAM units.") + ) + self.grid3.addWidget(self.ncc_choice_offset_cb, 19, 0) + + # ## NCC Offset value + # self.ncc_offset_label = QtWidgets.QLabel('%s:' % _("Offset value")) + # self.ncc_offset_label.setToolTip( + # _("If used, it will add an offset to the copper features.\n" + # "The copper clearing will finish to a distance\n" + # "from the copper features.\n" + # "The value can be between 0 and 10 FlatCAM units.") + # ) + self.ncc_offset_spinner = FCDoubleSpinner() + 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': + self.ncc_offset_spinner.setSingleStep(0.1) + else: + self.ncc_offset_spinner.setSingleStep(0.01) + + # self.grid3.addWidget(self.ncc_offset_label, 20, 0) + self.grid3.addWidget(self.ncc_offset_spinner, 19, 1) + + # self.ncc_offset_label.hide() + self.ncc_offset_spinner.setEnabled(False) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.grid3.addWidget(separator_line, 21, 0, 1, 2) + + self.apply_param_to_all = FCButton(_("Apply parameters to all tools")) + self.apply_param_to_all.setToolTip( + _("The parameters in the current form will be applied\n" + "on all the tools from the Tool Table.") + ) + self.grid3.addWidget(self.apply_param_to_all, 22, 0, 1, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.grid3.addWidget(separator_line, 23, 0, 1, 2) + + # General Parameters + self.gen_param_label = QtWidgets.QLabel('%s' % _("Common Parameters")) + self.gen_param_label.setToolTip( + _("Parameters that are common for all tools.") + ) + self.grid3.addWidget(self.gen_param_label, 24, 0, 1, 2) # Rest Machining self.ncc_rest_cb = FCCheckBox('%s' % _("Rest Machining")) @@ -437,51 +499,13 @@ class NonCopperClear(FlatCAMTool, Gerber): "If not checked, use the standard algorithm.") ) - self.grid3.addWidget(self.ncc_rest_cb, 18, 0, 1, 2) - - # ## 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" - "from the copper features.\n" - "The value can be between 0 and 10 FlatCAM units.") - ) - self.grid3.addWidget(self.ncc_choice_offset_cb, 19, 0, 1, 2) - - # ## NCC Offset value - self.ncc_offset_label = QtWidgets.QLabel('%s:' % _("Offset value")) - self.ncc_offset_label.setToolTip( - _("If used, it will add an offset to the copper features.\n" - "The copper clearing will finish to a distance\n" - "from the copper features.\n" - "The value can be between 0 and 10 FlatCAM units.") - ) - self.ncc_offset_spinner = FCDoubleSpinner() - 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': - self.ncc_offset_spinner.setSingleStep(0.1) - else: - self.ncc_offset_spinner.setSingleStep(0.01) - - self.grid3.addWidget(self.ncc_offset_label, 20, 0) - self.grid3.addWidget(self.ncc_offset_spinner, 20, 1) - - self.ncc_offset_label.hide() - self.ncc_offset_spinner.hide() + self.grid3.addWidget(self.ncc_rest_cb, 25, 0, 1, 2) # ## Reference self.reference_radio = RadioSet([ {'label': _('Itself'), 'value': 'itself'}, {"label": _("Area Selection"), "value": "area"}, - {'label': _("Reference Object"), 'value': 'box'} + {'label': _("Reference Object"), 'value': 'box'} ], orientation='vertical', stretch=False) self.reference_radio.setObjectName(_("Reference")) @@ -491,11 +515,11 @@ class NonCopperClear(FlatCAMTool, Gerber): "- 'Area Selection' - left mouse click to start selection of the area to be painted.\n" "- 'Reference Object' - will do non copper clearing within the area specified by another object.") ) - self.grid3.addWidget(self.reference_label, 21, 0) - self.grid3.addWidget(self.reference_radio, 21, 1) + self.grid3.addWidget(self.reference_label, 26, 0, 1, 2) + self.grid3.addWidget(self.reference_radio, 27, 0, 1, 2) form1 = QtWidgets.QFormLayout() - self.grid3.addLayout(form1, 22, 0, 1, 2) + self.grid3.addLayout(form1, 28, 0, 1, 2) self.box_combo_type_label = QtWidgets.QLabel('%s:' % _("Ref. Type")) self.box_combo_type_label.setToolTip( @@ -523,17 +547,10 @@ class NonCopperClear(FlatCAMTool, Gerber): self.box_combo_type.hide() self.box_combo_type_label.hide() - separator_line2 = QtWidgets.QFrame() - separator_line2.setFrameShape(QtWidgets.QFrame.HLine) - separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken) - self.grid3.addWidget(separator_line2, 23, 0, 1, 2) - - self.apply_param_to_all = FCButton(_("Apply parameters to all tools")) - self.apply_param_to_all.setToolTip( - _("The parameters in the current form will be applied\n" - "on all the tools from the Tool Table.") - ) - self.grid3.addWidget(self.apply_param_to_all, 24, 0, 1, 2) + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.grid3.addWidget(separator_line, 29, 0, 1, 2) self.generate_ncc_button = QtWidgets.QPushButton(_('Generate Geometry')) self.generate_ncc_button.setToolTip( @@ -630,10 +647,8 @@ class NonCopperClear(FlatCAMTool, Gerber): "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 } @@ -643,10 +658,8 @@ class NonCopperClear(FlatCAMTool, Gerber): _('Method'): "nccmethod", _("Connect"): "nccconnect", _("Contour"): "ncccontour", - _("Rest Machining"): "nccrest", _("Offset"): "nccoffset", _("Offset value"): "nccoffset_value", - _("Reference"): "nccref", _('Milling Type'): "milling_type", } @@ -720,7 +733,7 @@ class NonCopperClear(FlatCAMTool, Gerber): form_value_storage = tooluid_value[key] self.storage_to_form(form_value_storage) except Exception as e: - log.debug("FlatCAMObj ---> update_ui() " + str(e)) + log.debug("NonCopperClear ---> update_ui() " + str(e)) self.blockSignals(False) @@ -1164,12 +1177,13 @@ class NonCopperClear(FlatCAMTool, Gerber): self.box_combo_type_label.show() def on_offset_choice(self, state): - if state: - self.ncc_offset_label.show() - self.ncc_offset_spinner.show() - else: - self.ncc_offset_label.hide() - self.ncc_offset_spinner.hide() + # if state: + # self.ncc_offset_label.show() + # self.ncc_offset_spinner.show() + # else: + # self.ncc_offset_label.hide() + # self.ncc_offset_spinner.hide() + self.ncc_offset_spinner.setEnabled(state) def on_order_changed(self, order): if order != 'no': diff --git a/flatcamTools/ToolPaint.py b/flatcamTools/ToolPaint.py index 91dee46f..95fc26e7 100644 --- a/flatcamTools/ToolPaint.py +++ b/flatcamTools/ToolPaint.py @@ -14,13 +14,14 @@ from copy import deepcopy from flatcamParsers.ParseGerber import Gerber from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry from camlib import Geometry -from flatcamGUI.GUIElements import FCTable, FCDoubleSpinner, FCCheckBox, FCInputDialog, RadioSet +from flatcamGUI.GUIElements import FCTable, FCDoubleSpinner, FCCheckBox, FCInputDialog, RadioSet, FCButton import FlatCAMApp from shapely.geometry import base, Polygon, MultiPolygon, LinearRing from shapely.ops import cascaded_union import numpy as np +import math from numpy import Inf import traceback import logging @@ -66,8 +67,10 @@ class ToolPaint(FlatCAMTool, Gerber): self.tools_frame.setLayout(self.tools_box) # ## Form Layout - form_layout = QtWidgets.QFormLayout() - self.tools_box.addLayout(form_layout) + grid0 = QtWidgets.QGridLayout() + grid0.setColumnStretch(0, 0) + grid0.setColumnStretch(1, 1) + self.tools_box.addLayout(grid0) # ################################################ # ##### Type of object to be painted ############# @@ -90,7 +93,8 @@ class ToolPaint(FlatCAMTool, Gerber): "of objects that will populate the 'Object' combobox.") ) self.type_obj_combo_label.setMinimumWidth(60) - form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo) + grid0.addWidget(self.type_obj_combo_label, 1, 0) + grid0.addWidget(self.type_obj_combo, 1, 1) # ################################################ # ##### The object to be painted ################# @@ -103,10 +107,13 @@ class ToolPaint(FlatCAMTool, Gerber): self.object_label = QtWidgets.QLabel('%s:' % _("Object")) self.object_label.setToolTip(_("Object to be painted.")) - form_layout.addRow(self.object_label, self.obj_combo) + grid0.addWidget(self.object_label, 2, 0) + grid0.addWidget(self.obj_combo, 2, 1) - e_lab_0 = QtWidgets.QLabel('') - form_layout.addRow(e_lab_0) + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 5, 0, 1, 2) # ### Tools ## ## self.tools_table_label = QtWidgets.QLabel('%s' % _('Tools Table')) @@ -114,10 +121,11 @@ class ToolPaint(FlatCAMTool, Gerber): _("Tools pool from which the algorithm\n" "will pick the ones used for painting.") ) - self.tools_box.addWidget(self.tools_table_label) self.tools_table = FCTable() - self.tools_box.addWidget(self.tools_table) + + grid0.addWidget(self.tools_table_label, 6, 0, 1, 2) + grid0.addWidget(self.tools_table, 7, 0, 1, 2) self.tools_table.setColumnCount(4) self.tools_table.setHorizontalHeaderLabels(['#', _('Diameter'), _('TT'), '']) @@ -167,23 +175,107 @@ class ToolPaint(FlatCAMTool, Gerber): "'Reverse' --> menas that the tools will ordered from big to small\n\n" "WARNING: using rest machining will automatically set the order\n" "in reverse and disable this control.")) - form = QtWidgets.QFormLayout() - self.tools_box.addLayout(form) - form.addRow(QtWidgets.QLabel(''), QtWidgets.QLabel('')) - form.addRow(self.order_label, self.order_radio) - # ### Add a new Tool ## ## + grid0.addWidget(self.order_label, 9, 0) + grid0.addWidget(self.order_radio, 9, 1) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 10, 0, 1, 2) + + self.grid3 = QtWidgets.QGridLayout() + self.tools_box.addLayout(self.grid3) + self.grid3.setColumnStretch(0, 0) + self.grid3.setColumnStretch(1, 1) + + # ############################################################################## + # ###################### ADD A NEW TOOL ######################################## + # ############################################################################## + self.tool_sel_label = QtWidgets.QLabel('%s' % _("New Tool")) + self.grid3.addWidget(self.tool_sel_label, 1, 0, 1, 2) + + # Tool Type Radio Button + self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type')) + self.tool_type_label.setToolTip( + _("Default tool type:\n" + "- 'V-shape'\n" + "- Circular") + ) + + self.tool_type_radio = RadioSet([{'label': _('V-shape'), 'value': 'V'}, + {'label': _('Circular'), 'value': 'C1'}]) + self.tool_type_radio.setToolTip( + _("Default tool type:\n" + "- '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) + + # Tip Dia + self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia')) + self.tipdialabel.setToolTip( + _("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) + + # Tip Angle + self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle')) + self.tipanglelabel.setToolTip( + _("The tip angle for V-Shape Tool.\n" + "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) + + # Cut Z entry + cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z')) + cutzlabel.setToolTip( + _("Depth of cut into material. Negative value.\n" + "In FlatCAM units.") + ) + 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" + "In FlatCAM units.") + ) + self.grid3.addWidget(cutzlabel, 5, 0) + self.grid3.addWidget(self.cutz_entry, 5, 1) + + # ### Tool Diameter #### self.addtool_entry_lbl = QtWidgets.QLabel('%s:' % _('Tool Dia')) self.addtool_entry_lbl.setToolTip( - _("Diameter for the new tool.") + _("Diameter for the new tool to add in the Tool Table.\n" + "If the tool is V-shape type then this value is automatically\n" + "calculated from the other parameters.") ) 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")) - form.addRow(self.addtool_entry_lbl, self.addtool_entry) + self.grid3.addWidget(self.addtool_entry_lbl, 6, 0) + self.grid3.addWidget(self.addtool_entry, 6, 1) - grid2 = QtWidgets.QGridLayout() - self.tools_box.addLayout(grid2) + hlay = QtWidgets.QHBoxLayout() self.addtool_btn = QtWidgets.QPushButton(_('Add')) self.addtool_btn.setToolTip( @@ -191,29 +283,50 @@ class ToolPaint(FlatCAMTool, Gerber): "with the diameter specified above.") ) - # self.copytool_btn = QtWidgets.QPushButton('Copy') - # self.copytool_btn.setToolTip( - # "Copy a selection of tools in the Tool Table\n" - # "by first selecting a row in the Tool Table." - # ) + self.addtool_from_db_btn = QtWidgets.QPushButton(_('Add from DB')) + self.addtool_from_db_btn.setToolTip( + _("Add a new tool to the Tool Table\n" + "from the Tool DataBase.") + ) + + hlay.addWidget(self.addtool_btn) + hlay.addWidget(self.addtool_from_db_btn) + + self.grid3.addLayout(hlay, 7, 0, 1, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.grid3.addWidget(separator_line, 8, 0, 1, 2) self.deltool_btn = QtWidgets.QPushButton(_('Delete')) self.deltool_btn.setToolTip( _("Delete a selection of tools in the Tool Table\n" "by first selecting a row(s) in the Tool Table.") ) + self.grid3.addWidget(self.deltool_btn, 9, 0, 1, 2) - grid2.addWidget(self.addtool_btn, 0, 0) - # grid2.addWidget(self.copytool_btn, 0, 1) - grid2.addWidget(self.deltool_btn, 0, 2) + self.grid3.addWidget(QtWidgets.QLabel(''), 10, 0, 1, 2) - self.empty_label_0 = QtWidgets.QLabel('') - self.tools_box.addWidget(self.empty_label_0) + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.grid3.addWidget(separator_line, 11, 0, 1, 2) - grid3 = QtWidgets.QGridLayout() - self.tools_box.addLayout(grid3) - grid3.setColumnStretch(0, 0) - grid3.setColumnStretch(1, 1) + self.tool_data_label = QtWidgets.QLabel( + "%s: %s %d" % (_('Parameters for'), _("Tool"), int(1))) + self.tool_data_label.setToolTip( + _( + "The data used for creating GCode.\n" + "Each tool store it's own set of such data." + ) + ) + self.grid3.addWidget(self.tool_data_label, 12, 0, 1, 2) + + grid4 = QtWidgets.QGridLayout() + grid4.setColumnStretch(0, 0) + grid4.setColumnStretch(1, 1) + self.tools_box.addLayout(grid4) # Overlap ovlabel = QtWidgets.QLabel('%s:' % _('Overlap Rate')) @@ -231,8 +344,10 @@ class ToolPaint(FlatCAMTool, Gerber): self.paintoverlap_entry.setWrapping(True) self.paintoverlap_entry.setRange(0.0000, 99.9999) self.paintoverlap_entry.setSingleStep(0.1) - grid3.addWidget(ovlabel, 1, 0) - grid3.addWidget(self.paintoverlap_entry, 1, 1) + self.paintoverlap_entry.setObjectName(_("Overlap Rate")) + + grid4.addWidget(ovlabel, 1, 0) + grid4.addWidget(self.paintoverlap_entry, 1, 1) # Margin marginlabel = QtWidgets.QLabel('%s:' % _('Margin')) @@ -241,11 +356,12 @@ class ToolPaint(FlatCAMTool, Gerber): "the edges of the polygon to\n" "be painted.") ) - grid3.addWidget(marginlabel, 2, 0) self.paintmargin_entry = FCDoubleSpinner() self.paintmargin_entry.set_precision(self.decimals) + self.paintmargin_entry.setObjectName(_("Margin")) - grid3.addWidget(self.paintmargin_entry, 2, 1) + grid4.addWidget(marginlabel, 2, 0) + grid4.addWidget(self.paintmargin_entry, 2, 1) # Method methodlabel = QtWidgets.QLabel('%s:' % _('Method')) @@ -255,30 +371,60 @@ class ToolPaint(FlatCAMTool, Gerber): "- Seed-based: Outwards from seed.\n" "- Line-based: Parallel lines.") ) - grid3.addWidget(methodlabel, 3, 0) self.paintmethod_combo = RadioSet([ {"label": _("Standard"), "value": "standard"}, {"label": _("Seed-based"), "value": "seed"}, {"label": _("Straight lines"), "value": "lines"} ], orientation='vertical', stretch=False) - grid3.addWidget(self.paintmethod_combo, 3, 1) + self.paintmethod_combo.setObjectName(_("Method")) + + grid4.addWidget(methodlabel, 3, 0) + grid4.addWidget(self.paintmethod_combo, 3, 1) # Connect lines self.pathconnect_cb = FCCheckBox('%s' % _("Connect")) + self.pathconnect_cb.setObjectName(_("Connect")) self.pathconnect_cb.setToolTip( _("Draw lines between resulting\n" "segments to minimize tool lifts.") ) - grid3.addWidget(self.pathconnect_cb, 4, 0, 1, 2) self.paintcontour_cb = FCCheckBox('%s' % _("Contour")) + self.paintcontour_cb.setObjectName(_("Contour")) self.paintcontour_cb.setToolTip( _("Cut around the perimeter of the polygon\n" "to trim rough edges.") ) - grid3.addWidget(self.paintcontour_cb, 5, 0, 1, 2) + + grid4.addWidget(self.pathconnect_cb, 4, 0) + grid4.addWidget(self.paintcontour_cb, 4, 1) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid4.addWidget(separator_line, 5, 0, 1, 2) + + self.apply_param_to_all = FCButton(_("Apply parameters to all tools")) + self.apply_param_to_all.setToolTip( + _("The parameters in the current form will be applied\n" + "on all the tools from the Tool Table.") + ) + grid4.addWidget(self.apply_param_to_all, 7, 0, 1, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid4.addWidget(separator_line, 8, 0, 1, 2) + + # General Parameters + self.gen_param_label = QtWidgets.QLabel('%s' % _("Common Parameters")) + self.gen_param_label.setToolTip( + _("Parameters that are common for all tools.") + ) + grid4.addWidget(self.gen_param_label, 10, 0, 1, 2) self.rest_cb = FCCheckBox('%s' % _("Rest Machining")) + self.rest_cb.setObjectName(_("Rest Machining")) self.rest_cb.setToolTip( _("If checked, use 'rest machining'.\n" "Basically it will clear copper outside PCB features,\n" @@ -288,7 +434,7 @@ class ToolPaint(FlatCAMTool, Gerber): "no more copper to clear or there are no more tools.\n\n" "If not checked, use the standard algorithm.") ) - grid3.addWidget(self.rest_cb, 6, 0, 1, 2) + grid4.addWidget(self.rest_cb, 11, 0, 1, 2) # Polygon selection selectlabel = QtWidgets.QLabel('%s:' % _('Selection')) @@ -301,7 +447,7 @@ class ToolPaint(FlatCAMTool, Gerber): "- 'Reference Object' - will do non copper clearing within the area\n" "specified by another object.") ) - grid3.addWidget(selectlabel, 7, 0) + # grid3 = QtWidgets.QGridLayout() self.selectmethod_combo = RadioSet([ {"label": _("Polygon Selection"), "value": "single"}, @@ -309,6 +455,7 @@ class ToolPaint(FlatCAMTool, Gerber): {"label": _("All Polygons"), "value": "all"}, {"label": _("Reference Object"), "value": "ref"} ], orientation='vertical', stretch=False) + self.selectmethod_combo.setObjectName(_("Selection")) self.selectmethod_combo.setToolTip( _("How to select Polygons to be painted.\n" "- 'Polygon Selection' - left mouse click to add/remove polygons to be painted.\n" @@ -318,10 +465,12 @@ class ToolPaint(FlatCAMTool, Gerber): "- 'Reference Object' - will do non copper clearing within the area\n" "specified by another object.") ) - grid3.addWidget(self.selectmethod_combo, 7, 1) + + grid4.addWidget(selectlabel, 13, 0, 1, 2) + grid4.addWidget(self.selectmethod_combo, 14, 0, 1, 2) form1 = QtWidgets.QFormLayout() - self.tools_box.addLayout(form1) + grid4.addLayout(form1, 15, 0, 1, 2) self.box_combo_type_label = QtWidgets.QLabel('%s:' % _("Ref. Type")) self.box_combo_type_label.setToolTip( @@ -350,7 +499,7 @@ class ToolPaint(FlatCAMTool, Gerber): self.box_combo_type_label.hide() # GO Button - self.generate_paint_button = QtWidgets.QPushButton(_('Create Paint Geometry')) + self.generate_paint_button = QtWidgets.QPushButton(_('Generate Geometry')) self.generate_paint_button.setToolTip( _("- 'Area Selection' - left mouse click to start selection of the area to be painted.\n" "Keeping a modifier key pressed (CTRL or SHIFT) will allow to add multiple areas.\n" @@ -381,8 +530,28 @@ class ToolPaint(FlatCAMTool, Gerber): """) self.tools_box.addWidget(self.reset_button) - # #################################### FINSIHED GUI ##################################### - # ####################################################################################### + # #################################### FINSIHED GUI ########################### + # ############################################################################# + + # ############################################################################# + # ###################### Setup CONTEXT MENU ################################### + # ############################################################################# + self.tools_table.setupContextMenu() + self.tools_table.addContextMenu( + _("Add"), self.on_add_tool_by_key, icon=QtGui.QIcon(self.app.resource_location + "/plus16.png") + ) + self.tools_table.addContextMenu( + _("Add from DB"), self.on_add_tool_by_key, icon=QtGui.QIcon(self.app.resource_location + "/plus16.png") + ) + self.tools_table.addContextMenu( + _("Delete"), lambda: + self.on_tool_delete(rows_to_delete=None, all_tools=None), + icon=QtGui.QIcon(self.app.resource_location + "/delete32.png") + ) + + # ############################################################################# + # ########################## VARIABLES ######################################## + # ############################################################################# self.obj_name = "" self.paint_obj = None @@ -422,8 +591,8 @@ class ToolPaint(FlatCAMTool, Gerber): "name": '_paint', "plot": self.app.defaults["geometry_plot"], "cutz": self.app.defaults["geometry_cutz"], - "vtipdia": 0.1, - "vtipangle": 30, + "vtipdia": float(self.tipdia_entry.get_value()), + "vtipangle": float(self.tipangle_entry.get_value()), "travelz": self.app.defaults["geometry_travelz"], "feedrate": self.app.defaults["geometry_feedrate"], "feedrate_z": self.app.defaults["geometry_feedrate_z"], @@ -448,19 +617,43 @@ class ToolPaint(FlatCAMTool, Gerber): "selectmethod": self.app.defaults["tools_selectmethod"], "pathconnect": self.app.defaults["tools_pathconnect"], "paintcontour": self.app.defaults["tools_paintcontour"], - "paintoverlap": self.app.defaults["tools_paintoverlap"] + "paintoverlap": self.app.defaults["tools_paintoverlap"], + "paintrest": self.app.defaults["tools_paintrest"], }) self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"] + self.form_fields = { + "paintoverlap": self.paintoverlap_entry, + "paintmargin": self.paintmargin_entry, + "paintmethod": self.paintmethod_combo, + "pathconnect": self.pathconnect_cb, + "paintcontour": self.paintcontour_cb, + } + + self.name2option = { + _('Overlap Rate'): "paintoverlap", + _('Margin'): "paintmargin", + _('Method'): "paintmethod", + _("Connect"): "pathconnect", + _("Contour"): "paintcontour", + } + # ############################################################################# # ################################# Signals ################################### # ############################################################################# self.addtool_btn.clicked.connect(self.on_tool_add) self.addtool_entry.returnPressed.connect(self.on_tool_add) - # self.copytool_btn.clicked.connect(lambda: self.on_tool_copy()) - self.tools_table.itemChanged.connect(self.on_tool_edit) self.deltool_btn.clicked.connect(self.on_tool_delete) + + self.tipdia_entry.returnPressed.connect(self.on_calculate_tooldia) + self.tipangle_entry.returnPressed.connect(self.on_calculate_tooldia) + self.cutz_entry.returnPressed.connect(self.on_calculate_tooldia) + + # self.copytool_btn.clicked.connect(lambda: self.on_tool_copy()) + # self.tools_table.itemChanged.connect(self.on_tool_edit) + self.tools_table.currentItemChanged.connect(self.on_row_selection_change) + self.generate_paint_button.clicked.connect(self.on_paint_button_click) self.selectmethod_combo.activated_custom.connect(self.on_radio_selection) self.order_radio.activated_custom[str].connect(self.on_order_changed) @@ -489,22 +682,6 @@ class ToolPaint(FlatCAMTool, Gerber): def install(self, icon=None, separator=None, **kwargs): FlatCAMTool.install(self, icon, separator, shortcut='ALT+P', **kwargs) - def on_add_tool_by_key(self): - tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"), - text='%s:' % _('Enter a Tool Diameter'), - min=0.0000, max=99.9999, decimals=4) - tool_add_popup.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/letter_t_32.png')) - - val, ok = tool_add_popup.get_value() - if ok: - if float(val) == 0: - self.app.inform.emit('[WARNING_NOTCL] %s' % - _("Please enter a tool diameter with non-zero value, in Float format.")) - return - self.on_tool_add(dia=float(val)) - else: - self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Adding Tool cancelled")) - def run(self, toggle=True): self.app.report_usage("ToolPaint()") @@ -532,16 +709,203 @@ class ToolPaint(FlatCAMTool, Gerber): self.app.ui.notebook.setTabText(2, _("Paint Tool")) - def reset_usage(self): - self.obj_name = "" - self.paint_obj = None - self.bound_obj = None + def on_row_selection_change(self): + self.update_ui() - self.first_click = False - self.cursor_pos = None - self.mouse_is_dragging = False + def update_ui(self, row=None): + self.blockSignals(True) - self.sel_rect = [] + if row is None: + try: + current_row = self.tools_table.currentRow() + except Exception: + current_row = 0 + else: + current_row = row + + if current_row < 0: + current_row = 0 + + # populate the form with the data from the tool associated with the row parameter + try: + item = self.tools_table.item(current_row, 3) + if item is not None: + tooluid = int(item.text()) + else: + return + except Exception as e: + log.debug("Tool missing. Add a tool in the Tool Table. %s" % str(e)) + return + + # 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"), (current_row + 1)) + ) + + try: + # set the form with data from the newly selected tool + for tooluid_key, tooluid_value in list(self.paint_tools.items()): + if int(tooluid_key) == tooluid: + for key, value in tooluid_value.items(): + if key == 'data': + form_value_storage = tooluid_value[key] + self.storage_to_form(form_value_storage) + except Exception as e: + log.debug("ToolPaint ---> update_ui() " + str(e)) + + self.blockSignals(False) + + 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: + pass + + 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.paint_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: + # there is no tool in tool table so we can't save the GUI elements values to storage + log.debug("NonCopperClear.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.") + return + + self.blockSignals(True) + + row = self.tools_table.currentRow() + if row < 0: + row = 0 + + # 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.paint_tools.items(): + for key, value in tooluid_value.items(): + 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() + + 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) + + self.paint_tools.clear() + self.paint_tools = deepcopy(temp_tools) + temp_tools.clear() + + self.blockSignals(False) + + def on_add_tool_by_key(self): + tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"), + text='%s:' % _('Enter a Tool Diameter'), + min=0.0000, max=99.9999, decimals=4) + tool_add_popup.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/letter_t_32.png')) + + val, ok = tool_add_popup.get_value() + if ok: + if float(val) == 0: + self.app.inform.emit('[WARNING_NOTCL] %s' % + _("Please enter a tool diameter with non-zero value, in Float format.")) + return + self.on_tool_add(dia=float(val)) + else: + self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Adding Tool cancelled")) + + 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()) + + # if the sender is in the column with index 2 then we update the tool_type key + if cw_col == 2: + tt = cw.currentText() + typ = 'Iso' if tt == 'V' else "Rough" + + self.paint_tools[current_uid].update({ + 'type': typ, + 'tool_type': tt, + }) + + def on_tool_type(self, val): + if val == 'V': + self.addtool_entry_lbl.setDisabled(True) + self.addtool_entry.setDisabled(True) + self.tipdialabel.show() + self.tipdia_entry.show() + self.tipanglelabel.show() + self.tipangle_entry.show() + else: + self.addtool_entry_lbl.setDisabled(False) + self.addtool_entry.setDisabled(False) + self.tipdialabel.hide() + self.tipdia_entry.hide() + self.tipanglelabel.hide() + self.tipangle_entry.hide() + + def on_calculate_tooldia(self): + if self.tool_type_radio.get_value() == 'V': + tip_dia = float(self.tipdia_entry.get_value()) + tip_angle = float(self.tipangle_entry.get_value()) / 2.0 + cut_z = float(self.cutz_entry.get_value()) + cut_z = -cut_z if cut_z < 0 else cut_z + + # calculated tool diameter so the cut_z parameter is obeyed + tool_dia = tip_dia + (2 * cut_z * math.tan(math.radians(tip_angle))) + + # update the default_data so it is used in the ncc_tools dict + self.default_data.update({ + "vtipdia": tip_dia, + "vtipangle": (tip_angle * 2), + }) + + self.addtool_entry.set_value(tool_dia) + + return tool_dia + else: + return float(self.addtool_entry.get_value()) def on_radio_selection(self): if self.selectmethod_combo.get_value() == "ref": @@ -596,6 +960,14 @@ class ToolPaint(FlatCAMTool, Gerber): self.paintcontour_cb.set_value(self.default_data["paintcontour"]) self.paintoverlap_entry.set_value(self.default_data["paintoverlap"]) + self.cutz_entry.set_value(self.app.defaults["tools_paintcutz"]) + self.tool_type_radio.set_value(self.app.defaults["tools_painttool_type"]) + self.tipdia_entry.set_value(self.app.defaults["tools_painttipdia"]) + self.tipangle_entry.set_value(self.app.defaults["tools_painttipangle"]) + self.addtool_entry.set_value(self.app.defaults["tools_paintnewdia"]) + + self.on_tool_type(val=self.tool_type_radio.get_value()) + # make the default object type, "Geometry" self.type_obj_combo.setCurrentIndex(2) # updated units @@ -610,8 +982,8 @@ class ToolPaint(FlatCAMTool, Gerber): "name": '_paint', "plot": self.app.defaults["geometry_plot"], "cutz": float(self.app.defaults["geometry_cutz"]), - "vtipdia": 0.1, - "vtipangle": 30, + "vtipdia": float(self.tipdia_entry.get_value()), + "vtipangle": float(self.tipangle_entry.get_value()), "travelz": float(self.app.defaults["geometry_travelz"]), "feedrate": float(self.app.defaults["geometry_feedrate"]), "feedrate_z": float(self.app.defaults["geometry_feedrate_z"]), @@ -1077,9 +1449,7 @@ class ToolPaint(FlatCAMTool, Gerber): try: self.bound_obj = self.app.collection.get_by_name(self.bound_obj_name) except Exception as e: - self.app.inform.emit('[ERROR_NOTCL] %s: %s' % - (_("Could not retrieve object"), - self.obj_name)) + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), self.obj_name)) return "Could not retrieve object: %s" % self.obj_name self.paint_poly_ref(obj=self.paint_obj, @@ -2694,6 +3064,105 @@ class ToolPaint(FlatCAMTool, Gerber): plot=plot, run_threaded=run_threaded) + def ui_connect(self): + self.tools_table.itemChanged.connect(self.on_tool_edit) + + for row in range(self.tools_table.rowCount()): + 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() + 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 + + # then reconnect + 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) + + self.ncc_choice_offset_cb.stateChanged.connect(self.on_offset_choice) + self.ncc_rest_cb.stateChanged.connect(self.on_rest_machining_check) + self.ncc_order_radio.activated_custom[str].connect(self.on_order_changed) + + 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() + 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() + except (TypeError, AttributeError): + pass + + for row in range(self.tools_table.rowCount()): + for col in [2, 4]: + try: + self.ui.geo_tools_table.cellWidget(row, col).currentIndexChanged.disconnect() + 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 reset_usage(self): + self.obj_name = "" + self.paint_obj = None + self.bound_obj = None + + self.first_click = False + self.cursor_pos = None + self.mouse_is_dragging = False + + self.sel_rect = [] + + @staticmethod def paint_bounds(geometry): def bounds_rec(o):